Normative first: Component-StoreAndForward.md:83 specified the whole chunked, ack-confirmed SfBufferSnapshotChunk resync protocol, which Phase 2 deleted. It is rewritten rather than removed — the failure modes it reasoned about still exist, they are just bounded differently — and it now states the duplicate-delivery bound explicitly: a message can be delivered twice only when the OLD primary delivered it and the status change had not yet replicated when the gate flipped. One flush interval plus the in-flight ack, and unlike the old model it does NOT grow with backlog depth or with how long a node was absent. The N1 directional-authority and N5 orphan-row hazards are recorded as structurally gone, not merely unguarded. Frame-size known-issue amended: its 2026-06-26 resolution replaced the intra-site hop with notify-and-fetch; Phase 2 then deleted notify-and-fetch itself, so the 128 KB Akka frame constraint no longer applies to that hop in any form. Successor ceiling recorded (4 MB gRPC cap via MaxBatchSize, which batches by ROW COUNT), including that the failure mode differs — an oversized gRPC message is rejected, not silently dropped. Deployment docs gain the two operational constraints that have no home in code: a site pair must be stopped and started TOGETHER (the SfBufferSnapshot compat handler that made a mixed-version pair converge went with the replicator, and a mixed pair now diverges silently), and a node offline beyond TombstoneRetention can resurrect deleted rows on rejoin. Both CLAUDE.md files corrected — each still said Phase 2 was NOT started. Definition of done closed: build 0 warnings, all 10 suites green (3509 tests, 0 failures, 0 skips). Two DoD items needed amending rather than ticking: the stale-symbol grep still matches 4 lines, all deliberate comment prose recording what was deleted (a literal zero would delete the explanations that stop the old design coming back), and the live gate has 10 evidence items, not the 9 the checklist claimed. Deletes the phase2 resume-state scratch doc, which said to delete it on landing. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
6.3 KiB
ScadaBridge Installation Guide
Prerequisites
- Windows Server 2019 or later
- .NET 10.0 Runtime
- SQL Server 2019+ (Central nodes only)
- Network connectivity between all cluster nodes (TCP ports 8081-8082)
- LDAP/Active Directory server accessible from Central nodes
- SMTP server accessible from all nodes (for Notification Service)
Single Binary Deployment
ScadaBridge ships as a single executable (ZB.MOM.WW.ScadaBridge.Host.exe) that runs in either Central or Site role based on configuration.
Windows Service Installation
# Central Node
sc.exe create "ScadaBridge-Central" binPath="C:\ScadaBridge\ZB.MOM.WW.ScadaBridge.Host.exe" start=auto
sc.exe description "ScadaBridge-Central" "ScadaBridge SCADA Central Hub"
# Site Node
sc.exe create "ScadaBridge-Site" binPath="C:\ScadaBridge\ZB.MOM.WW.ScadaBridge.Host.exe" start=auto
sc.exe description "ScadaBridge-Site" "ScadaBridge SCADA Site Agent"
Directory Structure
C:\ScadaBridge\
ZB.MOM.WW.ScadaBridge.Host.exe
appsettings.json
appsettings.Production.json
data\ # Site: SQLite databases
site.db # Deployed configs, static overrides
store-and-forward.db # S&F message buffer
logs\ # Rolling log files
scadabridge-20260316.log
Configuration Templates
Central Node — appsettings.json
{
"ScadaBridge": {
"Node": {
"Role": "Central",
"NodeHostname": "central-01.example.com",
"RemotingPort": 8081
},
"Cluster": {
"SeedNodes": [
"akka.tcp://scadabridge@central-01.example.com:8081",
"akka.tcp://scadabridge@central-02.example.com:8081"
]
},
"Database": {
"ConfigurationDb": "Server=sqlserver.example.com;Database=ScadaBridge;User Id=scadabridge_svc;Password=<CHANGE_ME>;Encrypt=true;TrustServerCertificate=false",
"MachineDataDb": "Server=sqlserver.example.com;Database=ScadaBridge_MachineData;User Id=scadabridge_svc;Password=<CHANGE_ME>;Encrypt=true;TrustServerCertificate=false"
},
"Security": {
"LdapServer": "ldap.example.com",
"LdapPort": 636,
"LdapUseTls": true,
"AllowInsecureLdap": false,
"LdapSearchBase": "dc=example,dc=com",
"JwtSigningKey": "<GENERATE_A_32_PLUS_CHAR_RANDOM_STRING>",
"JwtExpiryMinutes": 15,
"IdleTimeoutMinutes": 30
},
"HealthMonitoring": {
"ReportInterval": "00:00:30",
"OfflineTimeout": "00:01:00"
},
"Logging": {
"MinimumLevel": "Information"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Akka": "Warning"
}
}
}
}
Site Node — appsettings.json
{
"ScadaBridge": {
"Node": {
"Role": "Site",
"NodeHostname": "site-01-node-a.example.com",
"SiteId": "plant-north",
"RemotingPort": 8081
},
"Cluster": {
"SeedNodes": [
"akka.tcp://scadabridge@site-01-node-a.example.com:8081",
"akka.tcp://scadabridge@site-01-node-b.example.com:8081"
]
},
"Database": {
"SiteDbPath": "C:\\ScadaBridge\\data\\site.db"
},
"DataConnection": {
"ReconnectInterval": "00:00:05",
"TagResolutionRetryInterval": "00:00:30"
},
"StoreAndForward": {
"SqliteDbPath": "C:\\ScadaBridge\\data\\store-and-forward.db",
"DefaultRetryInterval": "00:00:30",
"DefaultMaxRetries": 50,
"ReplicationEnabled": true
},
"SiteRuntime": {
"ScriptTimeoutSeconds": 30,
"StaggeredStartupDelayMs": 50
},
"SiteEventLog": {
"RetentionDays": 30,
"MaxStorageMB": 1024,
"PurgeIntervalHours": 24
},
"Communication": {
"CentralSeedNode": "akka.tcp://scadabridge@central-01.example.com:8081"
},
"HealthMonitoring": {
"ReportInterval": "00:00:30"
},
"Logging": {
"MinimumLevel": "Information"
}
}
}
Database Setup (Central Only)
SQL Server
- Create the configuration database:
CREATE DATABASE ScadaBridge;
CREATE LOGIN scadabridge_svc WITH PASSWORD = '<STRONG_PASSWORD>';
USE ScadaBridge;
CREATE USER scadabridge_svc FOR LOGIN scadabridge_svc;
ALTER ROLE db_owner ADD MEMBER scadabridge_svc;
- Create the machine data database:
CREATE DATABASE ScadaBridge_MachineData;
USE ScadaBridge_MachineData;
CREATE USER scadabridge_svc FOR LOGIN scadabridge_svc;
ALTER ROLE db_owner ADD MEMBER scadabridge_svc;
-
Apply EF Core migrations (development):
- Migrations auto-apply on startup in Development environment.
-
Apply EF Core migrations (production):
- Generate SQL script:
dotnet ef migrations script --project src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase - Review and execute the SQL script against the production database.
- Generate SQL script:
Network Requirements
| Source | Destination | Port | Protocol | Purpose |
|---|---|---|---|---|
| Central A | Central B | 8081 | TCP | Akka.NET remoting |
| Site A | Site B | 8081 | TCP | Akka.NET remoting |
| Site nodes | Central nodes | 8081 | TCP | Central-site communication |
| Central nodes | LDAP server | 636 | TCP/TLS | Authentication |
| All nodes | SMTP server | 587 | TCP/TLS | Notification delivery |
| Central nodes | SQL Server | 1433 | TCP | Configuration database |
| Users | Central nodes | 443 | HTTPS | Blazor Server UI |
Firewall Rules
Ensure bidirectional TCP connectivity between all Akka.NET cluster peers. The remoting port (default 8081) must be open in both directions.
Upgrading a Site Pair
Stop both nodes of a site pair, upgrade both, then start both. Rolling one node at a time is
not supported as of LocalDb Phase 2 — the legacy snapshot-compatibility handler that made a
mixed-version pair converge was deleted with the bespoke replicator, and a mixed pair now diverges
silently. See docs/deployment/topology-guide.md for the reasoning and for the related
TombstoneRetention bound on how long one node may stay offline.
Post-Installation Verification
- Start the service:
sc.exe start ScadaBridge-Central - Check the log file:
type C:\ScadaBridge\logs\scadabridge-*.log - Verify the readiness endpoint:
curl http://localhost:5000/health/ready - For Central: verify the UI is accessible at
https://central-01.example.com/