6.9 KiB
VM2 Processing deployment runbook
This directory is the independent VM2 foundation. It does not deploy VM1 or
codebase/backend. All commands below are operator commands; repository
creation does not execute them.
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. - 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.
Install
- Bootstrap a fresh Ubuntu 24.04 VM as root with
deployment/scripts/setup-vm.sh, supplyingVM1_PRIVATE_CIDRS, optional private/VPNOPS_CIDRS, and separate Ed25519 public-key files for deploy and break-glass admin. SSH is publicly reachable but key-only and protected by fail2ban; the CIDR variables apply only to private port8443. The script installs host packages/firewalls and roles but never starts Compose. Set a separate admin sudo password; verify deploy login, admin login and admin sudo in independent sessions before rerunning withHARDEN_SSH=true. Root/deploy/admin key reuse is rejected. - Checkout an immutable release under
/opt/han-chat/services. - Copy
.env.exampleto root-owned mode0600.env. - Install
secrets_loader.pyandhan-secretsunder/usr/local/lib/han-secrets-vm2/, root-owned and non-writable. - Install
han-composeas/usr/local/sbin/han-vm2-compose. - Install
han-secrets-vm2.serviceandhan-processing.serviceunder/etc/systemd/system/. - Install
han-message-safety-modeas root-owned0755and the sudoers template as/etc/sudoers.d/deploy-message-safety-modemode0440; validate withvisudo -cf. Create the dedicated host grouphan-message-safetywith GID10001. Before the first Compose validation, create/etc/han-chat/message-safety-mode.envasroot:han-message-safety 0640with all three flagsfalse(or invoke the helper'sstandardtransition after the fixed launcher is installed). - Install loader config using the exact
APP_ENVsuffix. With the committed example (APP_ENV=production-like) the path is/etc/han/secrets/vm2-production-like.selectel.jsonmode0600. For controlled no-provider recovery use an explicitfileconfig pointing to a root-only0700directory containing exactly one file per configured key. Selectel failure never falls back automatically.
Preflight and startup
Run deployment/preflight.sh first. Then, through the approved root units:
- synchronize secrets; any missing/oversized/invalid secret blocks startup;
- validate resolved Compose without storing its output;
- run the two
opsmigration jobs and create/activate the reviewed initial Message Safety config before starting either runtime; - validate nginx config and both certificate chains;
- start Redis/Collector, ClamAV, application API/workers, then nginx;
- verify that only nginx publishes
80,443, and private-bound8443; - verify all non-exact public paths return
404, HTTP webhook paths return426without redirect/query reflection, wrong methods fail, and wrong source CIDRs are rejected before upstream; - verify private Safety check/task/status and sync status only from approved
callers; verify public
/internal/*is404; - canary telemetry with a fake token marker and prove query, form body, Authorization, DSN, S3 key and object key are absent from logs/traces.
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.
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.