Phase 8: Production readiness — failover tests, security hardening, sandboxing, deployment docs
- WP-1-3: Central/site failover + dual-node recovery tests (17 tests) - WP-4: Performance testing framework for target scale (7 tests) - WP-5: Security hardening (LDAPS, JWT key length, no secrets in logs) (11 tests) - WP-6: Script sandboxing adversarial tests (28 tests, all forbidden APIs) - WP-7: Recovery drill test scaffolds (5 tests) - WP-8: Observability validation (structured logs, correlation IDs, metrics) (6 tests) - WP-9: Message contract compatibility (forward/backward compat) (18 tests) - WP-10: Deployment packaging (installation guide, production checklist, topology) - WP-11: Operational runbooks (failover, troubleshooting, maintenance) 92 new tests, all passing. Zero warnings.
This commit is contained in:
195
docs/deployment/installation-guide.md
Normal file
195
docs/deployment/installation-guide.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# ScadaLink 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
|
||||
|
||||
ScadaLink ships as a single executable (`ScadaLink.Host.exe`) that runs in either Central or Site role based on configuration.
|
||||
|
||||
### Windows Service Installation
|
||||
|
||||
```powershell
|
||||
# Central Node
|
||||
sc.exe create "ScadaLink-Central" binPath="C:\ScadaLink\ScadaLink.Host.exe" start=auto
|
||||
sc.exe description "ScadaLink-Central" "ScadaLink SCADA Central Hub"
|
||||
|
||||
# Site Node
|
||||
sc.exe create "ScadaLink-Site" binPath="C:\ScadaLink\ScadaLink.Host.exe" start=auto
|
||||
sc.exe description "ScadaLink-Site" "ScadaLink SCADA Site Agent"
|
||||
```
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
C:\ScadaLink\
|
||||
ScadaLink.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
|
||||
scadalink-20260316.log
|
||||
```
|
||||
|
||||
## Configuration Templates
|
||||
|
||||
### Central Node — `appsettings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"ScadaLink": {
|
||||
"Node": {
|
||||
"Role": "Central",
|
||||
"NodeHostname": "central-01.example.com",
|
||||
"RemotingPort": 8081
|
||||
},
|
||||
"Cluster": {
|
||||
"SeedNodes": [
|
||||
"akka.tcp://scadalink@central-01.example.com:8081",
|
||||
"akka.tcp://scadalink@central-02.example.com:8081"
|
||||
]
|
||||
},
|
||||
"Database": {
|
||||
"ConfigurationDb": "Server=sqlserver.example.com;Database=ScadaLink;User Id=scadalink_svc;Password=<CHANGE_ME>;Encrypt=true;TrustServerCertificate=false",
|
||||
"MachineDataDb": "Server=sqlserver.example.com;Database=ScadaLink_MachineData;User Id=scadalink_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`
|
||||
|
||||
```json
|
||||
{
|
||||
"ScadaLink": {
|
||||
"Node": {
|
||||
"Role": "Site",
|
||||
"NodeHostname": "site-01-node-a.example.com",
|
||||
"SiteId": "plant-north",
|
||||
"RemotingPort": 8081
|
||||
},
|
||||
"Cluster": {
|
||||
"SeedNodes": [
|
||||
"akka.tcp://scadalink@site-01-node-a.example.com:8081",
|
||||
"akka.tcp://scadalink@site-01-node-b.example.com:8081"
|
||||
]
|
||||
},
|
||||
"Database": {
|
||||
"SiteDbPath": "C:\\ScadaLink\\data\\site.db"
|
||||
},
|
||||
"DataConnection": {
|
||||
"ReconnectInterval": "00:00:05",
|
||||
"TagResolutionRetryInterval": "00:00:30"
|
||||
},
|
||||
"StoreAndForward": {
|
||||
"SqliteDbPath": "C:\\ScadaLink\\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://scadalink@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 ScadaLink;
|
||||
CREATE LOGIN scadalink_svc WITH PASSWORD = '<STRONG_PASSWORD>';
|
||||
USE ScadaLink;
|
||||
CREATE USER scadalink_svc FOR LOGIN scadalink_svc;
|
||||
ALTER ROLE db_owner ADD MEMBER scadalink_svc;
|
||||
```
|
||||
|
||||
2. Create the machine data database:
|
||||
```sql
|
||||
CREATE DATABASE ScadaLink_MachineData;
|
||||
USE ScadaLink_MachineData;
|
||||
CREATE USER scadalink_svc FOR LOGIN scadalink_svc;
|
||||
ALTER ROLE db_owner ADD MEMBER scadalink_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/ScadaLink.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.
|
||||
|
||||
## Post-Installation Verification
|
||||
|
||||
1. Start the service: `sc.exe start ScadaLink-Central`
|
||||
2. Check the log file: `type C:\ScadaLink\logs\scadalink-*.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/`
|
||||
Reference in New Issue
Block a user