Files
ACORE/docs/развёртывание-прод.md
T
lokses 030bbc98fd projects: модуль проектов с шагами по отделам и Task.
Шаблоны/ручное создание, access_projects, сид «Открытие аптеки». Плюс SILO/S3 для Django media.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-23 16:26:45 +03:00

366 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Развёртывание 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 <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; }
# При 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`) |
```