# Развёртывание Клонируем репозиторий и поднимаем контур Compose-ом. Контуры `test` и `prod` разделяются именем проекта (`-p`) и файлом окружения (`--env-file`) — у каждого свои БД и volume'ы, на одной машине не пересекаются. ## 1. Клонировать ```bash git clone ssh://git@:2222/mpakobecex/DispelEvilBot.git cd DispelEvilBot ``` ## 2. Подготовить окружение ```bash cp env/test.env.example env/test.env # заполнить: BOT_TOKEN, DB_PASSWORD, CHAT_ID (основной чат), ALERT_CHAT_ID (опц.), # ADMIN_IDS (опц.) # плюс для админки: BOT_USERNAME, ADMIN_PUBLIC_URL (HTTPS-домен), # ADMIN_SESSION_SECRET (openssl rand -hex 32) ``` Порт админки (`ADMIN_HTTP_PORT`) у контуров должен различаться: `prod` — `8101`, `test` — `8100` (8000 на VPS занят Portainer). `ADMIN_BIND_IP` по умолчанию `127.0.0.1`; чтобы nginx-proxy-manager (контейнер) видел админку, ставьте `172.17.0.1`. ## 3. Логин в registry (если пакет приватный) ```bash docker login git.mpakosrv.ru -u mpakobecex -p <ТОКЕН с read:package> ``` Если пакет сделать публичным — логин не нужен. ## 4. Запуск ```bash # тестовый контур docker compose -p test --env-file env/test.env -f docker-compose.prod.yml up -d # прод (когда понадобится) cp env/prod.env.example env/prod.env docker compose -p prod --env-file env/prod.env -f docker-compose.prod.yml up -d ``` Логи: ```bash docker compose -p test --env-file env/test.env -f docker-compose.prod.yml logs -f ``` ## Что происходит при старте 1. `postgres` поднимается и проходит healthcheck; 2. `migrate` (**Flyway**) накатывает миграции из `sql/` — для пустой БД с нуля; 3. `bot` стартует (`Запуск DispelEvilBot v<версия> (APP_ENV=...)`), аналитика и рассылки — в нём же; 4. `admin` стартует и отвечает на `GET /healthz` (порт на loopback хоста). Проверка контура (сервисы, миграции, Postgres, схема аналитики): ```bash make check E=test ``` ## Аналитика (Postgres) События и дневные счётчики сообщений пишутся в схему `analytics` той же БД Postgres (контракт — [`docs/events.md`](docs/events.md)). Отдельного сервиса нет. ```bash make psql E=test # пример: события за сегодня по типам SELECT event_type, count(*) FROM analytics.events WHERE event_time >= current_date GROUP BY event_type; # сообщения по дням SELECT day, sum(count) FROM analytics.messages_daily WHERE day >= current_date - 7 GROUP BY day ORDER BY day; ``` Если Postgres недоступен, бот продолжает работать: батч аналитики уходит в stdout (WARNING) и отбрасывается. Полностью выключить можно флагом `ANALYTICS_ENABLED=false`. Партиции создаются приложением при старте и раз в 6 часов; старые **не удаляются**. Схема создаётся миграцией `V26__analytics_schema.sql`. ## Админка (FastAPI) Сервис `admin` — отдельный образ `mpakobecex/dispelevilbot-admin`, общий пакет `common`. Наружу пока **не публикуется**: по умолчанию только loopback хоста (`127.0.0.1:${ADMIN_HTTP_PORT}`). Для nginx-proxy-manager (он в контейнере) задайте `ADMIN_BIND_IP=172.17.0.1` и указывайте в NPM цель `172.17.0.1:8100` (test) / `172.17.0.1:8101` (prod). Домен и TLS — Блок 4.2. ```bash # проверка живости curl -s http://127.0.0.1:8100/healthz # test curl -s http://127.0.0.1:8101/healthz # prod ``` Локально без Docker: `pip install -r requirements-admin.txt && python -m admin` (по умолчанию `0.0.0.0:8000`, переопределяется `ADMIN_HOST`/`ADMIN_PORT`). ### Вход через Telegram Админка закрыта Telegram Login Widget. Что нужно на контуре: - `BOT_TOKEN` — токен бота этого контура (им же проверяется подпись `hash`); - `BOT_USERNAME` — username бота без `@` (виджет рисует кнопку); - `ADMIN_PUBLIC_URL` — публичный HTTPS-адрес (`https://debot-test.mpakosrv.ru`); - `ADMIN_SESSION_SECRET` — `openssl rand -hex 32`; - `ADMIN_IDS` — Telegram-ID администраторов: ими засевается `bot.admin_users` при старте (`ON CONFLICT DO NOTHING`, существующих не трогает). Домен должен быть привязан к боту в `@BotFather` (`/setdomain`), а TLS — настроен на reverse proxy. Сессия живёт 12 часов (`ADMIN_SESSION_MAX_AGE`), cookie подписана и `HttpOnly`; в проде ставится флаг `Secure`. Все POST-формы проверяют CSRF-токен. Роль администратора перечитывается из БД на каждом запросе — отключение пользователя (`enabled = false`) или удаление записи сразу закрывает доступ. ## Бэкап Аналитика теперь лежит в Postgres, поэтому отдельного бэкапа событий больше нет. Бэкап БД — стандартными средствами Postgres (`pg_dump`/`pg_basebackup`); при росте истории следите за размером `analytics` и диском. ## Обновление и откат ```bash # обновление: поменять IMAGE_TAG в env/<контур>.env, затем docker compose -p <контур> --env-file env/<контур>.env -f docker-compose.prod.yml pull docker compose -p <контур> --env-file env/<контур>.env -f docker-compose.prod.yml up -d ``` Откат — вернуть предыдущий `IMAGE_TAG` (например, commit-sha) и повторить. ## Контуры | Контур | env-файл | БД | APP_ENV | Бот | | --- | --- | --- | --- | --- | | test | `env/test.env` | `dispel_evil_test` | `dev` | тестовый | | prod | `env/prod.env` | `dispel_evil` | `prod` | боевой | ## Переменные окружения См. `env/prod.env.example` / `env/test.env.example` (контуры). ## Дашборд (позже) Superset поднимается отдельно и читает Postgres (схема `analytics`) с полной историей событий. Отдельный read-only пользователь Postgres и сам дашборд — финальный этап, в этот контур не входят. ## Перенос данных на прод (позже, короткое окно простоя) 1. Остановить старого бота. 2. `pg_dump -Fc -f dump.pgc <старая_бд>`. 3. Поднять прод-контур (миграции создадут чистую схему). 4. Загрузить данные: `pg_restore --data-only --disable-triggers -d dispel_evil dump.pgc`. 5. Сверить: число пользователей, `SUM(money.amount)`, число сообщений. 6. Переключить бота, проверить команды. Бэкап старой БД обязателен. Дубли в `tg_users`/`de_users`/`unsubscribe` при загрузке нужно устранить до создания PK (миграция `V12` делает дедуп, но при загрузке поверх миграций дедуп — вручную).