# Развёртывание 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/` только пока media на локальном диске). - **PostgreSQL** и **Redis** — отдельные сервисы (могут быть на других хостах). - **SILO (S3)** — опционально: Django media + primary store Nextcloud; см. [silo.md](silo.md). --- ## 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 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; } # При SILO (S3_ENDPOINT в .env) /media/ с диска не нужен — URL подписанные. 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). Файлы NC лучше держать в SILO (primary object store) — [silo.md](silo.md). - **SILO / media** — при `S3_ENDPOINT` + ключах в `.env` фото и прочий `MEDIA` пишутся в бакет `acore-media`; nginx `/media/` с диска не нужен. - **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/` отдаёт nginx; `/media/` — nginx **или** подписанные URL SILO (см. [silo.md](silo.md)); - [ ] `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`) | ```