docs: мануал развёртывания в прод

Полная первичная установка на боевой сервер (LXC за Traefik, gunicorn,
nginx, PostgreSQL, Redis c requirepass, Celery worker+beat), настройка
интеграций (Mattermost/Nextcloud/OnlyOffice/почта/AD/PNMS-PVMS-MSorter),
бэкапы, обновление и типичные грабли. Дополняет перенос-на-прод.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 13:25:45 +03:00
parent 3ce3536e37
commit 3812098970
+361
View File
@@ -0,0 +1,361 @@
# Развёртывание ACORE в production
Полный мануал первичной установки портала на боевой сервер «с нуля».
Перенос данных из тестовой БД описан отдельно — см. [перенос-на-прод.md](перенос-на-прод.md).
**Целевая топология (как в `.env.example`):**
```
HTTPS HTTP :8000
клиент ───────▶ Traefik ───────▶ LXC с ACORE ──▶ PostgreSQL (10.10.31.2:5432)
(TLS) (Host rule) ├─ nginx ─▶ gunicorn (WSGI) └─ Redis (:6379, requirepass)
├─ celery worker
└─ celery beat
```
- **Traefik** терминирует TLS и проксирует на LXC по HTTP с заголовком `X-Forwarded-Proto: https`
(`production.py` уже настроен на `SECURE_PROXY_SSL_HEADER`).
- **nginx внутри LXC** отдаёт `/static/` и `/media/`, остальное проксирует в gunicorn.
- **PostgreSQL** и **Redis** — отдельные сервисы (могут быть на других хостах).
---
## 0. Требования
| Компонент | Версия | Назначение |
|---|---|---|
| Python | 3.14 | как в `.venv` проекта |
| PostgreSQL | 14+ | основная БД |
| Redis | 6+ | кэш (троттлинг логина) + брокер Celery, **обязателен `requirepass`** |
| nginx | любой | статика/медиа + reverse-proxy |
| gettext | — | для `compilemessages` (uk/en) |
| gcc, libpq-dev, libldap2-dev, libsasl2-dev | — | сборка psycopg2 / ldap3 |
Внешние интеграции (опциональны, настраиваются позже в админке): Mattermost, Nextcloud (WebDAV) + OnlyOffice Document Server, почта (IMAP/SMTP), Active Directory, порталы PNMS/PVMS/MSorter.
---
## 1. Системный пользователь и код
```bash
# от root
adduser --system --group --home /opt/acore acore
apt-get update && apt-get install -y \
python3.14 python3.14-venv python3-pip \
build-essential libpq-dev libldap2-dev libsasl2-dev gettext \
nginx redis-server postgresql-client git
sudo -u acore -H bash
cd /opt/acore
git clone <REPO_URL> app # или скопировать релиз
cd app
python3.14 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -r requirements.txt
```
> `main.css` (Tailwind) уже собран и лежит в репозитории — npm-сборка в проде **не нужна**.
> Пересобирать только при изменении стилей (см. `ACORE-template/`).
---
## 2. PostgreSQL
На сервере БД (10.10.31.2):
```sql
CREATE USER acore WITH PASSWORD 'СИЛЬНЫЙ_ПАРОЛЬ';
CREATE DATABASE acore OWNER acore ENCODING 'UTF8' LC_COLLATE 'C' TEMPLATE template0;
GRANT ALL PRIVILEGES ON DATABASE acore TO acore;
```
`pg_hba.conf` — разрешить доступ с IP LXC по `scram-sha-256`. Проверка с LXC:
```bash
psql "postgres://acore:ПАРОЛЬ@10.10.31.2:5432/acore" -c '\conninfo'
```
---
## 3. Redis (с паролем — production это требует)
`/etc/redis/redis.conf`:
```
requirepass СИЛЬНЫЙ_REDIS_ПАРОЛЬ
bind 127.0.0.1 # или адрес, доступный только LXC
maxmemory-policy allkeys-lru
```
```bash
systemctl enable --now redis-server
redis-cli -a СИЛЬНЫЙ_REDIS_ПАРОЛЬ ping # → PONG
```
> Продовые настройки **откажутся стартовать**, если в `CACHE_URL` / `CELERY_BROKER_URL`
> нет пароля (см. `_assert_redis_auth`). Временный обход на время cutover:
> `REDIS_ALLOW_UNAUTHENTICATED=1` (убрать сразу после установки `requirepass`).
Используются две БД Redis: **`/0`** — брокер Celery, **`/1`** — кэш Django.
---
## 4. Секреты и `.env`
Сгенерировать два независимых секрета:
```bash
python3 -c "import secrets; print(secrets.token_urlsafe(64))" # для SECRET_KEY
python3 -c "import secrets; print(secrets.token_urlsafe(64))" # для FIELD_ENCRYPTION_KEY
```
> ⚠️ **`FIELD_ENCRYPTION_KEY`** — это не Fernet-ключ, а любая длинная строка
> (внутри `core/utils/crypto.py` она хэшируется в ключ). **Задать один раз и не менять** —
> смена ключа делает все зашифрованные секреты коннекторов (пароли/токены) нечитаемыми.
> Хранить в менеджере секретов.
`/opt/acore/app/.env`:
```ini
DJANGO_SETTINGS_MODULE=acore.settings.production
DEBUG=False
SECRET_KEY=<секрет-1>
FIELD_ENCRYPTION_KEY=<секрет-2>
ALLOWED_HOSTS=portal.example.local,10.10.31.3
PORTAL_BASE_URL=https://portal.example.local
CSRF_TRUSTED_ORIGINS=https://portal.example.local
DATABASE_URL=postgres://acore:ПАРОЛЬ@10.10.31.2:5432/acore
# Redis с паролем (две отдельные БД)
CACHE_URL=redis://:REDIS_ПАРОЛЬ@127.0.0.1:6379/1
CELERY_BROKER_URL=redis://:REDIS_ПАРОЛЬ@127.0.0.1:6379/0
CELERY_RESULT_BACKEND=redis://:REDIS_ПАРОЛЬ@127.0.0.1:6379/0
# Интеграции (заполняются позже; пустые = fail-closed)
# MATTERMOST_SLASH_COMMAND_TOKEN=
# MATTERMOST_INTERACTIVE_TOKEN=
# PNMS_SYNC_ENABLED=1 / PNMS_BASE_URL=https://... / PNMS_ACORE_TOKEN=...
# MSORTER_* / PVMS_* — см. .env.example
```
```bash
chmod 600 /opt/acore/app/.env && chown acore:acore /opt/acore/app/.env
```
---
## 5. Миграции, статика, локали, суперпользователь
```bash
cd /opt/acore/app
export DJANGO_SETTINGS_MODULE=acore.settings.production
.venv/bin/python manage.py check --deploy # должно быть 0 issues
.venv/bin/python manage.py migrate
.venv/bin/python manage.py collectstatic --noinput # → staticfiles/
.venv/bin/python manage.py compilemessages # uk/en (нужен gettext)
.venv/bin/python manage.py rebuild_directory_tree # пересборка дерева справочников
.venv/bin/python manage.py createsuperuser
```
> При первом входе суперпользователь **обязан включить 2FA** (middleware принуждает
> всех staff). Держите под рукой доступ к TOTP-приложению.
---
## 6. gunicorn (systemd)
`/etc/systemd/system/acore-web.service`:
```ini
[Unit]
Description=ACORE gunicorn
After=network.target
[Service]
User=acore
Group=acore
WorkingDirectory=/opt/acore/app
EnvironmentFile=/opt/acore/app/.env
ExecStart=/opt/acore/app/.venv/bin/gunicorn acore.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 5 \
--timeout 120 \
--access-logfile - --error-logfile -
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
```
> Воркеры: `(2 × vCPU) + 1`. Загрузки/скачивания документов и OnlyOffice-callback
> могут быть крупными — `--timeout 120`.
---
## 7. Celery worker + beat (systemd)
`/etc/systemd/system/acore-celery.service`:
```ini
[Unit]
Description=ACORE Celery worker
After=network.target redis-server.service
[Service]
User=acore
Group=acore
WorkingDirectory=/opt/acore/app
EnvironmentFile=/opt/acore/app/.env
ExecStart=/opt/acore/app/.venv/bin/celery -A acore worker -l info --concurrency=4
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
`/etc/systemd/system/acore-celery-beat.service`:
```ini
[Unit]
Description=ACORE Celery beat
After=network.target redis-server.service
[Service]
User=acore
Group=acore
WorkingDirectory=/opt/acore/app
EnvironmentFile=/opt/acore/app/.env
ExecStart=/opt/acore/app/.venv/bin/celery -A acore beat -l info \
--scheduler django_celery_beat.schedulers:DatabaseScheduler
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
systemctl daemon-reload
systemctl enable --now acore-web acore-celery acore-celery-beat
systemctl status acore-web
```
> Celery нужен для фоновой синхронизации (Mattermost, AD, push в PNMS/PVMS/MSorter).
> Периодические задачи задаются в админке (django-celery-beat, DatabaseScheduler).
---
## 8. nginx (статика/медиа + proxy)
`/etc/nginx/sites-available/acore`:
```nginx
server {
listen 80;
server_name portal.example.local 10.10.31.3;
client_max_body_size 110m; # аплоады до 100 МБ (документы)
location /static/ { alias /opt/acore/app/staticfiles/; access_log off; expires 30d; }
location /media/ { alias /opt/acore/app/media/; access_log off; }
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; # приходит от Traefik
proxy_read_timeout 120s;
}
}
```
```bash
ln -s /etc/nginx/sites-available/acore /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
```
**Traefik** (внешний): Host-rule на `portal.example.local` → сервис `http://10.10.31.3:80`,
TLS-termination + редирект на HTTPS. Убедитесь, что Traefik проставляет
`X-Forwarded-Proto: https` (по умолчанию так и есть) — иначе `SECURE_SSL_REDIRECT`
уйдёт в цикл редиректов.
---
## 9. Настройка интеграций (после запуска, в админке)
Все секреты коннекторов шифруются в БД (`EncryptedCharField`) — вводятся в
**Админка → Настройки → Конфиги коннекторов**.
- **Mattermost** — bot-токен + `MATTERMOST_SLASH_COMMAND_TOKEN`/`INTERACTIVE_TOKEN` в `.env`.
Команды: `create_mattermost_bot`, `import_mattermost_teams/channels`, `sync_users_to_teams`.
- **Nextcloud** — `ConnectorConfig(nextcloud)`: host/логин/пароль сервисного аккаунта (WebDAV).
- **OnlyOffice** (`ConnectorConfig(onlyoffice)`):
- `host/port/use_ssl` → публичный URL Document Server (браузер грузит `api.js`);
- **«Токен»** = `JWT_SECRET` Document Server (`JWT_ENABLED=true`, `JWT_HEADER=Authorization`);
- `extra_config`: `{"public_url": "...", "callback_base": "https://portal.example.local", "verify_ssl": true}`.
- **Сеть в обе стороны:** браузер → DS, DS → портал (`/documents/onlyoffice/content|callback`).
Если DS ходит к порталу по внутреннему адресу — добавьте его в `extra_config.download_hosts`
(иначе SSRF-guard отклонит сохранение).
- **Почта** — `ConnectorConfig(mail)`: IMAP/SMTP хост, сервисный аккаунт.
- **Active Directory** — `ConnectorConfig(ad)`; синк: `sync_users_to_ad`.
- **PNMS/PVMS/MSorter** — включаются флагами в `.env` (`*_SYNC_ENABLED=1`, `*_BASE_URL` **https**,
`*_ACORE_TOKEN`). Первичная выгрузка: `push_to_pnms` / `push_to_pvms` / `push_to_msorter`.
---
## 10. Проверочный чек-лист
```bash
export DJANGO_SETTINGS_MODULE=acore.settings.production
cd /opt/acore/app
.venv/bin/python manage.py check --deploy # 0 issues
.venv/bin/python manage.py makemigrations --check --dry-run # No changes detected
```
- [ ] `https://portal.example.local/` открывается, редирект c http на https работает;
- [ ] логин, принудительная 2FA у staff;
- [ ] `/static/` и `/media/` отдаёт nginx (не 404);
- [ ] `systemctl status acore-web acore-celery acore-celery-beat` — все `active (running)`;
- [ ] `redis-cli -a … -n 1 dbsize` растёт при попытках логина (кэш-троттлинг жив);
- [ ] загрузка документа в аптеке и открытие в OnlyOffice → правка сохраняется в Nextcloud.
---
## 11. Резервное копирование
```bash
# БД (ежедневно, cron от пользователя postgres или acore)
pg_dump "postgres://acore:ПАРОЛЬ@10.10.31.2:5432/acore" | gzip > /backup/acore-$(date +%F).sql.gz
# Медиа
rsync -a /opt/acore/app/media/ /backup/media/
# КРИТИЧНО: .env (SECRET_KEY + FIELD_ENCRYPTION_KEY) — в менеджер секретов.
# Потеря FIELD_ENCRYPTION_KEY = потеря всех секретов коннекторов.
```
---
## 12. Обновление (деплой новой версии)
```bash
sudo -u acore -H bash
cd /opt/acore/app
git pull # или распаковать релиз
.venv/bin/pip install -r requirements.txt
export DJANGO_SETTINGS_MODULE=acore.settings.production
.venv/bin/python manage.py migrate
.venv/bin/python manage.py collectstatic --noinput
.venv/bin/python manage.py compilemessages
exit
systemctl restart acore-web acore-celery acore-celery-beat
```
---
## 13. Частые грабли
| Симптом | Причина / решение |
|---|---|
| `ImproperlyConfigured: FIELD_ENCRYPTION_KEY must be set` | не задан ключ в `.env` |
| `ImproperlyConfigured: CACHE_URL must include a Redis password` | нет пароля в Redis-URL; задать `requirepass` или временно `REDIS_ALLOW_UNAUTHENTICATED=1` |
| Бесконечный редирект на https | Traefik не шлёт `X-Forwarded-Proto: https` / nginx не пробрасывает заголовок |
| Секреты коннекторов «поехали» после смены ключа | `FIELD_ENCRYPTION_KEY` менять нельзя после ввода секретов |
| Стили/иконки админки не грузятся | не выполнен `collectstatic` или nginx `location /static/` указывает не туда |
| OnlyOffice не сохраняет файл | не совпадает `JWT_SECRET`↔«Токен», или DS не достаёт `callback_base`; проверьте `download_hosts` |
| uk/en не переведены | не выполнен `compilemessages` (нет пакета `gettext`) |
```