324ba369d9
Row 79 executed its own prescribed remedy without waiting for a second corruption: all ten rig data dirs (8 docker/ nodes + 2 docker-env2/ site nodes) move from virtiofs bind mounts to named docker volumes (scadabridge-<node>-data), seeded in place with both rigs stopped and live-verified (site-a localDbReplicationConnected=true / backlog 0 over the migrated files; env2 stable, 0 restarts). The feared inspectability loss is moot — OrbStack exposes volumes at ~/OrbStack/docker/volumes/. The rebuild surfaced latent env2 drift (pre-Secrets-0.6.2 image): env2 now carries the x-secrets-store-env posture + central data volumes. Row 93's silent maintenance trap is now loud: SiteEventsSchemaCalibrationTests pins the site_events columns/indexes the EventLogPurgeService size model is calibrated against, failing with a pointer to the memo §4 recalibration on any schema drift. Rows 92 (accepted by design) and 94 (standing tripwire) annotated accordingly.
172 lines
8.0 KiB
YAML
172 lines
8.0 KiB
YAML
# Secrets 0.6.2 requires Secrets:SqlitePath to be absolute AND outside the content root
|
|
# (/app) — mirrors docker/docker-compose.yml's x-secrets-store-env anchor, including the
|
|
# trap documented there: DELETING the key does not fall back to the per-user default, it
|
|
# yields the RELATIVE "secrets.db", which fails both rules and the node refuses to boot.
|
|
# (env2 ran a stale pre-0.6.2 image until the 2026-08-18 rebuild, which surfaced this.)
|
|
# /data is the node's own data volume mounted a second time outside /app, so the store
|
|
# shares the durable volume without living under the content root.
|
|
x-secrets-store-env: &secrets-store-env
|
|
Secrets__SqlitePath: "/data/scadabridge-secrets.db"
|
|
|
|
services:
|
|
central-a:
|
|
image: scadabridge:latest
|
|
# CoordinatedShutdown needs cluster-leave (15s budget) + cluster-exiting +
|
|
# actor-system-terminate + Serilog flush; the 10s SIGTERM default SIGKILLed
|
|
# mid-drain, turning every redeploy into the crash path (review 01 [Medium]).
|
|
stop_grace_period: 30s
|
|
container_name: scadabridge-env2-central-a
|
|
environment:
|
|
<<: *secrets-store-env
|
|
SCADABRIDGE_CONFIG: Central
|
|
ASPNETCORE_ENVIRONMENT: Development
|
|
ASPNETCORE_URLS: "http://+:5000"
|
|
# DEV-ONLY local-cluster value — NOT a real secret. The Auth/Config normalization
|
|
# (2026-06-03) made ScadaBridge:InboundApi:ApiKeyPepper a hard Central startup
|
|
# requirement (>=16 chars, per-environment). Distinct from the docker/ cluster's
|
|
# pepper per the "different per environment" guidance; real deployments inject a
|
|
# true secret out-of-band, never from source control. Both Central nodes share it.
|
|
ScadaBridge__InboundApi__ApiKeyPepper: "dev-only-insecure-pepper-env2-cluster-0001"
|
|
# DEV-ONLY gRPC control-plane preshared key for site-x — NOT a real secret.
|
|
# Must match ScadaBridge:Communication:GrpcPsk in site-x-node-*/appsettings.Site.json.
|
|
# Production seeds SB-GRPC-PSK-<siteId> into the secret store instead.
|
|
ScadaBridge__Communication__SitePsks__site-x: "dev-grpc-psk-docker-env2-site-x"
|
|
ports:
|
|
- "9101:5000" # Web UI + Inbound API
|
|
- "9111:8081" # Akka remoting
|
|
volumes:
|
|
- ./central-node-a/appsettings.Central.json:/app/appsettings.Central.json:ro
|
|
- central-node-a-data:/app/data
|
|
# Same named volume, mounted a second time OUTSIDE the content root (/app) for the
|
|
# secrets store — see the x-secrets-store-env anchor above.
|
|
- central-node-a-data:/data
|
|
- ./central-node-a/logs:/app/logs
|
|
networks:
|
|
- scadabridge-net
|
|
restart: unless-stopped
|
|
|
|
central-b:
|
|
image: scadabridge:latest
|
|
# CoordinatedShutdown needs cluster-leave (15s budget) + cluster-exiting +
|
|
# actor-system-terminate + Serilog flush; the 10s SIGTERM default SIGKILLed
|
|
# mid-drain, turning every redeploy into the crash path (review 01 [Medium]).
|
|
stop_grace_period: 30s
|
|
container_name: scadabridge-env2-central-b
|
|
environment:
|
|
<<: *secrets-store-env
|
|
SCADABRIDGE_CONFIG: Central
|
|
ASPNETCORE_ENVIRONMENT: Development
|
|
ASPNETCORE_URLS: "http://+:5000"
|
|
# DEV-ONLY local-cluster value — NOT a real secret. The Auth/Config normalization
|
|
# (2026-06-03) made ScadaBridge:InboundApi:ApiKeyPepper a hard Central startup
|
|
# requirement (>=16 chars, per-environment). Distinct from the docker/ cluster's
|
|
# pepper per the "different per environment" guidance; real deployments inject a
|
|
# true secret out-of-band, never from source control. Both Central nodes share it.
|
|
ScadaBridge__InboundApi__ApiKeyPepper: "dev-only-insecure-pepper-env2-cluster-0001"
|
|
# DEV-ONLY gRPC control-plane preshared key for site-x — NOT a real secret.
|
|
# Must match ScadaBridge:Communication:GrpcPsk in site-x-node-*/appsettings.Site.json.
|
|
# Production seeds SB-GRPC-PSK-<siteId> into the secret store instead.
|
|
ScadaBridge__Communication__SitePsks__site-x: "dev-grpc-psk-docker-env2-site-x"
|
|
ports:
|
|
- "9102:5000" # Web UI + Inbound API
|
|
- "9112:8081" # Akka remoting
|
|
volumes:
|
|
- ./central-node-b/appsettings.Central.json:/app/appsettings.Central.json:ro
|
|
- central-node-b-data:/app/data
|
|
# Same named volume, mounted a second time OUTSIDE the content root (/app) for the
|
|
# secrets store — see the x-secrets-store-env anchor above.
|
|
- central-node-b-data:/data
|
|
- ./central-node-b/logs:/app/logs
|
|
networks:
|
|
- scadabridge-net
|
|
restart: unless-stopped
|
|
|
|
site-x-a:
|
|
image: scadabridge:latest
|
|
# CoordinatedShutdown needs cluster-leave (15s budget) + cluster-exiting +
|
|
# actor-system-terminate + Serilog flush; the 10s SIGTERM default SIGKILLed
|
|
# mid-drain, turning every redeploy into the crash path (review 01 [Medium]).
|
|
stop_grace_period: 30s
|
|
container_name: scadabridge-env2-site-x-a
|
|
environment:
|
|
<<: *secrets-store-env
|
|
SCADABRIDGE_CONFIG: Site
|
|
# Register row 63 — mirrors docker/docker-compose.yml. Enables the Development-only
|
|
# DI scope validation (register row 50) on the site nodes, which previously ran
|
|
# unvalidated because only the central containers set this. Dev/rig only.
|
|
ASPNETCORE_ENVIRONMENT: Development
|
|
ports:
|
|
- "9121:8082" # Akka remoting
|
|
- "9123:8083" # gRPC streaming
|
|
volumes:
|
|
- ./site-x-node-a/appsettings.Site.json:/app/appsettings.Site.json:ro
|
|
- site-x-node-a-data:/app/data
|
|
# Same named volume, mounted a second time OUTSIDE the content root (/app) for the
|
|
# secrets store — see the x-secrets-store-env anchor above.
|
|
- site-x-node-a-data:/data
|
|
- ./site-x-node-a/logs:/app/logs
|
|
networks:
|
|
- scadabridge-net
|
|
restart: unless-stopped
|
|
|
|
site-x-b:
|
|
image: scadabridge:latest
|
|
# CoordinatedShutdown needs cluster-leave (15s budget) + cluster-exiting +
|
|
# actor-system-terminate + Serilog flush; the 10s SIGTERM default SIGKILLed
|
|
# mid-drain, turning every redeploy into the crash path (review 01 [Medium]).
|
|
stop_grace_period: 30s
|
|
container_name: scadabridge-env2-site-x-b
|
|
environment:
|
|
<<: *secrets-store-env
|
|
SCADABRIDGE_CONFIG: Site
|
|
# Register row 63 — mirrors docker/docker-compose.yml. Enables the Development-only
|
|
# DI scope validation (register row 50) on the site nodes, which previously ran
|
|
# unvalidated because only the central containers set this. Dev/rig only.
|
|
ASPNETCORE_ENVIRONMENT: Development
|
|
ports:
|
|
- "9122:8082" # Akka remoting
|
|
- "9124:8083" # gRPC streaming
|
|
volumes:
|
|
- ./site-x-node-b/appsettings.Site.json:/app/appsettings.Site.json:ro
|
|
- site-x-node-b-data:/app/data
|
|
# Same named volume, mounted a second time OUTSIDE the content root (/app) for the
|
|
# secrets store — see the x-secrets-store-env anchor above.
|
|
- site-x-node-b-data:/data
|
|
- ./site-x-node-b/logs:/app/logs
|
|
networks:
|
|
- scadabridge-net
|
|
restart: unless-stopped
|
|
|
|
traefik:
|
|
image: traefik:v3.4
|
|
container_name: scadabridge-env2-traefik
|
|
ports:
|
|
- "9100:80" # Env2 central load-balanced entrypoint
|
|
- "8181:8080" # Env2 Traefik dashboard
|
|
volumes:
|
|
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
|
- ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
|
|
networks:
|
|
- scadabridge-net
|
|
restart: unless-stopped
|
|
|
|
networks:
|
|
scadabridge-net:
|
|
external: true
|
|
|
|
# Named volumes for the nodes' SQLite data dirs — mirrors docker/docker-compose.yml
|
|
# (register row 79: bind mounts cross OrbStack's virtiofs, which does not give SQLite
|
|
# true fsync durability). Host-side inspection via ~/OrbStack/docker/volumes/<name>.
|
|
# The central volumes are NEW with the Secrets 0.6.2 posture — env2 centrals previously
|
|
# kept data on the container overlay (lost on recreate), which 0.6.2's store-path guard
|
|
# exists to prevent.
|
|
volumes:
|
|
central-node-a-data:
|
|
name: scadabridge-env2-central-node-a-data
|
|
central-node-b-data:
|
|
name: scadabridge-env2-central-node-b-data
|
|
site-x-node-a-data:
|
|
name: scadabridge-env2-site-x-node-a-data
|
|
site-x-node-b-data:
|
|
name: scadabridge-env2-site-x-node-b-data
|