# module-10. Runbook развёртывания HAN Chat > Статус: последовательная инструкция первого production-like деплоя и эксплуатации на одной Ubuntu VM. > Все значения в `<УГЛОВЫХ_СКОБКАХ>` — placeholders. Команды с `cd ` требуют подстановки реального пути корня backend-репозитория на VM. > Источники: [`README.md`](README.md), [`arch-00-glossary.md`](arch-00-glossary.md), [`arch-01-system-architecture.md`](arch-01-system-architecture.md), [`arch-02-api-contracts.md`](arch-02-api-contracts.md), [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md), [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md), [`module-01-api-backend.md`](module-01-api-backend.md)–[`module-09-observability.md`](module-09-observability.md). ## 1. Неподвижные правила 1. Один root `docker compose` запускается из ``. 2. Ровно один edge nginx публикует `80/443`. 3. API, Keycloak, Redis, OTEL, Safety и Bitrix-сервисы не имеют host `ports`. 4. `/internal/*` не маршрутизируется публично. 5. Managed PostgreSQL находится вне Compose, в той же VPC, без public IP. 6. S3 — внешний Selectel-compatible storage; клиент получает только presigned URL. 7. Секреты не коммитятся, не вставляются в команды shell history и не выводятся в отчёты. 8. Миграции выполняются отдельными one-shot steps до новой версии приложения. 9. Message Safety запускается как documented stub до замены; это не production antivirus/moderation. 10. `bitrix-sync` запускается как DB-connectivity stub; полноценной CRM sync нет. ## 2. Роли и обозначения - **Cloud admin**: VPC, VM, PG, S3, DNS/security groups. - **Deploy operator**: VM, Compose, migrations, release/rollback. - **Bitrix admin**: local app, connector, Open Line 8, callbacks. - **Security owner**: secrets, Keycloak admin MFA, firewall, retention. Placeholders: ```text например chat.example.ru публичный IPv4 VM приватный IPv4 VM например 10.20.0.0/24 private FQDN/IP managed PG 5432 или 6432 han_chat URL репозитория /opt/han-chat/backend immutable tag/git SHA адрес ops, не placeholder в реальном запуске разрешённый портал ``` ## 3. Stage 0 — решения до provisioning ### 3.1. Зафиксировать параметры - region/availability zone и VPC; - hostnames и TTL DNS; - VM image Ubuntu 24.04 LTS; - sizing; - PG plan/storage/backups/PITR; - S3 region/endpoint/bucket names; - container registry и immutable image tags/digests; - remote observability backend; - RPO/RTO и maintenance window; - ответственных за alerts/Bitrix/Keycloak. Начальный sizing без local Grafana stack: - VM: 4 vCPU, 8 ГБ RAM, 80 ГБ SSD, 4 ГБ swap; - managed PG: минимум 2 vCPU, 4 ГБ RAM, 50 ГБ, HA по возможности; - Redis limit: 512 МиБ; - OTEL Collector: 512 МиБ + 5–10 ГБ queue; - свободный диск VM после pull/build: не менее 30%. Это baseline, не гарантия. До real traffic обязателен load test с long Safety poll и WS. ### Gate 0 - [ ] Владельцы и maintenance window назначены. - [ ] RPO/RTO приняты хотя бы временно: ориентир RPO PG ≤15 минут/PITR, RTO ≤4 часа. - [ ] Решено: images pull из registry или build на VM. - [ ] Remote telemetry backend выбран либо явно принят ограниченный debug-only режим. - [ ] Риск mock OTP и Safety stub письменно принят. **Ожидаемый результат:** есть release checklist с конкретными values; не создано ни одной публичной БД/Redis. ## 4. Stage 1 — VPC, VM, DNS и security groups ### 4.1. Сеть Создать одну private subnet для VM и managed PG. PG получает только private address. VM имеет public IP только для nginx/SSH. Security groups: | Source | Destination | Port | Rule | |---|---|---:|---| | trusted ops CIDR/VPN | VM | SSH `` | allow | | internet | VM | TCP 80 | allow для redirect/ACME | | internet | VM | TCP 443 | allow | | VM private IP/SG | managed PG | `` | allow | | VM | internet | 443 | allow egress: registry, Bitrix, S3, OTLP, ACME | | internet | managed PG | any | deny | | internet | VM | 6379, 4317, 4318, 8000, 8080, 9000 | deny | Если cloud SG не поддерживает egress allow-list, оставить egress open и контролировать destinations приложением/TLS; не ломать S3/Bitrix/OIDC. ### 4.2. DNS Создать `A `. Не добавлять `www`, если он не нужен и не включён в certificate. Для отдельного API host действуют правила arch-03; MVP предпочтительно использует один host с paths. Проверка с рабочей станции: ```bash dig +short ``` Ответ должен совпасть с ``. ### Gate 1 - [ ] PG не имеет public endpoint. - [ ] SSH доступен только trusted source. - [ ] Снаружи открыты только 80/443/ограниченный SSH. - [ ] DNS стабильно разрешается с нескольких resolver. - [ ] VM достигает private PG и внешних HTTPS endpoints. **Ожидаемый результат:** `nc -vz ` с VM успешен; с внешней машины PG недоступен. ## 5. Stage 2 — hardening Ubuntu и deploy user ### 5.1. Первичный вход Войти cloud user, добавить отдельный deploy key. Не отключать пароль/root до проверки второго SSH-сеанса. Прототипный скрипт можно адаптировать: ```bash sudo DEPLOY_USER=deploy \ DEPLOY_DIR=/opt/han-chat \ SSH_PORT= \ SWAP_SIZE_GB=4 \ PUBLIC_DOCKER_PORTS=80,443 \ ./deploy/setup-vm-han-chat.sh ``` Скрипт из [`../../HAN_chat/deploy/setup-vm-han-chat.sh`](../../HAN_chat/deploy/setup-vm-han-chat.sh) полезен для UFW, fail2ban, Docker и `DOCKER-USER`, но перед production: - проверить его версию/review; - не передавать реальные IP/ключи в git; - проверить auto reboot unattended upgrades относительно maintenance; - решить, действительно ли deploy user нужен в группе `docker` (это root-equivalent); - не применять `AllowTcpForwarding no`, если утверждённый break-glass DB tunnel необходим; предпочтителен VPN/bastion. ### 5.2. Проверки ```bash sudo sshd -t sudo ufw status verbose sudo fail2ban-client status sshd docker version docker compose version sudo iptables -L HAN-CHAT-DOCKER -n -v timedatectl status df -h free -h ``` Открыть второй SSH session как `deploy`, затем отключить root/password login. `.env` позже имеет mode `0600`. ### Gate 2 - [ ] SSH key login `deploy` проверен во втором сеансе. - [ ] Root/password auth выключены. - [ ] UFW и DOCKER-USER активны после restart Docker. - [ ] Docker Engine/Compose plugin закреплены поддерживаемой версией. - [ ] NTP active; disk/swap соответствуют sizing. - [ ] Break-glass процедура сохранена вне VM. **Ожидаемый результат:** reboot VM не теряет SSH, firewall и Docker service. ## 6. Stage 3 — managed PostgreSQL ### 6.1. Защита и восстановление managed PostgreSQL До создания схем включить и проверить настройки managed PostgreSQL: - **Daily backup** — автоматический полный снимок БД не реже одного раза в сутки. Нужно задать срок хранения, например 7–14 дней, и убедиться, что резервные копии размещаются отдельно от вычислительного узла БД. Наличие backup без периодической проверки восстановления не считается достаточным. - **PITR (Point-in-Time Recovery)** — восстановление состояния БД на выбранный момент времени между полными backup за счёт архивации WAL. Это позволяет откатиться, например, к состоянию непосредственно перед ошибочной миграцией или удалением данных. Период доступного восстановления должен соответствовать принятому RPO. - **Encryption at rest** — шифрование дисков, backup и WAL на стороне провайдера managed PostgreSQL. Ключ управляется провайдером либо KMS организации; пароль пользователя БД не заменяет это шифрование. - **TLS для соединения с PostgreSQL** — шифрование трафика между контейнерами на VM и managed PostgreSQL с обязательной проверкой имени сервера. CA-сертификат обычно выдаёт провайдер PostgreSQL. Это **не** публичный сертификат сайта и не сертификат Let's Encrypt. - **Alerts** — уведомления как минимум о нехватке диска, исчерпании подключений, высокой загрузке CPU/IO, replication lag (если есть реплики), неуспешном backup и истечении/замене CA. - **Deletion protection** — запрет удаления кластера обычной командой/API. Отключение защиты должно быть отдельным подтверждаемым действием администратора. Эта настройка не защищает от `DROP TABLE`, поэтому least privilege и backup всё равно обязательны. CA managed PostgreSQL скачать из панели или документации провайдера в защищённый путь VM, например `/opt/han-chat/secrets/pg/ca.pem`, с владельцем `root:deploy` и mode `0440` (либо `0400`, если файл читает один пользователь). Все DSN используют `sslmode=verify-full` и `sslrootcert=/run/secrets/pg-ca.pem` либо эквивалент драйвера. Режимы `disable`, `allow`, `prefer` и `require` без проверки CA для production запрещены. Публичный HTTPS-сертификат `` выпускается отдельно через **Let's Encrypt** на Stage 9. Он устанавливается в корневой nginx, а не в PostgreSQL. ### 6.2. Роли Целевая модель разделяет: - admin/bootstrap role; - migration role каждого schema с DDL; - runtime role без DDL. Прототип `init-managed-postgres.py` выдаёт runtime roles `CREATE` на schema и печатает DSN. Это допустимо только для bootstrap/dev, но **слишком широко для production runtime**. Перед production адаптировать: 1. создать пять schemas: `han_app`, `bitrix_local`, `bitrix_sync`, `keycloak`, `message_safety`; 2. создать runtime roles; 3. создать migration roles либо controlled admin job; 4. schema owner = migration role; 5. runtime: `USAGE`, DML и sequence grants только на свои objects; 6. `ALTER DEFAULT PRIVILEGES` от migration owner; 7. запретить чужие schemas и public schema create; 8. `bitrix_sync_user` не получает `han_app` grants, пока module-07 остаётся stub. Команда bootstrap требует ``: ```bash cd cp deploy/pg-init.env.example deploy/pg-init.env chmod 600 deploy/pg-init.env # заполнить private host/database/admin и generated passwords set -a; source deploy/pg-init.env; set +a python3 deploy/init-managed-postgres.py unset HAN_PG_ADMIN_PASSWORD ``` Не сохранять stdout с DSN в shared logs. Исторический `init-managed-postgres.sql` содержит placeholder passwords и database `postgres`; для целевой БД применять только после review и замены database name. ### 6.3. Проверка least privilege Для каждого runtime user: ```bash psql "host= port= dbname= user= sslmode=verify-full sslrootcert=" \ -c "select current_user, current_setting('search_path');" ``` Негативно проверить `CREATE TABLE` и доступ к чужой schema — они должны завершиться permission denied. ### 6.4. Migration policy Порядок ownership: 1. `api-backend` Alembic владеет `han_app`, triggers, seed; 2. `bitrix-local-app` Alembic владеет `bitrix_local`; 3. `message-safety` stub не создаёт PG tables до production implementation; 4. `bitrix-sync` stub — optional empty baseline; 5. Keycloak мигрирует standard tables сам; custom provider имеет собственные versioned migrations. Только expand/migrate/contract. Destructive migration — отдельный backup, approval и release. Downgrade data migrations не обещается; rollback приложения требует backward-compatible schema. ### Gate 3 - [ ] Backups/PITR/TLS/deletion protection включены. - [ ] Пять schemas/roles созданы. - [ ] Runtime roles не имеют DDL/чужого доступа. - [ ] Migration credentials отделены от runtime. - [ ] Empty/previous-version migration test успешен. - [ ] PITR restore point создан перед первым release. **Ожидаемый результат:** runtime `SELECT 1` успешен, unauthorized schema read/create запрещены. ## 7. Stage 4 — S3 buckets, IAM, CORS и lifecycle Создать три приватных bucket: - `-quarantine`; - `-attachments`; - `-documents`. Public ACL/listing выключены. Versioning включить для data buckets по policy; server-side encryption включить. IAM: - API role/key: exact prefixes, presign PUT quarantine, Head/copy/delete quarantine, write/read data; - Safety role/key: **read-only quarantine**; - backup/ops role: отдельно; - frontend: никаких permanent credentials. CORS quarantine: ```json [ { "AllowedOrigins": ["https://"], "AllowedMethods": ["PUT"], "AllowedHeaders": ["Content-Type", "x-amz-*"], "ExposeHeaders": ["ETag", "x-amz-checksum-sha256"], "MaxAgeSeconds": 600 } ] ``` Уточнить фактические required signed headers. Не разрешать `*` origin с credentials. Lifecycle: - quarantine: expire orphan objects только после периода, превышающего Safety poll + recovery; initial 2 дня, согласовать; - incomplete multipart upload: abort через 1 день; - attachments/documents: без auto-delete до legal retention; - noncurrent versions: policy после legal review. ### Gate 4 - [ ] Все buckets private. - [ ] API key не может list/write вне exact scope. - [ ] Safety key не может write/delete. - [ ] Browser test origin выполняет presigned PUT. - [ ] Quarantine lifecycle не удалит active `safety_tasks`. - [ ] Data lifecycle соответствует retention. **Ожидаемый результат:** anonymous GET/PUT получает deny; API capability test проходит. ## 8. Stage 5 — repository и release layout На VM: ```text /opt/han-chat/ backend/ # checkout текущего release releases// # optional immutable release dirs secrets/ # не в git backups/ # только metadata/short-lived encrypted artifacts ``` Рекомендуемый rollout — immutable images из registry. Build на VM допустим для MVP, но требует reproducible Dockerfiles и достаточно диска. ```bash sudo install -d -m 0755 -o deploy -g deploy /opt/han-chat git clone cd git fetch --tags git checkout --detach git status --short ``` Ожидается clean tree. Запретить deploy из mutable branch без recorded SHA. Проверить структуру: root `docker-compose.yml`, service directories, `nginx`, `keycloak`, `redis`, `observability`, frontend artifact. ### Gate 5 - [ ] Checkout exact SHA/tag. - [ ] Working tree clean. - [ ] Images/Dockerfiles pinned, `latest` отсутствует. - [ ] SBOM/vulnerability scan без unresolved critical/high. - [ ] Root Compose — единственный production entrypoint. ## 9. Stage 6 — `.env` и secrets ### 9.1. Создание ```bash cd umask 077 cp .env.example .env chmod 600 .env ``` Генерировать минимум 256-bit: ```bash openssl rand -hex 32 ``` Не выполнять `export SECRET=...` в shared shell history. Использовать editor с restricted permissions или secret manager/secret files. ### 9.2. Обязательные группы - `APP_ENV`, release/version, log level; - private PG host/port/database, TLS CA и runtime DSN; - Redis ACL credentials/URLs DB0/1/2; - public web/API/auth URLs; - Keycloak realm/audience/hostname/bootstrap/provider technical secrets; - paired service tokens из arch-02; - Bitrix client/application/webhook/encryption secrets; - S3 endpoint/buckets/API and read-only Safety credentials; - OTEL endpoint/remote exporter secrets; - nginx/TLS/rate limits; - frontend public build values. Пары должны совпасть: ```text BITRIX_LOCAL_APP_INTERNAL_TOKEN == BITRIX_INTERNAL_API_TOKEN BITRIX_API_FORWARD_TOKEN == BITRIX_API_INBOX_TOKEN ``` Service token и webhook token — разные secrets. ### 9.3. Validation Добавить/запустить `scripts/validate-env`: - mandatory not empty; - нет `change-me`, example IP/domain, default OTP; - URLs have correct schemes; - public URLs HTTPS, internal URLs service DNS; - PG TLS enabled; - paired tokens equal; - CORS/origins exact; - no duplicate keys; - `FRONTEND_DEV_PROXY_ENABLED=false`; - Safety timeout согласован с nginx; - secrets minimum length; - mock OTP risk flag explicitly accepted. ```bash cd ./scripts/validate-env .env docker compose config --quiet ``` `docker compose config` может раскрыть resolved secrets; не сохранять/публиковать его stdout. ### Gate 6 - [ ] `.env` mode 0600, отсутствует в git. - [ ] Все placeholder/default secrets отклонены. - [ ] Paired tokens совпадают. - [ ] DSN private/TLS; URLs/issuer согласованы. - [ ] Validation и Compose interpolation успешны. - [ ] Secret recovery/rotation owner назначен. ## 10. Stage 7 — images и frontend static ### Pull-вариант ```bash cd docker login docker compose pull docker image ls --digests ``` Registry token read-only и короткоживущий. ### Build-вариант ```bash cd DOCKER_BUILDKIT=1 docker compose build --pull ``` Build не получает production secrets. Записать image digests. Frontend: ```bash cd npm ci npm run test npx expo export --platform web ``` Скопировать artifact в versioned `frontend-static` volume/image. `index.html` revalidate, hashed assets immutable. Build env содержит только public URL/realm/client id. Проверить отсутствие service tokens/mock OTP/S3 keys командой secret scanner. ### Gate 7 - [ ] Все images доступны по digest. - [ ] Frontend build/tests успешны. - [ ] Static artifact не содержит secrets/source maps по policy. - [ ] nginx image/config содержит request-id module и TLS features. - [ ] Disk после pull/build >30% free. ## 11. Stage 8 — root Compose, networks и volumes До запуска: ```bash cd docker compose config --services ``` Ожидаются: `nginx`, `api-backend`, `message-safety`, `keycloak`, `bitrix-sync`, `bitrix-local-app`, `redis`, `otel-collector` и one-shot jobs/profile components. Networks: - `public`: nginx и минимально Keycloak/frontend path; - `backend`: internal services/Redis; - `observability`: services + Collector. Volumes: - `redis-data`; - ACME certs/webroot; - frontend static; - `otel-queue`; - никаких PG data volumes. Проверить: ```bash docker compose config | rg 'ports:|expose:|networks:|volumes:' ``` Если `rg` на VM нет, использовать reviewed script, не ручной визуальный просмотр. Единственные published mappings — nginx 80/443. Redis: ACL, AOF everysec, RDB, maxmemory, volume, no host port. OTEL: config read-only, queue bounded, no public OTLP. ### Gate 8 - [ ] Только nginx публикует ports. - [ ] Internal services не подключены к public без причины. - [ ] Named volumes созданы и permissions проверены. - [ ] Container resource limits/healthchecks заданы. - [ ] `docker compose config --quiet` success. ## 12. Stage 9 — TLS bootstrap, фаза 1 Прототип `ssl-issue.sh` останавливает весь Compose и использует standalone Certbot. Для full stack предпочтителен **webroot two-phase**, чтобы не делать `compose down`. ### Phase A: HTTP bootstrap 1. DNS уже указывает на VM. 2. Запустить nginx с bootstrap config: только `/.well-known/acme-challenge/` и redirect; TLS block не требует отсутствующий cert. 3. Запустить Certbot profile: Сначала проверить процесс через Let's Encrypt staging CA, добавив `--staging`. Staging-сертификат не является доверенным браузерами и нужен только для проверки DNS, firewall, ACME webroot и конфигурации. После успешной проверки удалить staging lineage либо выпустить production-сертификат с отдельным `--cert-name `. Production-выпуск: ```bash cd docker compose --profile tls-bootstrap up -d nginx docker compose --profile certbot run --rm certbot certonly \ --webroot -w /var/www/certbot \ -d \ --cert-name \ --email \ --agree-tos --no-eff-email --non-interactive ``` Каталоги `/etc/letsencrypt` и `/var/www/certbot` должны быть общими named volumes для контейнеров `certbot` и `nginx`. Сертификат и закрытый ключ не копируются в репозиторий или image. TLS-конфигурация nginx использует: ```text /etc/letsencrypt/live//fullchain.pem /etc/letsencrypt/live//privkey.pem ``` ### Phase B: TLS activation ```bash cd docker compose exec -T nginx nginx -t # активировать rendered TLS config атомарно docker compose exec -T nginx nginx -s reload ``` HSTS пока не включать. Проверить chain/hostname/redirect, затем включить HSTS без preload. ### Автоматическое обновление Let's Encrypt Сертификаты Let's Encrypt действуют 90 дней. Проверку обновления выполнять **дважды в сутки**; Certbot сам обновляет сертификат только при приближении срока истечения. Для VM предпочтителен systemd timer, который вызывает репозиторный скрипт root Compose. Скрипт `/deploy/ssl-renew.sh` должен: 1. взять `flock`, чтобы исключить параллельные запуски; 2. выполнить `docker compose --profile certbot run --rm certbot renew --webroot -w /var/www/certbot --quiet`; 3. при успешном обновлении проверить `docker compose exec -T nginx nginx -t`; 4. только после успешной проверки выполнить `docker compose exec -T nginx nginx -s reload`; 5. записать структурированный результат и метрику времени до истечения; 6. вернуть ненулевой exit code при ошибке, чтобы сработал alert; 7. не удалять действующий сертификат при неуспешном renew. Пример unit `/etc/systemd/system/han-chat-cert-renew.service`: ```ini [Unit] Description=Renew HAN Chat Let's Encrypt certificate Requires=docker.service After=docker.service network-online.target [Service] Type=oneshot User=deploy WorkingDirectory= ExecStart=/deploy/ssl-renew.sh ``` Пример timer `/etc/systemd/system/han-chat-cert-renew.timer`: ```ini [Unit] Description=Check HAN Chat certificate renewal twice daily [Timer] OnCalendar=*-*-* 03,15:20:00 RandomizedDelaySec=30m Persistent=true Unit=han-chat-cert-renew.service [Install] WantedBy=timers.target ``` Установка и обязательная проверка расписания: ```bash sudo systemctl daemon-reload sudo systemctl enable --now han-chat-cert-renew.timer sudo systemctl list-timers han-chat-cert-renew.timer cd docker compose --profile certbot run --rm certbot renew --dry-run sudo systemctl start han-chat-cert-renew.service sudo systemctl status han-chat-cert-renew.service --no-pager ``` Дополнительно настроить alert при остатке менее 21 дня и критический alert менее 7 дней. Проверять срок можно synthetic probe снаружи и метрикой exporter/скрипта на VM. Ошибка одного запуска renew не должна останавливать nginx. Прототипные [`ssl-common.sh`](../../HAN_chat/bitrix-local-app/deploy/ssl-common.sh), [`ssl-renew.sh`](../../HAN_chat/bitrix-local-app/deploy/ssl-renew.sh) полезны концептуально, но должны работать с **root Compose**, не service compose. ### Gate 9 - [ ] Staging issuance rehearsal успешен. - [ ] Production cert chain/hostname valid. - [ ] HTTP только ACME + 308. - [ ] TLS 1.0/1.1 rejected; 1.2/1.3 accepted. - [ ] Renewal dry-run и safe reload успешны. - [ ] `han-chat-cert-renew.timer` включён, имеет следующий запуск и переживает reboot. - [ ] Alert expiry настроен. ## 13. Stage 10 — миграции и seed Остановить public traffic либо использовать maintenance page до gate. ### 13.1. Preflight ```bash cd docker compose run --rm api-backend alembic current docker compose run --rm bitrix-local-app alembic current ``` Создать PITR marker. Выполнить dry-run/SQL review в clone/staging. ### 13.2. Upgrade ```bash cd docker compose run --rm api-backend alembic upgrade head docker compose run --rm bitrix-local-app alembic upgrade head ``` Для message-safety stub PG migration отсутствует. Для bitrix-sync stub — baseline только если реализован. Keycloak стандартную schema мигрирует выбранная pinned версия при controlled startup; provider migration выполняется отдельным approved job. ### 13.3. Seed `app_settings` Seed обязан быть idempotent/versioned и содержать все ключи arch-04. Выполнить migration или: ```bash cd docker compose run --rm api-backend python -m app.cli.seed_settings --file /deploy/app-settings.production-like.yaml docker compose run --rm api-backend python -m app.cli.validate_settings ``` Команды являются целевым интерфейсом; если CLI ещё не реализован, gate не проходить ручными ad-hoc INSERT без reviewed SQL. Проверить public keys/DTO, consent versions/URLs, CORS host, file MIME/size, UX idle timeout. Не копировать phone/URLs из prototype без product approval. ### Gate 10 - [ ] PITR marker до migrations. - [ ] Expected Alembic revisions active. - [ ] Runtime users не выполняли DDL. - [ ] Seed idempotency проверена повторным запуском. - [ ] Mandatory settings valid; secrets отсутствуют в `app_settings`. - [ ] Backward compatibility с текущими images подтверждена. ## 14. Stage 11 — Keycloak bootstrap ### 14.1. Первый старт Запустить PostgreSQL-ready Keycloak отдельно: ```bash cd docker compose up -d keycloak docker compose ps keycloak docker compose logs --since=10m keycloak ``` Bootstrap admin secret существует только на первый запуск. После создания named admin с MFA удалить/ротировать bootstrap credential из runtime env. ### 14.2. Realm Clean environment может импортировать secret-free `han-chat` realm template. Живой production realm нельзя перетирать `--import-realm` без diff. Проверить: - client `han-chat-frontend`, public, PKCE S256; - direct/implicit/password/social disabled; - audience `han-chat-api`; - exact redirect/web origins; - issuer `https:///auth/realms/han-chat`; - claims `sub`, `phone_number`, verified, audience; - custom phone OTP provider; - settings bridge token/path; - mock code non-default и не виден UI/log; - brute-force, token/session TTL, refresh rotation; - admin console limited by VPN/allow-list. ### 14.3. Provider migration Custom OTP tables мигрируются versioned mechanism до включения flow. Не редактировать standard Keycloak tables вручную. ### Gate 11 - [ ] Discovery/JWKS public через HTTPS. - [ ] Issuer exact, no internal hostname. - [ ] Realm drift check clean. - [ ] Только Authorization Code + PKCE S256. - [ ] OTP wrong/replay/limit tests fail safely. - [ ] Settings bridge cache/fail-closed tested. - [ ] Bootstrap admin removed; named admin MFA enabled. ## 15. Stage 12 — ordered startup и health gates Архитектурный порядок: 1. Redis; 2. Keycloak; 3. OTEL Collector; 4. Message Safety; 5. API backend; 6. Bitrix local app; 7. Bitrix sync; 8. nginx. Команды: ```bash cd 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 bitrix-local-app bitrix-sync docker compose up -d nginx docker compose ps ``` После каждого шага ждать health, но проверять readiness отдельно из internal network: ```bash docker compose exec -T api-backend python -c "" ``` Не использовать host ports для curl. Допустим dedicated toolbox container в `backend` network. Expected: - Redis `PONG`; - Keycloak DB/realm/provider ready; - Collector health + exporter queue; - Safety ready и Redis DB2; - API DB/Redis/JWKS/settings/S3/Safety ready; - local app до Bitrix install может быть `portal_not_installed`; - bitrix-sync возвращает `mode=db_connectivity_stub`, не CRM-ready; - nginx config test success. ### Gate 12 - [ ] Все containers live, нет restart loop/OOM. - [ ] Critical readiness green. - [ ] Expected degraded statuses только Bitrix not-installed/sync stub. - [ ] `docker compose ps` не публикует internal ports. - [ ] Internal `/internal/*` снаружи 404. - [ ] OTEL принимает telemetry. ## 16. Stage 13 — Bitrix24 local app и Open Lines Bitrix admin создаёт local application: - install URL `https:///bitrix/install`; - handler URL `https:///bitrix/handler`; - placement URL `https:///bitrix/placement`; - required scopes по module-06; - client id/secret загружены в secret store до install; - portal/domain allow-list exact. Выполнить install в Bitrix24. Local app должен сохранить encrypted OAuth, затем: 1. `imconnector.register` connector `han_mobile_app`; 2. `imconnector.activate` line `8`; 3. `event.bind`; 4. status/reconciliation. Не использовать prototype `/bitrix-internal/internal/v1/*`: canonical path только internal Docker `/internal/openlines/v1/*`, наружу он отсутствует. Проверить status из toolbox/internal network с Bearer token, не печатая token: ```bash cd docker compose run --rm --no-deps \ ``` Создать test dialog/message через public API, не прямым legacy payload с телефоном. Проверить mapping и ответ оператора. ### Gate 13 - [ ] OAuth stored encrypted; token не в logs. - [ ] Connector configured/active on line 8. - [ ] Events bound exactly once. - [ ] Outbound text reaches Open Lines once. - [ ] Operator reply reaches API, затем delivery ack. - [ ] Duplicate callback не создаёт duplicate message. - [ ] Internal status с internet недоступен. ## 17. Stage 14 — public smoke и E2E ### 17.1. Edge ```bash curl -I http:/// curl -fsS https:///api/v1/public/app-config curl -fsS https:///api/v1/public/content curl -fsS https:///auth/realms/han-chat/.well-known/openid-configuration curl -i https:///internal/safety/v1/messages/check openssl s_client -connect :443 -servername ``` Expected: 308; public 200 strict DTO; discovery 200; internal 404; valid cert. ### 17.2. Auth/frontend - guest открывает content без write; - protected write без JWT → 401; - consent → mock OTP → PKCE tokens; - bootstrap не передаёт phone body; - session-start создаёт `ux_session_id`; - silent refresh работает без OTP; - logout очищает tokens; - wrong/replayed OTP не выдаёт tokens. ### 17.3. Message Safety правила stub Обязательные E2E: - text, начинающийся после normalization с `ф/Ф` → public `422 message_blocked`, Bitrix не вызван; - text с цифры → Safety `203`, API poll до `200` или test terminal `400`; клиент никогда не получает `203`; - прочий text → allow; - terminal stub `400` преобразуется в `422`, не в generic validation; - timeout → `503/504`, checkpoint/recovery, без duplicate; - один slow poll не блокирует другие requests. Статус `message-safety` должен быть явно `stub`; для real production он не заменяет antivirus/file scan. ### 17.4. Files - allow image/PDF ≤ configured size; - wrong extension+MIME/oversize/checksum reject; - direct presigned PUT quarantine; - allow promote attachments; - deny остаётся вне data и quarantine cleanup; - Safety read-only credential не может write; - download URL owner-only + audit; - presigned URL отсутствует в logs. ### 17.5. Realtime и ownership - WS connects/subscribes; - operator reply arrives; - reconnect + REST gap reconciliation; - polling fallback; - чужие dialog/message/attachment/document id → 404; - idempotency same body replay, changed body 409; - rate limits 429 + `Retry-After`. ### Gate 14 - [ ] Полный first-send flow успешен. - [ ] Safety allow/deny/pending/timeout проверены. - [ ] Text/file/WS/polling работают. - [ ] Ownership и no-public-internal tests зелёные. - [ ] Нет secret/PII/message body/presigned URL в logs. - [ ] Audit events созданы. - [ ] Bitrix получает только allowed message. ## 18. Stage 15 — observability validation Следовать [`module-09-observability.md`](module-09-observability.md): 1. послать request с известным `X-Request-ID`; 2. найти nginx log, API trace и downstream spans; 3. проверить `service.name`, environment, trace/request/UX ids; 4. при выбранном telemetry backend проверить dashboards всех services; до выбора — проверить bounded stdout/debug acceptance и явно зафиксировать ограничение; 5. trigger safe synthetic 4xx/5xx и проверить alert route, если backend с alerting уже выбран; 6. при настроенном remote OTLP временно блокировать его, проверить bounded queue и business continuity; 7. проверить Collector health/drop/refused; 8. выполнить PII/secret canary test. ### Gate 15 - [ ] Три сигнала доступны. - [ ] Trace cross-service связан. - [ ] Для выбранного backend alerts доставляются on-call и SLO queries возвращают данные; иначе limitation/TBD явно принят и traffic не называется production-ready. - [ ] Collector outage не ломает business path. - [ ] Redaction test пройден. ## 19. Stage 16 — opening traffic До открытия: - удалить maintenance response; - включить HSTS после финального TLS test; - сохранить release SHA/image digests/schema revisions/realm desired version; - создать restore point; - подтвердить on-call; - не удалять previous images; - observation window 60 минут. В первые 60 минут: 5xx, auth, message delivery, DB/Redis, memory, restart, Collector queue, Bitrix OAuth/DLQ. ### Gate 16 - [ ] Все Gate 0–15 подписаны. - [ ] Rollback release доступен. - [ ] Backup/restore evidence свежий. - [ ] Нет active page alert. - [ ] Product owner принял stub limitations. ## 20. Backup и restore ### PostgreSQL - provider daily + PITR; - перед migrations/Keycloak upgrade — manual restore point; - ежеквартальный restore clone; - проверить все schemas, Alembic/Keycloak revisions, keys, grants. ### S3 - versioning/lifecycle data buckets; - inventory/checksum при поддержке; - restore не делает objects public; - quarantine не является backup. ### Redis AOF/RDB ускоряют restart, но не business backup. При corruption поднять clean Redis; API восстанавливает durable state из PG. Никогда не считать Redis dump достаточным для messages/audit. ### Keycloak DB backup включает realm/users/signing keys/provider data. Secret-free realm export — config backup, не полный data backup. После restore проверить issuer/JWKS/PKCE/OTP/refresh. ### Restore rehearsal 1. isolated VPC/hostnames; 2. restore PG clone и S3 copies; 3. deploy same image digests; 4. не направлять production DNS/Bitrix callbacks; 5. run migrations only if required release; 6. smoke auth/chat without real operator impact; 7. record measured RPO/RTO; 8. destroy isolated secrets/resources controlled. ## 21. Rollback ### Application-only 1. объявить incident/maintenance; 2. сохранить diagnostics и current state; 3. остановить новые claims/send при возможности; 4. переключить image tags на previous digests; 5. не выполнять Alembic downgrade; 6. `docker compose up -d`; 7. health/smoke/idempotency; 8. проверить outbox/inbox/recovery. ### После backward-incompatible migration Обычный rollback запрещён. Выбор: - forward-fix; - restore PG PITR + coordinated S3/Bitrix reconciliation; - maintenance до решения. Нельзя rollback DB отдельно от Keycloak signing/session state без анализа. ### TLS/nginx rollback `nginx -t`; оставить старый config/workers при failure. Не удалять working cert. При ошибке renewal current cert остаётся, но alert. ### Gate rollback - [ ] Previous images available. - [ ] Schema совместима. - [ ] No duplicate outbound after restart. - [ ] Data reconciliation completed. - [ ] Incident timeline содержит release/request ids без PII. ## 22. Routine operations Ежедневно автоматически: - health/synthetics/SLO/alerts; - PG backup/PITR status; - TLS expiry/renew; - disk/inodes/OTEL queue; - Redis AOF/memory/evictions; - DLQ/backlog/quarantine age; - Keycloak signing/OAuth/settings cache. Еженедельно: - vulnerability/image updates review; - failed login/rate-limit trend; - Bitrix connector desired/observed; - S3 lifecycle/inventory; - restore/rollback artifacts availability. Ежемесячно: - patch OS/images in maintenance; - secret/access review; - capacity/cardinality/cost; - stale users/admins; - runbook sample drill. Команды: ```bash cd docker compose ps docker compose logs --since=15m docker stats --no-stream docker system df docker compose exec -T nginx nginx -t ``` Не использовать unbounded `logs`, `docker system prune -a`, Redis `KEYS/FLUSH*` или ad-hoc DB DELETE. ## 23. Incident commands Безопасный triage: ```bash cd date -Is docker compose ps docker stats --no-stream docker compose logs --since=10m --tail=500 df -h free -h sudo ss -lntp sudo iptables -L HAN-CHAT-DOCKER -n -v ``` PG: ```bash psql "" -c "select now(), count(*) from pg_stat_activity;" ``` Redis — только ops ACL: ```bash docker compose exec -T redis redis-cli --user --pass '' PING ``` Никогда не вставлять secret literal в ticket/chat. Предпочесть stdin/secret file. Не выполнять ручной replay message/DLQ до проверки idempotency и ambiguous Bitrix outcome. Типовые сценарии: - API 503: DB/Redis/JWKS/settings/Safety readiness и circuits; - send timeout: safety checkpoint/outbox, не повторять с новым key; - Bitrix down: OAuth/circuit/backlog/DLQ, reads оставить; - Redis loss: clean restart, durable fallback, polling; - PG outage: не restart storm; provider incident; - disk full: остановить ingest growth, очистить только known cache/old image после inventory; - cert near expiry: webroot/DNS/rate limit, staging rehearsal; - secret leak: revoke/rotate, telemetry deletion, redeploy. ## 24. Upgrades Общий порядок: 1. release notes/security advisories; 2. compatibility matrix; 3. backup/PITR; 4. staging clone; 5. image/build/test/scan; 6. expand migration; 7. one service at a time по dependency order; 8. health/E2E/observation; 9. contract migration later; 10. record digests/revisions. Keycloak: не пропускать unsupported majors; проверить SPI/provider migration и JWKS. Redis: AOF compatibility/rewrite. Collector: config validate against exact version. nginx: `nginx -t` и TLS scan. PostgreSQL major upgrade сначала rehearsal clone. ## 25. Disaster recovery ### Потеря VM 1. provision new Ubuntu VM в VPC; 2. применить reviewed hardening; 3. attach public IP/update DNS с low TTL; 4. restore secrets из vault, не со старого disk без проверки; 5. pull exact images; 6. mount/create volumes; Redis можно clean; 7. connect existing/restored PG/S3; 8. TLS issue/restore safely; 9. ordered startup/gates; 10. Bitrix callback/connectors verify; 11. public smoke, then traffic. ### Потеря PG Restore PITR в new managed instance, private SG/TLS, update DSN, validate schemas/grants/revisions. Остановить writes до chosen restore point/reconciliation. S3 objects после restore point могут стать orphan; выполнить audit-backed reconcile. ### Потеря S3 Без data backup/versioning полное восстановление невозможно. Временно отключить file operations, оставить text flow, restore objects/inventory, reconcile DB metadata, не генерировать URLs отсутствующих objects. ### Compromise Isolate VM, preserve forensic snapshot, rotate all service/DB/S3/Bitrix/Keycloak secrets, revoke sessions/signing keys по масштабу, deploy clean VM/images, restore trusted data, notify по incident/legal process. ## 26. Teardown cautions `docker compose down` не удаляет managed PG/S3, но может остановить callbacks. `down -v` удалит Redis/ACME/OTEL queue volumes и запрещён без approval. Запрещены: ```text docker compose down -v docker system prune -a --volumes DROP DATABASE / DROP SCHEMA S3 recursive delete cloud project/VPC delete certbot delete active cert ``` Перед teardown: - export inventory/digests/config without secrets; - revoke Bitrix app/callbacks; - revoke/rotate credentials; - backup/retention/legal hold; - DNS drain; - deletion protection removal — отдельное approval; - проверить shared VPC/PG/S3; - зафиксировать evidence уничтожения. ## 27. Definition of Done - VM/VPC/DNS/SG/hardening соответствуют Gate 1–2; - managed PG private/TLS/backups/least privilege/migrations работают; - S3 private/IAM/CORS/lifecycle проверены; - exact release/images/frontend deployed; - `.env` validated, secrets protected; - один root Compose, один nginx, только 80/443; - Redis/Collector volumes/resources/security работают; - Keycloak realm/provider/PKCE/OTP готов; - ordered startup/readiness пройден; - Bitrix connector line 8 и callback flow проверены; - full auth/text/file/realtime/safety E2E зелёный; - observability/redaction проверены; SLO/alerts проверены для выбранного backend либо явно остаются принятым production-blocking TBD; - backup restore и rollback rehearsed; - ops/incident/upgrade/DR owners назначены; - все assumptions/TBD приняты до открытия traffic. ## 28. Допущения, TBD и архитектурные конфликты ### Допущения - D-A1: одна VM и один public host на MVP. - D-A2: обязательный минимум — `otel-collector`; доступность remote backend не предполагается до закрытия D-TBD11, local Grafana stack не обязателен. - D-A3: managed provider даёт private network, TLS, backups/PITR. - D-A4: Bitrix portal/connector/line остаются разрешёнными значениями architecture. - D-A5: mock OTP временно разрешён как documented risk. ### TBD до production - D-TBD1: реальные domains, Expo native redirect URI и Bitrix placement frame ancestors. - D-TBD2: final VM/PG sizing, RPS/WS, SLO/RPO/RTO. - D-TBD3: legal retention/erasure для PG/S3/audit/telemetry. - D-TBD4: secret manager и rotation windows. - D-TBD5: production Safety вместо stub и antivirus inbound operator files. - D-TBD6: полноценный bitrix-sync/GRANT или явное исключение CRM sync из release. - D-TBD7: pinned Keycloak/nginx/Collector versions и SPI compatibility. - D-TBD8: exact migration/seed/toolbox CLI commands после реализации repo. - D-TBD9: Keycloak admin VPN/MFA topology. - D-TBD10: final CSP/CORS/S3 headers и cloud-specific IAM. - D-TBD11: выбрать observability backend/provider, endpoint/auth/retention и alert route либо явно ограничить среду acceptance-режимом без production-ready SLO. ### Обнаруженные конфликты 1. `arch-01/02/03` описывают полноценный `bitrix-sync`, но module-07 реализует только `SELECT 1`. Первый release не поддерживает обещанную CRM profile sync. 2. `arch-01/02` ожидают production-like Safety и S3 scan, но module-05 — Redis-only random stub, file-only default allow и test-only terminal `400`. Это блокер настоящего production, даже если допустимо для production-like acceptance. 3. Прототипный PG init даёт runtime role `CREATE` schema и не разделяет migration/runtime roles; runbook требует ужесточения. 4. Прототипные TLS scripts используют отдельный service Compose/standalone downtime, тогда как целевая архитектура требует root Compose и two-phase webroot. 5. Prototype публиковал `/bitrix-internal/*` и использовал `/internal/v1/*`; целевой контур это запрещает и использует `/internal/openlines/v1/*`. 6. `arch-04` не содержит ряд proposed env из module-04–09; production `.env.example` должен быть синхронизирован до реализации. 7. Точные RPO/RTO, retention, SLO, Keycloak version/TTL и Bitrix retry semantics не утверждены; начальные значения runbook не закрывают product/security decision. 8. `init-managed-postgres.py` по умолчанию не задаёт TLS parameters при bootstrap connection и печатает credential-bearing DSN; его production-hardening обязателен. ## 29. Ссылки на прототип - Исторические шаги: [`../../HAN_chat/Deploy_steps.md`](../../HAN_chat/Deploy_steps.md). - Исторические команды эксплуатации: [`../../HAN_chat/backend-managing.md`](../../HAN_chat/backend-managing.md). - VM baseline: [`../../HAN_chat/deploy/setup-vm-han-chat.sh`](../../HAN_chat/deploy/setup-vm-han-chat.sh). - PG bootstrap: [`../../HAN_chat/deploy/init-managed-postgres.py`](../../HAN_chat/deploy/init-managed-postgres.py), [`../../HAN_chat/deploy/init-managed-postgres.sql`](../../HAN_chat/deploy/init-managed-postgres.sql), [`../../HAN_chat/deploy/pg-init.env.example`](../../HAN_chat/deploy/pg-init.env.example). - Legacy nginx: [`../../HAN_chat/bitrix-local-app/deploy/nginx-tohin.ru.site.conf`](../../HAN_chat/bitrix-local-app/deploy/nginx-tohin.ru.site.conf), [`../../HAN_chat/bitrix-local-app/deploy/nginx/nginx.conf`](../../HAN_chat/bitrix-local-app/deploy/nginx/nginx.conf). - Legacy TLS: [`../../HAN_chat/bitrix-local-app/deploy/ssl-issue.sh`](../../HAN_chat/bitrix-local-app/deploy/ssl-issue.sh), [`../../HAN_chat/bitrix-local-app/deploy/ssl-renew.sh`](../../HAN_chat/bitrix-local-app/deploy/ssl-renew.sh), [`../../HAN_chat/bitrix-local-app/deploy/ssl-install-cron.sh`](../../HAN_chat/bitrix-local-app/deploy/ssl-install-cron.sh).