11 KiB
VM2 Processing deployment runbook
This directory is the independent VM2 foundation in the VM2_services
repository. It does not deploy VM1 or VM1_app/codebase/backend. All commands
below are operator commands; repository creation does not execute them.
Repository root: HAN_chat_specification/VM2_services. Compose and deployment
artifacts live under VM2_services/codebase/services/. Local operator commands
assume the current directory is VM2_services unless stated otherwise.
The full step-by-step procedure with gates and copy-paste commands is in
RUNBOOK.ru.md.
Production blockers before first start
- Replace every
.envplaceholder with reviewed non-secret values. KeepBITRIX_SYNC_ENABLED=falseuntil migrations, grants, portal fields, robot contracts and cutover are signed off. Build and push application images to the registry first (blocker 2). - Fill every
*_IMAGEvariable with a reviewed registry digest. Root Compose rejects missing image references; mutable tags are not production evidence. - Install production files as
root:root;deploymust not be indockerand must not be able to write Compose, units, helpers, allow-lists or secret mappings. - Populate separate reviewed active CIDR files from the two
.templatefiles. Their committed active versions are intentionallydeny all. - Provision public ACME material under host
/etc/letsencryptand the managed PostgreSQL CA under/etc/han/ca. Provision an internal-CA certificate whose SAN matches the private VM2 name. Permit host port8443only from VM1 SG and, when needed, approved private/VPN ops CIDRs. - Create a dedicated VM2 Selectel IAM principal. It may read only names in
deployment/secrets/config.example.json. Never reuse the VM1 principal. REDIS_SAFETY_ACLis the complete ACL file, not merely a password. It must expose unauthenticatedPINGonly for health and a password-protectedsafetyuser limited to requiredhan:safety:*keys/commands. The password inMESSAGE_SAFETY_REDIS_URLmust match. Start fromredis/redis-safety.acl.template, replaceREPLACE_WITH_LONG_RANDOM_PASSWORD, and never commit the password.- Provision distinct runtime and migration DB credentials.
MESSAGE_SAFETY_CONFIG_ADMIN_DATABASE_URLmay migrate/activate policy whileMESSAGE_SAFETY_DATABASE_URLcannot;BITRIX_SYNC_MIGRATION_DATABASE_URLowns DDL whileBITRIX_SYNC_DATABASE_URLis the least-privilege runtime role. Migration credentials are mounted only into theopsprofile jobs. - The setup script leaves UFW egress open for bootstrap. Before production, constrain egress through Selectel SG/NAT/proxy to the approved PostgreSQL, S3, Secrets Manager, Bitrix24, DNS/NTP, SigNoz and ClamAV destinations. Registry/package access exists only during controlled maintenance windows.
Who runs what
- Operator workstation: builds the release archive and transfers it to VM2.
Local examples use PowerShell from
VM2_services. rooton VM2: host bootstrap, verified release activation, root-owned files,.env, secret mapping, credentials, TLS/allow-lists, migrations and first start.deployon VM2: accepts releases only in/var/lib/han-deploy/incoming, checks status/logs and runs installed fixed systemd operations through exact sudo rules.deploymust not rundocker, edit/opt/han-chat/servicesor join thedockergroup.adminon VM2: personal break-glass role with a separate SSH key and local sudo password. Not used for routine deploy; not indocker/lxd; every login and sudo call is an incident operation.
Prerequisites (before §1)
This runbook covers operations on an already provisioned VM2. Prepare outside Compose first:
- Selectel infrastructure — VPC/subnet, SG (public
80/443/22; private8443only; default-deny egress after bootstrap), sizing (4 vCPU / 8 GB RAM / 80 GB SSD — seemodule-10-deployment-vm2.md), public and private VM2 IPs, DNS A record forPROCESSING_PUBLIC_HOST. - Managed PostgreSQL — schemas/roles for
message_safetyandbitrix_sync, separate migration/runtime DSNs; seearch-10-deployment.md§6. - Images — build and push
han-message-safety,han-bitrix-sync; record immutable digests for every*_IMAGEin.env.example(nginx, redis, clamav, otel-collector). - Selectel Secrets Manager — populate all remote names from
deployment/secrets/config.example.json(DSNs, tokens, S3 read-only keys,REDIS_SAFETY_ACL, internal TLS PEM for8443). Dedicated VM2 IAM principal with read-only access to those names only. - S3 quarantine bucket and SigNoz OTLP endpoint — non-secret values in
.env. - Internal TLS — internal-CA certificate with SAN = VM2 private DNS; PEM stored in Secrets Manager, not in the release tree.
Section order: §1–§5 → Gates 1–9 → §7 (post-acceptance). Run
systemctl enable and systemctl start han-processing.service only after
Gate 5 succeeds.
1. Bootstrap a fresh VM2
From VM2_services on the operator workstation, copy the setup script:
scp -i C:\Users\MI\.ssh\hansel `
.\codebase\services\deployment\scripts\setup-vm.sh `
root@<VM2_PUBLIC_IP>:/root/setup-vm2.sh
Bootstrap as root with separate Ed25519 deploy/admin keys, VM1_PRIVATE_CIDRS,
optional OPS_CIDRS, admin sudo password, deploy/admin login verification, then
rerun with HARDEN_SSH=true. Full commands: RUNBOOK.ru.md §1.
2. Release transfer under deploy
From VM2_services:
$Release = "<VERSION_OR_GIT_SHA>"
tar --exclude=services/.env `
--exclude='services/**/__pycache__' `
--exclude='services/**/.pytest_cache' `
--exclude='services/**/.ruff_cache' `
-czf "vm2-services-$Release.tar.gz" -C .\codebase services
Get-FileHash "vm2-services-$Release.tar.gz" -Algorithm SHA256
scp -i C:\Users\MI\.ssh\hansel "vm2-services-$Release.tar.gz" `
deploy@<VM2_PUBLIC_IP>:/var/lib/han-deploy/incoming/
deploy verifies SHA-256 and archive listing only; it must not unpack into
production. Details: RUNBOOK.ru.md §2–§3.
3. Activation and root-owned install
root verifies the archive, extracts to staging, rsyncs into
/opt/han-chat/services, reruns setup-vm.sh to install helpers/units. Details:
RUNBOOK.ru.md §3.
4. Non-secret config and Selectel
Copy .env.example → .env, install loader config as
/etc/han/secrets/vm2-<APP_ENV>.selectel.json, encrypt Selectel service-user
password with systemd-creds, edit nginx allow-lists. Details:
RUNBOOK.ru.md §4.
5. PostgreSQL CA and initial public TLS
Install managed PostgreSQL CA under /etc/han/ca, issue Let's Encrypt cert for
PROCESSING_PUBLIC_HOST, stage public cert/key for nginx. Details:
RUNBOOK.ru.md §5.
6. Gates 1–9: preflight, migrations and first start
Execute gates in order 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9. Do not enable
han-processing.service until Gate 5 completes successfully.
| Gate | Purpose |
|---|---|
| 1 | Secrets materialized via han-secrets-vm2.service |
| 2 | Static preflight and resolved Compose (@sha256: images) |
| 3 | Migrations, initial Message Safety config create/activate |
| 4 | nginx -t with TLS and upstream placeholders |
| 5 | Ordered compose up; then systemctl enable/start han-processing |
| 6 | Host ports, public/private certificate chains, certbot renewal |
| 7 | Public routing smoke (308/404/426/405) |
| 8 | Private Safety/sync API from VM1/ops only |
| 9 | Fake-token canary — no secrets in logs/traces |
After Gate 5, deploy may run:
sudo systemctl restart han-secrets-vm2.service
sudo systemctl restart han-processing.service
sudo systemctl --no-pager status han-processing.service
sudo journalctl --no-pager -u han-processing.service
Full gate commands: RUNBOOK.ru.md §6.
Do not open webhook traffic while bitrix-sync is disabled. A disabled or
failed receiver must return retryable 503/closed routing, never successful
2xx ignored.
7. After Gate 9 — post-acceptance
Gate 9 completes VM2 technical acceptance but does not authorize Message Safety cutover on VM1 or Bitrix sync enablement.
- Verify autostart:
docker,han-chat-vm2-docker-firewall,han-secrets-vm2,han-processing,certbot.timer. - Reboot-gate:
systemctl reboot, then repeat autostart checks and Gates 6–8 briefly. - Record release evidence:
han-vm2-compose config --images,ps, certbot timer, unit journals — without secret values. - Configure operational monitoring (unhealthy/restart/OOM, TLS expiry, ClamAV signature age, OTEL queue, disk/RAM, MOCK mode, private Safety API).
- Proceed to controlled Message Safety cutover on VM1 — see
module-10-deployment-vm2.md§13. - Keep
BITRIX_SYNC_ENABLED=falseandBITRIX_SYNC_MODE=disabled; Bitrix public allow-list remainsdeny all;untilmodule-07-bitrix-sync.mdcutover gates are signed off.
Details: RUNBOOK.ru.md §7.
Failure policy
- Safety dependency failure is fail-closed: VM1 must not send/promote content.
- Stale/unavailable ClamAV signatures disable file capability only; they never convert a scan error to allow.
- Redis loss may remove acceleration but PostgreSQL remains authoritative.
- OTEL outage queues within the bounded volume and must not change verdicts.
- Rollback does not downgrade schemas, delete durable tasks/mappings, or run
docker compose down -v.
Emergency MOCK
Only these five sudo commands are allowed:
han-message-safety-mode standard
han-message-safety-mode mock --text-free true --file-free true
han-message-safety-mode mock --text-free true --file-free false
han-message-safety-mode mock --text-free false --file-free true
han-message-safety-mode mock --text-free false --file-free false
The helper atomically writes only
/etc/han-chat/message-safety-mode.env, recreates only the Safety API, checks
health, and restores the previous mode on failure. MOCK has no timeout: keep a
high-severity alert active until explicit standard, then verify normal
text/link/file capabilities and an EICAR canary.
Known image exceptions
ClamAV images may require UID/path adjustments after validating the exact
digest. Do not weaken read_only, capabilities or mounts globally: document
the smallest writable signature/runtime paths and compensate with network and
resource limits. freshclam alone receives signature-CDN egress; clamd
receives none.