12fa7affc3
Pins ZB.MOM.WW.LocalDb, .Replication and .Contracts at 0.3.7 (published 2026-11-12). Wire-compatible; no config or code change needed. - 0.3.6 (scadaproj#36): the library sync service enforces LocalDb:Replication:ApiKey itself. LocalDbSyncAuthInterceptor is kept for its refuse-every-stream-when-blank rule; its doc comment is updated. The rig's site-a pair already carries one identical key on both nodes. - 0.3.7 (scadaproj#16): a tombstone is held until the peer has it, so a node offline past TombstoneRetention no longer resurrects deleted rows. The survivor's ledger (sf_messages / site_events deletes) grows for the length of the outage; optional TombstoneHardCap bounds it. Deploy notes added to the topology + installation guides, the S&F requirement and CLAUDE.md: stop/start the pair together, same ApiKey on both nodes, ledger growth while a peer is down. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
6.6 KiB
6.6 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"
},
"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.
For a replicated pair (LocalDb 0.3.7):
LocalDb:Replication:ApiKeymust be set, byte-identical, on both nodes (a${secret:...}reference or an environment variable — never a literal in a shipped config). Both the library and ScadaBridge'sLocalDbSyncAuthInterceptorrefuse the sync stream without it.- While one node is down, the survivor's delete tombstones are held until the partner returns and acks them, so its LocalDb file grows for the length of the outage. Deleted rows no longer resurrect on rejoin, however long the outage (scadaproj#16).
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/