Проект разделен на два репозитория

This commit is contained in:
mi
2026-08-14 15:42:45 +03:00
parent e06a77ee1d
commit bbef7a30c9
521 changed files with 2597 additions and 2302 deletions
@@ -0,0 +1,213 @@
# module-10-vm1. Runbook развёртывания ВМ1 HAN Chat
> Статус: целевой runbook репозитория ВМ1.
> Общий контракт (VPC/SG, PG, S3, роли `deploy`, TLS процедура, порядок cutover) — [`arch-10-deployment.md`](../../architectory/arch-10-deployment.md).
> ВМ2 — [`module-10-deployment-vm2.md`](../../VM2_services/documentation/module-10-deployment-vm2.md). Не переносить команды ВМ2 и не шарить Compose/secrets.
## 1. Границы
ВМ1 владеет edge nginx `80/443`, `api-backend`, Keycloak, SMS, `bitrix-local-app`, Redis DB0/DB1, Collector. После Safety cutover local Safety/Redis DB2 отсутствуют; `MESSAGE_SAFETY_URL` — private HTTPS ВМ2.
`<BACKEND_ROOT>` / `<BACKEND_REPO_URL>` — репозиторий ВМ1. Host ACME — `<PUBLIC_HOST>`.
## 2. Sizing ВМ1
Final sizing — D-TBD2. Disk после pull/build ≥30% free. `PUBLIC_DOCKER_PORTS=80,443`.
## 3. Hardening
По arch-10 §5 / arch-06. Пример:
```bash
sudo DEPLOY_USER=deploy \
DEPLOY_DIR=/opt/han-chat \
SSH_PORT=<SSH_PORT> \
SWAP_SIZE_GB=4 \
PUBLIC_DOCKER_PORTS=80,443 \
./deploy/setup-vm-han-chat.sh
```
Gate 2 — arch-10. Break-glass вне VM.
## 4. Release layout и `.env` ВМ1
Checkout exact SHA в `/opt/han-chat/backend`. Структура: root Compose, `nginx`, `keycloak`, `redis`, `observability`, frontend artifact.
Обязательные группы секретов/config ВМ1:
- `APP_ENV`, release, log level;
- private PG host/port/database и TLS CA; runtime DSN в secret backend;
- Redis ACL URLs DB0/DB1 только в secret backend; после cutover DB2 нет;
- public web/API/auth URLs;
- Keycloak realm/audience/hostname/bootstrap/provider secrets;
- SMS DB URL, парные Keycloak↔SMS tokens, Direct `TOKEN_1`, callback credentials;
- paired service tokens arch-02;
- Bitrix local app client/application/encryption secrets (не CRM sync webhook ВМ2);
- S3 API credentials (не Safety read-only key ВМ2);
- OTEL exporter secrets;
- nginx/TLS/rate limits;
- frontend public build values;
- после cutover: `MESSAGE_SAFETY_URL=https://<private-vm2-name>:8443` и `MESSAGE_SAFETY_CA_HOST_PATH`.
Пары: `BITRIX_LOCAL_APP_INTERNAL_TOKEN == BITRIX_INTERNAL_API_TOKEN`, `BITRIX_API_FORWARD_TOKEN == BITRIX_API_INBOX_TOKEN`, `KEYCLOAK_SMS_SERVICE_TOKEN == SMS_SERVICE_TOKEN`.
`FRONTEND_DEV_PROXY_ENABLED=false`. Safety timeout согласован с nginx ВМ1. Validation — arch-10 §8; `validate-env` в этом репозитории.
## 5. Images и frontend
Pull или build без production secrets. Frontend:
```bash
cd <FRONTEND_PROJECT_PATH>
npm ci
npm run test
npx expo export --platform web
```
Artifact в versioned `frontend-static`. Build env — только public URL/realm/client id. Secret scanner: нет service tokens/mock OTP/S3 keys.
Image/frontend gate: images по digest; static без secrets; nginx image с request-id/TLS; disk >30% free.
## 6. Root Compose ВМ1
Сервисы: edge `nginx`, `api-backend`, `keycloak`, `sms-service`, `sms-worker`, `bitrix-local-app`, Redis DB0/DB1, local `otel-collector`. После cutover — без `message-safety` и Redis DB2.
Networks: `public`, `backend`, `egress` (`sms-worker`; Keycloak входит только при `KEYCLOAK_YANDEX_CAPTCHA_ENABLED=true` и только с destination allow-list SmartCaptcha API), `observability`. `sms-service` без egress при отдельном worker.
Volumes: `redis-data`, ACME, `frontend-static`, `otel-queue` + `otel-queue-init`. Единственные published mappings — nginx 80/443.
Compose gate — arch-10 применительно к этому Compose.
## 7. TLS ВМ1
Arch-10 §9 / arch-08, `-d <PUBLIC_HOST> --cert-name <PUBLIC_HOST>`. Private `8443` на ВМ1 нет.
## 8. Миграции и seed ВМ1
Preflight/upgrade:
```bash
cd <BACKEND_ROOT>
docker compose run --rm api-backend alembic current
docker compose run --rm bitrix-local-app alembic current
# PITR marker, затем:
docker compose run --rm api-backend alembic upgrade head
docker compose run --rm bitrix-local-app alembic upgrade head
```
Shared `han_app.sync_queue` мигрирует api-backend **до** sync cutover на ВМ2, но grants `bitrix_sync_user` — после обеих migrations (см. runbook ВМ2). Seed `app_settings` idempotent из этого репозитория.
SMS schema/migrations выполняет `sms-service` на ВМ1.
### Controlled rollout real SMS
1. seed `otp.phone.*`;
2. schema/role `sms`, migrations, seed `sms_setting`/active `auth_otp`;
3. test env с mock Direct;
4. production Direct `TOKEN_1`, sender, template, callback credentials;
5. записать `<IDGTL_STATIC_EGRESS_IP>` из `sms-worker`;
6. deploy `sms-service`/worker и callback route nginx ВМ1, `KEYCLOAK_OTP_MOCK_ENABLED=true`;
7. Keycloak expand migration/SPI;
8. provider smoke на `<IDGTL_TEST_PHONE>`;
9. только после evidence — `KEYCLOAK_OTP_MOCK_ENABLED=false`;
10. проверить durable order, resend, limits.
Rollback SMS: вернуть Keycloak в mock; не удалять schema/journal. Production cutover запрещён при placeholder или нестабильном egress IP.
## 9. Keycloak bootstrap
```bash
cd <BACKEND_ROOT>
docker compose up -d keycloak
```
Bootstrap admin только на первый запуск, затем MFA named admin и удаление bootstrap. Realm: public client PKCE S256, issuer `https://<PUBLIC_HOST>/auth/realms/han-chat`, без `--import-realm` на живой production без diff. Keycloak gate — discovery/JWKS HTTPS, OTP fail-closed, settings bridge.
## 10. Ordered startup ВМ1
После готовности ВМ2 (arch-10 §10 шаги 13):
1. Redis ВМ1, `otel-queue-init`, Collector;
2. API, SMS, Keycloak, local app;
3. edge nginx последним; после readiness — `nginx -t -c /tmp/nginx.conf` и HUP.
4. `MESSAGE_SAFETY_URL` переключается на ВМ2 **только** после cutover gates runbook ВМ2 и legacy gate §14.
Не использовать host ports для health curl. Expected: Redis `PONG`; Keycloak ready; Collector health; API core DB/Redis/JWKS/settings ready. S3/Safety/Open Lines могут быть `degraded` без снятия read API из readiness, но send path при недоступном Safety остаётся fail-closed. Local app до install может быть `portal_not_installed`.
## 11. Bitrix24 local app и Open Lines
Install/handler/placement URL на `https://<PUBLIC_HOST>/bitrix/...`. Canonical internal path `/internal/openlines/v1/*`, не prototype `/bitrix-internal/*`. Open Lines gate: connector line 8, outbound once, operator reply, duplicate callback безопасен.
CRM webhook robots **не** настраиваются на ВМ1.
## 12. Public smoke ВМ1
```bash
curl -I http://<PUBLIC_HOST>/
curl -fsS https://<PUBLIC_HOST>/api/v1/public/app-config
curl -fsS https://<PUBLIC_HOST>/api/v1/public/content
curl -fsS https://<PUBLIC_HOST>/auth/realms/han-chat/.well-known/openid-configuration
curl -i https://<PUBLIC_HOST>/internal/safety/v2/messages/check
openssl s_client -connect <PUBLIC_HOST>:443 -servername <PUBLIC_HOST>
```
Expected: 308; public 200; discovery 200; internal 404; valid cert.
Далее: guest content; 401 без JWT; consent → OTP → PKCE; bootstrap без phone body; `ux_session_id`; silent refresh; logout; wrong OTP. Real SMS mode — по §8.
Safety E2E **со стороны caller** (правила stub до v2 cutover): `ф``422 message_blocked`; allow path; timeout 503/504 без duplicate. Статус Safety `stub` не заменяет production AV.
Files: presigned PUT quarantine, promote/deny, owner-only download, нет URL в logs. Realtime/ownership/idempotency/429 — module-01.
Сквозной first-send до Open Lines требует готовую ВМ2; system end-to-end gate закрывается по arch-10 после обоих runbook.
## 13. Observability ВМ1
[`module-09-observability-vm1.md`](module-09-observability-vm1.md) + arch-07. Сквозной `X-Request-ID` до Safety span — совместно с ВМ2.
## 14. Legacy gate перед cutover Safety
Перед `MESSAGE_SAFETY_URL` на ВМ2:
1. validator принимает только `https://<private-vm2-name>:8443`, требует CA path, запрещает Docker hostname и plaintext;
2. internal CA root-owned; read-test UID `api-backend`, negative посторонний UID;
3. local `message-safety`, Redis DB2 и local rules-version env удалены из Compose/validator;
4. root-owned stack unit; `deploy` не в `docker`;
5. `DOCKER-USER` counters через `conntrack --ctorigdstport` после Docker restart и reboot;
6. images digest; rollback по compatible digests;
7. ordered startup этого runbook, не legacy `docker compose up -d`;
8. `han-secrets` и firewall oneshot явно перезапущены; TLS renew success — пустой stderr.
Ни старый single-VM guide, ни успешный stub Compose не являются evidence. Rollback caller — предыдущий immutable release ВМ1. Уже созданные v2 tasks не down-migrate.
## 15. Rollback, ops, incidents ВМ1
Rollback application-only: previous digests, без Alembic downgrade; при SMS incident — mock OTP, сохранить journal. Redis restore — clean instance, прогрев idempotency из PG ([`module-04-redis-vm1.md`](module-04-redis-vm1.md)). Keycloak restore — проверить issuer/JWKS/PKCE/OTP.
Routine: health, PG/TLS/disk/OTEL/Redis, Keycloak signing, Bitrix connector desired/observed.
Incident triage — arch-10 команды в `<BACKEND_ROOT>` ВМ1. Типовое: API 503 (DB/Redis/JWKS/Safety circuit); send timeout — не новый idempotency key; Redis loss — polling.
Потеря ВМ1: provision в той же VPC, restore secrets из vault, existing PG/S3, TLS `<PUBLIC_HOST>`, Bitrix local app/callbacks verify. Не пересоздавать ВМ2.
## 16. Definition of Done ВМ1
Дополнительно к arch-10 §12:
- Compose/nginx/Redis/Collector ВМ1 прошли профильные compose/nginx gates;
- Keycloak realm/provider/PKCE/OTP готов;
- SMS либо mock с accepted risk, либо real mode после §8;
- Bitrix connector line 8 проверен;
- auth/text/file/realtime E2E caller-side зелёный;
- observability ВМ1 + redaction;
- после cutover — legacy gate §14 закрыт.
## 17. TBD ВМ1
D-TBD1, D-TBD2 (VM1 sizing/SLO), D-TBD8 CLI, D-TBD9 Keycloak admin VPN.
## 18. Ссылки
- Контракт: [`arch-10-deployment.md`](../../architectory/arch-10-deployment.md).
- ВМ2: [`module-10-deployment-vm2.md`](../../VM2_services/documentation/module-10-deployment-vm2.md).
- Указатель: [`module-10-deployment-runbook.md`](module-10-deployment-runbook.md).