# 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 ```powershell # 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` ```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=;Encrypt=true;TrustServerCertificate=false" }, "Security": { "LdapServer": "ldap.example.com", "LdapPort": 636, "LdapUseTls": true, "AllowInsecureLdap": false, "LdapSearchBase": "dc=example,dc=com", "JwtSigningKey": "", "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` ```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 1. Create the configuration database: ```sql CREATE DATABASE ScadaBridge; CREATE LOGIN scadabridge_svc WITH PASSWORD = ''; USE ScadaBridge; CREATE USER scadabridge_svc FOR LOGIN scadabridge_svc; ALTER ROLE db_owner ADD MEMBER scadabridge_svc; ``` 2. Create the machine data database: ```sql CREATE DATABASE ScadaBridge_MachineData; USE ScadaBridge_MachineData; CREATE USER scadabridge_svc FOR LOGIN scadabridge_svc; ALTER ROLE db_owner ADD MEMBER scadabridge_svc; ``` 3. Apply EF Core migrations (development): - Migrations auto-apply on startup in Development environment. 4. 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. ## 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:ApiKey` must 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's `LocalDbSyncAuthInterceptor` refuse 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 1. Start the service: `sc.exe start ScadaBridge-Central` 2. Check the log file: `type C:\ScadaBridge\logs\scadabridge-*.log` 3. Verify the readiness endpoint: `curl http://localhost:5000/health/ready` 4. For Central: verify the UI is accessible at `https://central-01.example.com/`