# Инструкция по развертыванию HAN Chat в production-like окружении Это исполняемый чек-лист для контура на одной виртуальной машине. PostgreSQL и S3 используются как внешние управляемые сервисы. Во время развертывания запрещено использовать `docker compose down -v`, откат миграций Alembic и изменяемые теги образов. Подробная пошаговая инструкция для первого запуска находится в `deployment/DEPLOYMENT_GUIDE.ru.md`. ## Этап 0 — решения и зоны ответственности - [ ] Зафиксированы SHA/дайджесты релиза, окно обслуживания, дежурный и ответственный за откат. - [ ] Согласованы RPO/RTO; начальные цели: RPO PostgreSQL не более 15 минут и RTO не более 4 часов. - [ ] Выбран удаленный OTLP-бэкенд либо принято ограничение на использование только отладочного контура. - [ ] Явно приняты риски mock OTP, заглушки Safety и заглушки bitrix-sync. ## Этап 1 — VPC, DNS и группы безопасности - [ ] Управляемый PostgreSQL имеет только приватную точку доступа и принимает трафик от группы безопасности VM. - [ ] Из интернета доступны только TCP-порты VM 80/443; SSH ограничен VPN или CIDR администраторов. - [ ] Порты 6379, 4317/4318, 8000, 8080 и 9000 закрыты для внешнего доступа. - [ ] DNS-запись `A` для `PUBLIC_HOST` указывает на VM; исходящий HTTPS доступен. ## Этап 2 — защита виртуальной машины На новой Ubuntu 24.04 можно выполнить подготовительный скрипт: ```sh sudo deployment/scripts/setup-vm.sh ``` Перед включением `HARDEN_SSH=true` обязательно проверьте вход по ключу в отдельной SSH-сессии. Параметры запуска и безопасные значения по умолчанию описаны в начале скрипта. - [ ] Установлена Ubuntu 24.04; работают NTP, автоматические обновления безопасности и оповещения о заполнении диска. - [ ] Вход учетной записью развертывания по ключу проверен во второй сессии; вход root и SSH по паролю отключены. - [ ] Правила UFW/облачной группы безопасности и политика `DOCKER-USER` сохраняются после перезагрузки. - [ ] Docker Engine и Compose поддерживают `include` и полную форму `env_file`. ## Этап 3 — управляемый PostgreSQL - [ ] Включены ежедневные резервные копии, PITR, защита от удаления, шифрование и оповещения. - [ ] CA-сертификат провайдера установлен по пути `PG_CA_HOST_PATH`; все DSN используют `verify-full`. - [ ] Для схем `han_app`, `bitrix_local`, `bitrix_sync`, `message_safety`, `keycloak` созданы отдельные роли миграций и выполнения; запрет лишних прав проверен тестами. - [ ] Миграции проверены на пустой БД и на клоне БД предыдущего релиза. ## Этап 4 — Selectel S3 - [ ] Бакеты карантина, вложений и документов закрыты от публичного доступа и зашифрованы. - [ ] Права API ограничены префиксами; учетные данные Safety имеют доступ к карантину только на чтение. - [ ] CORS бакетов разрешает только точный HTTPS-origin браузера и необходимые заголовки PUT. - [ ] Срок хранения карантина превышает время Safety polling/recovery; политика хранения данных согласована. ## Этап 5 — неизменяемый релиз - [ ] Репозиторий переключен на утвержденный SHA в detached-режиме; рабочее дерево чистое. - [ ] Образы сервисов неизменяемы и просканированы; нерешенных критических и высоких уязвимостей нет. - [ ] Корневой `docker-compose.yml` является единственной точкой запуска. ## Этап 6 — окружение и секреты ```sh umask 077 cp .env.example .env chmod 600 .env # Замените заглушки через защищенный редактор или менеджер секретов. ./scripts/validate-env .env docker compose --env-file .env config --quiet ``` - [ ] Парные токены совпадают, PostgreSQL проверяет TLS, публичные URL используют HTTPS. - [ ] Риск mock OTP принят; все секреты уникальны и содержат не менее 128 бит энтропии. - [ ] `NOTIFICATIONS_TOKEN_PRODUCER_TEST` сгенерирован отдельно, передан только через secret/env; seed `notification_sources.code='producer_test'` содержит только его hash. - [ ] Установлено `FRONTEND_DEV_PROXY_ENABLED=false`; таймауты Safety и nginx согласованы. ## Этап 7 — образы и статический frontend ```sh docker compose --env-file .env pull docker compose --env-file .env build --pull frontend-static nginx redis docker compose --env-file .env run --rm frontend-static ``` - [ ] Экспорт frontend проверен и просканирован, затем скопирован сервисом `frontend-static` в именованный volume. - [ ] Артефакты сборки не содержат секретов и непредусмотренных source map. - [ ] На диске VM остается не менее 30% свободного места. ## Этап 8 — топология ```sh docker compose --env-file .env config --services python3 -m unittest discover -s tests -v ``` - [ ] Только nginx публикует `80:80` и `443:443`; сервиса PostgreSQL в Compose нет. - [ ] Присутствуют volumes Redis AOF/RDB/ACL и постоянной очереди OTEL. - [ ] Сети `backend` и `observability` являются внутренними. ## Этап 9 — первоначальная настройка ACME/TLS Установите `NGINX_TLS_ENABLED=false` только для команды первоначального запуска: ```sh NGINX_TLS_ENABLED=false docker compose --env-file .env up -d nginx docker compose --profile certbot run --rm certbot certonly \ --webroot -w /var/www/certbot -d "$PUBLIC_HOST" \ --cert-name "$PUBLIC_HOST" --email "$ACME_EMAIL" \ --agree-tos --no-eff-email --non-interactive docker compose --env-file .env up -d --force-recreate nginx docker compose exec -T nginx nginx -t -c /tmp/nginx.conf ``` Сначала выполните проверку с параметром Certbot `--staging`. Установите systemd-таймер, запускающий `deployment/scripts/ssl-renew.sh` дважды в сутки, и проверьте `certbot renew --dry-run`. Включайте HSTS только после проверки цепочки сертификатов, имени хоста, перенаправления и поддержки TLS 1.2/1.3. ## Этап 10 — миграции и начальные данные Создайте у провайдера точку восстановления PITR, затем выполните: ```sh PITR_MARKER_CONFIRMED=true deployment/scripts/migrate.sh deployment/scripts/seed.sh ``` - [ ] Активны ожидаемые ревизии Alembic; runtime-пользователи не выполняли DDL. - [ ] Повторный seed завершается успешно; обязательные настройки не содержат секретов. - [ ] Схема остается обратно совместимой с образами предыдущего релиза. ## Этап 11 — Keycloak ```sh docker compose up -d keycloak docker compose ps keycloak ``` - [ ] Issuer discovery/JWKS точно совпадает с публичным HTTPS URL `/auth`. - [ ] Frontend-клиент является публичным PKCE S256; implicit, password и social flows отключены. - [ ] Неверный или повторно использованный OTP и превышение лимитов безопасно отклоняются; settings bridge работает fail-closed. - [ ] При `KEYCLOAK_YANDEX_CAPTCHA_ENABLED=true` initial send и resend требуют свежий SmartCaptcha token; техническая недоступность Yandex подтверждена как fail-open в логах. - [ ] CSP login-страницы содержит `smartcaptcha.cloud.yandex.ru`/`yastatic.net`, а `/auth/realms/master/protocol/openid-connect/3p-cookies/step2.html` и Admin Console работают без CAPTCHA CSP. - [ ] Временный администратор удален либо его пароль изменен; для именного администратора включена MFA. Если предыдущая попытка сохранила custom CSP в realm, сбросьте только это поле через `kcadm`; `.env` как shell-файл не загружать: ```sh docker compose exec -T keycloak sh -lc ' set -eu cfg=/tmp/han-kcadm.config /opt/keycloak/bin/kcadm.sh config credentials --config "$cfg" \ --server http://127.0.0.1:8080/auth --realm master \ --user "$KC_BOOTSTRAP_ADMIN_USERNAME" \ --password "$KC_BOOTSTRAP_ADMIN_PASSWORD" /opt/keycloak/bin/kcadm.sh update realms/han-chat --config "$cfg" \ -s "browserSecurityHeaders.contentSecurityPolicy=" rm -f "$cfg" ' ``` ## Этап 12 — последовательный запуск и готовность ```sh docker compose up -d redis docker compose up -d keycloak otel-collector docker compose up -d message-safety docker compose up -d api-backend docker compose up -d delivery-worker safety-recovery-worker cleanup-worker \ notification-expire-worker notification-draft-cleanup-worker docker compose up -d bitrix-local-app bitrix-sync docker compose up -d nginx docker compose up -d --wait api-backend keycloak sms-service bitrix-local-app docker compose exec -T nginx nginx -t -c /tmp/nginx.conf docker compose kill -s HUP nginx docker compose ps ``` Nginx разрешает Docker DNS имена upstream при загрузке конфигурации. После любого пересоздания `api-backend`, `keycloak`, `sms-service` или `bitrix-local-app` дождитесь их readiness, проверьте именно рабочий `/tmp/nginx.conf` и отправьте master-процессу `HUP`, как показано выше. Обычные `nginx -t` и `nginx -s reload` использовать нельзя: они обращаются к дефолтному config/PID в read-only `/var/run` и не перезагружают рабочий Nginx. - [ ] Нет циклических перезапусков и OOM; критические readiness-проверки успешны. - [ ] `notification-expire-worker` выполняет ежедневное закрытие с advisory lock; `notification-draft-cleanup-worker` очищает просроченные drafts/S3. Оба entrypoint присутствуют в установленном образе. - [ ] Сохраняется только документированная деградация: Bitrix не установлен и bitrix-sync работает как заглушка. - [ ] Внешний запрос `/internal/*` возвращает 404; OTEL принимает телеметрию. ## Этап 13 — Bitrix24 - [ ] URL установки, обработчика и placement используют точные публичные HTTPS-пути. - [ ] Коннектор `han_mobile_app` активен в Открытой линии 8; события привязаны однократно. - [ ] OAuth зашифрован; callback-, application- и service-токены не попадают в логи. - [ ] Исходящие сообщения и ответы оператора идемпотентны; внутренний статус не опубликован наружу. ## Этап 14 — smoke- и E2E-тесты ```sh deployment/scripts/smoke.sh ``` - [ ] Успешны сценарии гостя, OTP/PKCE/bootstrap/session, обновления токена и выхода. - [ ] Проверены Safety allow/deny/pending/timeout и один параллельный медленный poll. - [ ] Проверены карантин, перенос и удаление файлов, скачивание только владельцем и аудит. - [ ] Проверены переподключение WS с REST-сверкой, 404 при обращении к чужому ресурсу, идемпотентность и 429. - [ ] От имени `producer_test` выполнены Create и Cancel через закрытый `/internal/notifications/v1/*`; тот же Create вернул `200`, изменённый payload — `409`, внешний запрос — `404`. - [ ] Проверены expire job с advisory lock и первое скачивание любого связанного документа: уведомление скрывается один раз, а исходный `date_expired` не перезаписывается. - [ ] Логи не содержат PII, текстов сообщений, токенов и query-параметров presigned URL. ## Этап 15 — наблюдаемость - [ ] Известный request ID связывает трассировку nginx, API и downstream-сервисов; UX ID не используется как label. - [ ] Все три сигнала поступают в выбранный бэкенд; SLO-запросы и оповещения проверены. - [ ] При недоступности удаленного сервиса ограниченная постоянная очередь заполняется и опустошается без остановки бизнес-функций. - [ ] Тестовые секреты и PII отсутствуют; проверены метрики перезапуска, потерь, отказов и очереди Collector. Только для локальной приемки запустите отладочный Collector с удалением чувствительных данных: `docker compose --profile observability-local up -d otel-collector-local`. ## Этап 16 — открытие трафика - [ ] Этапы 0–15 подписаны; имеются свежие подтверждения backup/PITR и предыдущие образы. - [ ] HSTS включен; дайджесты релиза, версии схем и realm зафиксированы. - [ ] Активных инцидентов нет; дежурный и владелец продукта приняли ограничения заглушек. - [ ] В течение 60 минут контролируются 5xx, auth, доставка, БД, Redis, OOM, очередь OTEL и Bitrix. ## Резервное копирование и восстановление Основным механизмом являются backup/PITR провайдера. Дополнительный проверенный логический дамп: ```sh PG_BACKUP_DSN='postgresql://...?...sslmode=verify-full&sslrootcert=...' \ deployment/scripts/backup.sh /opt/han-chat/backups ``` Ежеквартально восстанавливайте PostgreSQL и S3 в изолированной VPC, развертывайте те же дайджесты образов, не направляйте туда production DNS и callbacks Bitrix, выполняйте smoke-тесты и фиксируйте фактические RPO/RTO. Redis можно восстановить пустым: его AOF/RDB не является резервной копией бизнес-данных. ## Откат Откатывайтесь только на образы, совместимые с текущей схемой: ```sh SCHEMA_BACKWARD_COMPATIBLE_CONFIRMED=true \ deployment/scripts/rollback.sh /secure/path/previous-release.env ENV_FILE=/secure/path/previous-release.env deployment/scripts/smoke.sh ``` ## Дополнение: rollout реальной SMS-авторизации Текущий runbook остаётся mock-only, пока артефакты module-11 не реализованы. Для SMS release обязательны: schema/role `sms`, migrations/seed active approved `auth_otp`, `sms-service`/worker, exact callback route, парные service tokens, Direct `TOKEN_1`, согласованные sender/template, отдельные callback credentials, подтверждённый callback source IP и статический egress IP worker. Порядок: App DB OTP seed → SMS schema/migrations/seed → test с mock Direct → production SMS deploy при `KEYCLOAK_OTP_MOCK_ENABLED=true` → Keycloak expand migration/SPI → provider smoke и callback/redaction evidence → real mode. Rollback: вернуть mock, не удалять journal/schema, остановить новые real orders и зафиксировать in-flight/`uncertain`; downgrade только при доказанной совместимости. Никогда не выполняйте downgrade Alembic. После обратно несовместимой миграции используйте исправление вперед либо согласованный PITR с восстановлением S3 и сверкой Bitrix во время технического обслуживания. Всегда проверяйте outbox, inbox и recovery, чтобы сообщение с неопределенным статусом не было отправлено повторно.