9cf7c0f007
Task 11. site-a-a is the initiator (dials scadabridge-site-a-b:8083, the
existing site gRPC h2c listener - no new port); site-a-b is passive. Both carry
the SAME dev ApiKey, which is load-bearing: LocalDbSyncAuthInterceptor is
fail-closed, so a mismatch does not degrade to unauthenticated replication, it
rejects every stream and the pair silently stops converging.
site-b and site-c stay unreplicated on purpose, so the default-OFF posture is
proven side-by-side on one rig rather than asserted.
Plain dev key matches the rig's existing posture; production uses a
${secret:...} reference through the pre-host expander. Noted in both files.
Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
87 lines
3.0 KiB
JSON
87 lines
3.0 KiB
JSON
{
|
|
"ScadaBridge": {
|
|
"Node": {
|
|
"Role": "Site",
|
|
"NodeName": "node-a",
|
|
"NodeHostname": "scadabridge-site-a-a",
|
|
"SiteId": "site-a",
|
|
"RemotingPort": 8082,
|
|
"GrpcPort": 8083,
|
|
"MetricsPort": 8084
|
|
},
|
|
"Cluster": {
|
|
"SeedNodes": [
|
|
"akka.tcp://scadabridge@scadabridge-site-a-a:8082",
|
|
"akka.tcp://scadabridge@scadabridge-site-a-b:8082"
|
|
],
|
|
"SplitBrainResolverStrategy": "keep-oldest",
|
|
"StableAfter": "00:00:15",
|
|
"HeartbeatInterval": "00:00:02",
|
|
"FailureDetectionThreshold": "00:00:10",
|
|
"MinNrOfMembers": 1
|
|
},
|
|
"Database": {
|
|
"SiteDbPath": "/app/data/scadabridge.db"
|
|
},
|
|
"DataConnection": {
|
|
"ReconnectInterval": "00:00:05",
|
|
"TagResolutionRetryInterval": "00:00:10",
|
|
"WriteTimeout": "00:00:30",
|
|
"SeedReadTimeout": "00:00:30"
|
|
},
|
|
"StoreAndForward": {
|
|
"SqliteDbPath": "/app/data/store-and-forward.db",
|
|
"ReplicationEnabled": true
|
|
},
|
|
"Communication": {
|
|
"CentralContactPoints": [
|
|
"akka.tcp://scadabridge@scadabridge-central-a:8081",
|
|
"akka.tcp://scadabridge@scadabridge-central-b:8081"
|
|
],
|
|
"DeploymentTimeout": "00:02:00",
|
|
"LifecycleTimeout": "00:00:30",
|
|
"QueryTimeout": "00:00:30",
|
|
"TransportHeartbeatInterval": "00:00:05",
|
|
"TransportFailureThreshold": "00:00:15"
|
|
},
|
|
"HealthMonitoring": {
|
|
"ReportInterval": "00:00:30",
|
|
"OfflineTimeout": "00:01:00"
|
|
},
|
|
"SiteEventLog": {
|
|
"RetentionDays": 30,
|
|
"MaxStorageMb": 1024,
|
|
"PurgeScheduleCron": "0 2 * * *"
|
|
},
|
|
"Notification": {},
|
|
"Logging": {
|
|
"MinimumLevel": "Information"
|
|
}
|
|
},
|
|
// Consolidated site database (LocalDb Phase 1): OperationTracking + site_events.
|
|
// On the mounted /app/data volume so it survives container recreate - unlike the
|
|
// legacy site-tracking.db / site_events.db, which defaulted to CWD-relative paths
|
|
// outside the volume and were lost on every recreate.
|
|
// Replication is opt-in and configured separately; absent = local-only.
|
|
"LocalDb": {
|
|
"Path": "/app/data/site-localdb.db",
|
|
// Site-a is the rig's REPLICATED pair; site-b and site-c deliberately stay
|
|
// unreplicated so the default-OFF posture is proven side-by-side on one rig.
|
|
//
|
|
// This node is the initiator: it dials the peer. Replication is still
|
|
// bidirectional - the passive node's writes flow back over the same stream -
|
|
// so only one side needs PeerAddress. Port 8083 is the existing site gRPC
|
|
// listener (h2c); the sync endpoint shares it, no new port.
|
|
//
|
|
// The ApiKey must be IDENTICAL on both nodes: the host's
|
|
// LocalDbSyncAuthInterceptor is fail-closed, so a mismatch (or a missing key)
|
|
// rejects every sync stream. A plain dev key here matches the rig's existing
|
|
// posture; PRODUCTION uses a "${secret:...}" reference resolved by the
|
|
// pre-host secret expander.
|
|
"Replication": {
|
|
"PeerAddress": "http://scadabridge-site-a-b:8083",
|
|
"ApiKey": "dev-site-a-localdb-sync-key"
|
|
}
|
|
}
|
|
}
|