refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Stand up a sibling `docker-env2/` directory that brings up a minimal second ScadaLink cluster (2 central + 1 site × 2 nodes + Traefik) on the same machine, concurrent with the existing `docker/` stack, so Transport (#24) can be exercised end-to-end across two real environments.
|
||||
**Goal:** Stand up a sibling `docker-env2/` directory that brings up a minimal second ScadaBridge cluster (2 central + 1 site × 2 nodes + Traefik) on the same machine, concurrent with the existing `docker/` stack, so Transport (#24) can be exercised end-to-end across two real environments.
|
||||
|
||||
**Architecture:** Five new app containers + Traefik on the shared `scadalink-net` Docker bridge. Reuses the existing `scadalink-mssql`, `scadalink-ldap`, `scadalink-smtp`, `scadalink-opcua`, `scadalink-restapi` infra containers. Env2 uses dedicated MSSQL databases `ScadaLinkConfig2` / `ScadaLinkMachineData2`. Host port pattern `91XX` (vs primary `90XX`). Single site `site-x`. No application/C# code changes — deploy tooling only.
|
||||
**Architecture:** Five new app containers + Traefik on the shared `scadabridge-net` Docker bridge. Reuses the existing `scadabridge-mssql`, `scadabridge-ldap`, `scadabridge-smtp`, `scadabridge-opcua`, `scadabridge-restapi` infra containers. Env2 uses dedicated MSSQL databases `ScadaBridgeConfig2` / `ScadaBridgeMachineData2`. Host port pattern `91XX` (vs primary `90XX`). Single site `site-x`. No application/C# code changes — deploy tooling only.
|
||||
|
||||
**Tech Stack:** Bash, Docker / Docker Compose, MS SQL 2022 (`sqlcmd`), Traefik v3.4, the existing `scadalink:latest` image.
|
||||
**Tech Stack:** Bash, Docker / Docker Compose, MS SQL 2022 (`sqlcmd`), Traefik v3.4, the existing `scadabridge:latest` image.
|
||||
|
||||
**Design source:** [docs/plans/2026-05-24-second-environment-design.md](2026-05-24-second-environment-design.md).
|
||||
|
||||
@@ -44,39 +44,39 @@ T10 is the only task that requires all of T0–T9 done. Everything else can run
|
||||
|
||||
**Step 1: Write `infra/mssql/setup-env2.sql`**
|
||||
|
||||
Idempotent script that creates the env2 logical databases and grants `db_owner` to the existing `scadalink_app` login. Mirrors the existing `setup.sql` exactly, swapping the database names. Does NOT create the login (it already exists from `setup.sql`).
|
||||
Idempotent script that creates the env2 logical databases and grants `db_owner` to the existing `scadabridge_app` login. Mirrors the existing `setup.sql` exactly, swapping the database names. Does NOT create the login (it already exists from `setup.sql`).
|
||||
|
||||
```sql
|
||||
-- ScadaLink env2 database setup
|
||||
-- Creates env2 logical databases on an existing scadalink-mssql instance.
|
||||
-- ScadaBridge env2 database setup
|
||||
-- Creates env2 logical databases on an existing scadabridge-mssql instance.
|
||||
-- Idempotent: re-runs are no-ops. Assumes setup.sql has already run
|
||||
-- (i.e. the scadalink_app login already exists).
|
||||
-- (i.e. the scadabridge_app login already exists).
|
||||
|
||||
-- Create env2 databases
|
||||
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'ScadaLinkConfig2')
|
||||
CREATE DATABASE ScadaLinkConfig2;
|
||||
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'ScadaBridgeConfig2')
|
||||
CREATE DATABASE ScadaBridgeConfig2;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'ScadaLinkMachineData2')
|
||||
CREATE DATABASE ScadaLinkMachineData2;
|
||||
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'ScadaBridgeMachineData2')
|
||||
CREATE DATABASE ScadaBridgeMachineData2;
|
||||
GO
|
||||
|
||||
-- Grant db_owner on ScadaLinkConfig2
|
||||
USE ScadaLinkConfig2;
|
||||
-- Grant db_owner on ScadaBridgeConfig2
|
||||
USE ScadaBridgeConfig2;
|
||||
GO
|
||||
IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = 'scadalink_app')
|
||||
CREATE USER scadalink_app FOR LOGIN scadalink_app;
|
||||
IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = 'scadabridge_app')
|
||||
CREATE USER scadabridge_app FOR LOGIN scadabridge_app;
|
||||
GO
|
||||
ALTER ROLE db_owner ADD MEMBER scadalink_app;
|
||||
ALTER ROLE db_owner ADD MEMBER scadabridge_app;
|
||||
GO
|
||||
|
||||
-- Grant db_owner on ScadaLinkMachineData2
|
||||
USE ScadaLinkMachineData2;
|
||||
-- Grant db_owner on ScadaBridgeMachineData2
|
||||
USE ScadaBridgeMachineData2;
|
||||
GO
|
||||
IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = 'scadalink_app')
|
||||
CREATE USER scadalink_app FOR LOGIN scadalink_app;
|
||||
IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = 'scadabridge_app')
|
||||
CREATE USER scadabridge_app FOR LOGIN scadabridge_app;
|
||||
GO
|
||||
ALTER ROLE db_owner ADD MEMBER scadalink_app;
|
||||
ALTER ROLE db_owner ADD MEMBER scadabridge_app;
|
||||
GO
|
||||
```
|
||||
|
||||
@@ -92,7 +92,7 @@ So the full `mssql` service `volumes:` block becomes:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- scadalink-mssql-data:/var/opt/mssql
|
||||
- scadabridge-mssql-data:/var/opt/mssql
|
||||
- ./mssql/setup.sql:/docker-entrypoint-initdb.d/setup.sql:ro
|
||||
- ./mssql/machinedata_seed.sql:/docker-entrypoint-initdb.d/machinedata_seed.sql:ro
|
||||
- ./mssql/setup-env2.sql:/docker-entrypoint-initdb.d/setup-env2.sql:ro
|
||||
@@ -167,8 +167,8 @@ http:
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
servers:
|
||||
- url: "http://scadalink-env2-central-a:5000"
|
||||
- url: "http://scadalink-env2-central-b:5000"
|
||||
- url: "http://scadabridge-env2-central-a:5000"
|
||||
- url: "http://scadabridge-env2-central-b:5000"
|
||||
```
|
||||
|
||||
Identical to primary except for the two `servers:` URLs — they point at the env2 central container hostnames.
|
||||
@@ -198,17 +198,17 @@ These are clones of `docker/central-node-a/appsettings.Central.json` and `docker
|
||||
|
||||
```json
|
||||
{
|
||||
"ScadaLink": {
|
||||
"ScadaBridge": {
|
||||
"Node": {
|
||||
"Role": "Central",
|
||||
"NodeName": "central-a",
|
||||
"NodeHostname": "scadalink-env2-central-a",
|
||||
"NodeHostname": "scadabridge-env2-central-a",
|
||||
"RemotingPort": 8081
|
||||
},
|
||||
"Cluster": {
|
||||
"SeedNodes": [
|
||||
"akka.tcp://scadalink@scadalink-env2-central-a:8081",
|
||||
"akka.tcp://scadalink@scadalink-env2-central-b:8081"
|
||||
"akka.tcp://scadabridge@scadabridge-env2-central-a:8081",
|
||||
"akka.tcp://scadabridge@scadabridge-env2-central-b:8081"
|
||||
],
|
||||
"SplitBrainResolverStrategy": "keep-oldest",
|
||||
"StableAfter": "00:00:15",
|
||||
@@ -217,18 +217,18 @@ These are clones of `docker/central-node-a/appsettings.Central.json` and `docker
|
||||
"MinNrOfMembers": 1
|
||||
},
|
||||
"Database": {
|
||||
"ConfigurationDb": "Server=scadalink-mssql,1433;Database=ScadaLinkConfig2;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true",
|
||||
"MachineDataDb": "Server=scadalink-mssql,1433;Database=ScadaLinkMachineData2;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true"
|
||||
"ConfigurationDb": "Server=scadabridge-mssql,1433;Database=ScadaBridgeConfig2;User Id=scadabridge_app;Password=ScadaBridge_Dev1#;TrustServerCertificate=true",
|
||||
"MachineDataDb": "Server=scadabridge-mssql,1433;Database=ScadaBridgeMachineData2;User Id=scadabridge_app;Password=ScadaBridge_Dev1#;TrustServerCertificate=true"
|
||||
},
|
||||
"Security": {
|
||||
"LdapServer": "scadalink-ldap",
|
||||
"LdapServer": "scadabridge-ldap",
|
||||
"LdapPort": 3893,
|
||||
"LdapUseTls": false,
|
||||
"AllowInsecureLdap": true,
|
||||
"LdapSearchBase": "dc=scadalink,dc=local",
|
||||
"LdapServiceAccountDn": "cn=admin,dc=scadalink,dc=local",
|
||||
"LdapSearchBase": "dc=scadabridge,dc=local",
|
||||
"LdapServiceAccountDn": "cn=admin,dc=scadabridge,dc=local",
|
||||
"LdapServiceAccountPassword": "password",
|
||||
"JwtSigningKey": "scadalink-env2-dev-jwt-signing-key-must-be-at-least-32-characters-long",
|
||||
"JwtSigningKey": "scadabridge-env2-dev-jwt-signing-key-must-be-at-least-32-characters-long",
|
||||
"JwtExpiryMinutes": 15,
|
||||
"IdleTimeoutMinutes": 30,
|
||||
"RequireHttpsCookie": false
|
||||
@@ -248,7 +248,7 @@ These are clones of `docker/central-node-a/appsettings.Central.json` and `docker
|
||||
"DefaultMethodTimeout": "00:00:30"
|
||||
},
|
||||
"Notification": {
|
||||
"SmtpServer": "scadalink-smtp",
|
||||
"SmtpServer": "scadabridge-smtp",
|
||||
"SmtpPort": 1025,
|
||||
"AuthMode": "None",
|
||||
"FromAddress": "scada-notifications-env2@company.com"
|
||||
@@ -271,7 +271,7 @@ These are clones of `docker/central-node-a/appsettings.Central.json` and `docker
|
||||
|
||||
Identical to node-a except two fields:
|
||||
- `Node.NodeName`: `"central-b"`
|
||||
- `Node.NodeHostname`: `"scadalink-env2-central-b"`
|
||||
- `Node.NodeHostname`: `"scadabridge-env2-central-b"`
|
||||
|
||||
(Everything else — cluster seed nodes, DB, security, etc. — is identical between a and b, exactly mirroring the primary's pattern.)
|
||||
|
||||
@@ -309,19 +309,19 @@ git commit -m "feat(docker-env2): add central node appsettings"
|
||||
|
||||
```json
|
||||
{
|
||||
"ScadaLink": {
|
||||
"ScadaBridge": {
|
||||
"Node": {
|
||||
"Role": "Site",
|
||||
"NodeName": "node-a",
|
||||
"NodeHostname": "scadalink-env2-site-x-a",
|
||||
"NodeHostname": "scadabridge-env2-site-x-a",
|
||||
"SiteId": "site-x",
|
||||
"RemotingPort": 8082,
|
||||
"GrpcPort": 8083
|
||||
},
|
||||
"Cluster": {
|
||||
"SeedNodes": [
|
||||
"akka.tcp://scadalink@scadalink-env2-site-x-a:8082",
|
||||
"akka.tcp://scadalink@scadalink-env2-site-x-b:8082"
|
||||
"akka.tcp://scadabridge@scadabridge-env2-site-x-a:8082",
|
||||
"akka.tcp://scadabridge@scadabridge-env2-site-x-b:8082"
|
||||
],
|
||||
"SplitBrainResolverStrategy": "keep-oldest",
|
||||
"StableAfter": "00:00:15",
|
||||
@@ -330,7 +330,7 @@ git commit -m "feat(docker-env2): add central node appsettings"
|
||||
"MinNrOfMembers": 1
|
||||
},
|
||||
"Database": {
|
||||
"SiteDbPath": "/app/data/scadalink.db"
|
||||
"SiteDbPath": "/app/data/scadabridge.db"
|
||||
},
|
||||
"DataConnection": {
|
||||
"ReconnectInterval": "00:00:05",
|
||||
@@ -343,8 +343,8 @@ git commit -m "feat(docker-env2): add central node appsettings"
|
||||
},
|
||||
"Communication": {
|
||||
"CentralContactPoints": [
|
||||
"akka.tcp://scadalink@scadalink-env2-central-a:8081",
|
||||
"akka.tcp://scadalink@scadalink-env2-central-b:8081"
|
||||
"akka.tcp://scadabridge@scadabridge-env2-central-a:8081",
|
||||
"akka.tcp://scadabridge@scadabridge-env2-central-b:8081"
|
||||
],
|
||||
"DeploymentTimeout": "00:02:00",
|
||||
"LifecycleTimeout": "00:00:30",
|
||||
@@ -373,7 +373,7 @@ git commit -m "feat(docker-env2): add central node appsettings"
|
||||
|
||||
Identical to node-a except:
|
||||
- `Node.NodeName`: `"node-b"`
|
||||
- `Node.NodeHostname`: `"scadalink-env2-site-x-b"`
|
||||
- `Node.NodeHostname`: `"scadabridge-env2-site-x-b"`
|
||||
|
||||
**Step 3: Sanity-check JSON**
|
||||
|
||||
@@ -406,13 +406,13 @@ git commit -m "feat(docker-env2): add site-x appsettings"
|
||||
|
||||
**Step 1: Write the file**
|
||||
|
||||
Five services: `central-a`, `central-b`, `site-x-a`, `site-x-b`, `traefik`. All on the external `scadalink-net` network. Host ports per the design's topology table.
|
||||
Five services: `central-a`, `central-b`, `site-x-a`, `site-x-b`, `traefik`. All on the external `scadabridge-net` network. Host ports per the design's topology table.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
central-a:
|
||||
image: scadalink:latest
|
||||
container_name: scadalink-env2-central-a
|
||||
image: scadabridge:latest
|
||||
container_name: scadabridge-env2-central-a
|
||||
environment:
|
||||
SCADALINK_CONFIG: Central
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
@@ -424,12 +424,12 @@ services:
|
||||
- ./central-node-a/appsettings.Central.json:/app/appsettings.Central.json:ro
|
||||
- ./central-node-a/logs:/app/logs
|
||||
networks:
|
||||
- scadalink-net
|
||||
- scadabridge-net
|
||||
restart: unless-stopped
|
||||
|
||||
central-b:
|
||||
image: scadalink:latest
|
||||
container_name: scadalink-env2-central-b
|
||||
image: scadabridge:latest
|
||||
container_name: scadabridge-env2-central-b
|
||||
environment:
|
||||
SCADALINK_CONFIG: Central
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
@@ -441,12 +441,12 @@ services:
|
||||
- ./central-node-b/appsettings.Central.json:/app/appsettings.Central.json:ro
|
||||
- ./central-node-b/logs:/app/logs
|
||||
networks:
|
||||
- scadalink-net
|
||||
- scadabridge-net
|
||||
restart: unless-stopped
|
||||
|
||||
site-x-a:
|
||||
image: scadalink:latest
|
||||
container_name: scadalink-env2-site-x-a
|
||||
image: scadabridge:latest
|
||||
container_name: scadabridge-env2-site-x-a
|
||||
environment:
|
||||
SCADALINK_CONFIG: Site
|
||||
ports:
|
||||
@@ -457,12 +457,12 @@ services:
|
||||
- ./site-x-node-a/data:/app/data
|
||||
- ./site-x-node-a/logs:/app/logs
|
||||
networks:
|
||||
- scadalink-net
|
||||
- scadabridge-net
|
||||
restart: unless-stopped
|
||||
|
||||
site-x-b:
|
||||
image: scadalink:latest
|
||||
container_name: scadalink-env2-site-x-b
|
||||
image: scadabridge:latest
|
||||
container_name: scadabridge-env2-site-x-b
|
||||
environment:
|
||||
SCADALINK_CONFIG: Site
|
||||
ports:
|
||||
@@ -473,12 +473,12 @@ services:
|
||||
- ./site-x-node-b/data:/app/data
|
||||
- ./site-x-node-b/logs:/app/logs
|
||||
networks:
|
||||
- scadalink-net
|
||||
- scadabridge-net
|
||||
restart: unless-stopped
|
||||
|
||||
traefik:
|
||||
image: traefik:v3.4
|
||||
container_name: scadalink-env2-traefik
|
||||
container_name: scadabridge-env2-traefik
|
||||
ports:
|
||||
- "9100:80" # Env2 central load-balanced entrypoint
|
||||
- "8181:8080" # Env2 Traefik dashboard
|
||||
@@ -486,11 +486,11 @@ services:
|
||||
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
||||
- ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
|
||||
networks:
|
||||
- scadalink-net
|
||||
- scadabridge-net
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
scadalink-net:
|
||||
scadabridge-net:
|
||||
external: true
|
||||
```
|
||||
|
||||
@@ -542,14 +542,14 @@ All four files must end with `chmod +x`.
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q '^scadalink-mssql$'; then
|
||||
echo "ERROR: scadalink-mssql is not running. Start it: cd infra && docker compose up -d" >&2
|
||||
if ! docker ps --format '{{.Names}}' | grep -q '^scadabridge-mssql$'; then
|
||||
echo "ERROR: scadabridge-mssql is not running. Start it: cd infra && docker compose up -d" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Applying env2 database setup..."
|
||||
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
|
||||
docker exec -i scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C \
|
||||
< "$SCRIPT_DIR/../infra/mssql/setup-env2.sql"
|
||||
|
||||
echo "Env2 databases ready."
|
||||
@@ -563,12 +563,12 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== ScadaLink Env2 Docker Deploy ==="
|
||||
echo "=== ScadaBridge Env2 Docker Deploy ==="
|
||||
|
||||
# Reuse the primary build (same scadalink:latest image, same network creation)
|
||||
# Reuse the primary build (same scadabridge:latest image, same network creation)
|
||||
"$SCRIPT_DIR/../docker/build.sh"
|
||||
|
||||
# Ensure env2 databases exist on the shared scadalink-mssql
|
||||
# Ensure env2 databases exist on the shared scadabridge-mssql
|
||||
"$SCRIPT_DIR/init-db.sh"
|
||||
|
||||
echo ""
|
||||
@@ -602,7 +602,7 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== ScadaLink Env2 Docker Teardown ==="
|
||||
echo "=== ScadaBridge Env2 Docker Teardown ==="
|
||||
|
||||
echo "Stopping env2 application containers..."
|
||||
docker compose -f "$SCRIPT_DIR/docker-compose.yml" down
|
||||
@@ -611,11 +611,11 @@ echo ""
|
||||
echo "Teardown complete."
|
||||
echo "Site data (SQLite DBs) and logs are preserved in node directories."
|
||||
echo ""
|
||||
echo "Env2 databases (ScadaLinkConfig2 / ScadaLinkMachineData2) remain on"
|
||||
echo "the shared scadalink-mssql. To drop them:"
|
||||
echo " docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \\"
|
||||
echo " -S localhost -U sa -P 'ScadaLink_Dev1#' -C \\"
|
||||
echo " -Q \"DROP DATABASE ScadaLinkConfig2; DROP DATABASE ScadaLinkMachineData2;\""
|
||||
echo "Env2 databases (ScadaBridgeConfig2 / ScadaBridgeMachineData2) remain on"
|
||||
echo "the shared scadabridge-mssql. To drop them:"
|
||||
echo " docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \\"
|
||||
echo " -S localhost -U sa -P 'ScadaBridge_Dev1#' -C \\"
|
||||
echo " -Q \"DROP DATABASE ScadaBridgeConfig2; DROP DATABASE ScadaBridgeMachineData2;\""
|
||||
```
|
||||
|
||||
**Step 4: Write `docker-env2/seed-sites.sh`**
|
||||
@@ -637,11 +637,11 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CLI="dotnet run --project $PROJECT_ROOT/src/ScadaLink.CLI --"
|
||||
CLI="dotnet run --project $PROJECT_ROOT/src/ZB.MOM.WW.ScadaBridge.CLI --"
|
||||
AUTH="--username multi-role --password password"
|
||||
URL="--url http://localhost:9100"
|
||||
|
||||
echo "=== Seeding ScadaLink Env2 Sites ==="
|
||||
echo "=== Seeding ScadaBridge Env2 Sites ==="
|
||||
|
||||
echo ""
|
||||
echo "Creating Site-X (Env2 Site X)..."
|
||||
@@ -649,10 +649,10 @@ $CLI $URL $AUTH site create \
|
||||
--name "Env2 Site X" \
|
||||
--identifier "site-x" \
|
||||
--description "Env2 test site - two-node cluster" \
|
||||
--node-a-address "akka.tcp://scadalink@scadalink-env2-site-x-a:8082" \
|
||||
--node-b-address "akka.tcp://scadalink@scadalink-env2-site-x-b:8082" \
|
||||
--grpc-node-a-address "http://scadalink-env2-site-x-a:8083" \
|
||||
--grpc-node-b-address "http://scadalink-env2-site-x-b:8083" \
|
||||
--node-a-address "akka.tcp://scadabridge@scadabridge-env2-site-x-a:8082" \
|
||||
--node-b-address "akka.tcp://scadabridge@scadabridge-env2-site-x-b:8082" \
|
||||
--grpc-node-a-address "http://scadabridge-env2-site-x-a:8083" \
|
||||
--grpc-node-b-address "http://scadabridge-env2-site-x-b:8083" \
|
||||
|| echo " (Site-X may already exist)"
|
||||
|
||||
echo ""
|
||||
@@ -756,9 +756,9 @@ git commit -m "chore(gitignore): add explicit docker-env2 runtime data patterns"
|
||||
Mirrors `docker/README.md`'s structure but documents the env2 specifics. Reuses tables and command idioms verbatim where they're the same; calls out the differences (port range, single site, shared MSSQL with separate databases).
|
||||
|
||||
```markdown
|
||||
# ScadaLink Env2 Docker Infrastructure
|
||||
# ScadaBridge Env2 Docker Infrastructure
|
||||
|
||||
A second Docker deployment of a minimal ScadaLink cluster topology, designed to run **concurrently with** the primary `docker/` stack so the Transport (#24) feature can be exercised end-to-end across two real environments.
|
||||
A second Docker deployment of a minimal ScadaBridge cluster topology, designed to run **concurrently with** the primary `docker/` stack so the Transport (#24) feature can be exercised end-to-end across two real environments.
|
||||
|
||||
See [`docs/plans/2026-05-24-second-environment-design.md`](../docs/plans/2026-05-24-second-environment-design.md) for the design rationale.
|
||||
|
||||
@@ -798,30 +798,30 @@ Env2 host ports are the primary's ports + 100. Both stacks can run simultaneousl
|
||||
|
||||
| Node | Container Name | Host Web | Host Akka | Host gRPC | Internal |
|
||||
|------|----------------|----------|-----------|-----------|----------|
|
||||
| Traefik LB | `scadalink-env2-traefik` | 9100 | — | — | 80 (proxy), 8080 (dashboard host:8181) |
|
||||
| Central A | `scadalink-env2-central-a` | 9101 | 9111 | — | 5000 (web), 8081 (Akka) |
|
||||
| Central B | `scadalink-env2-central-b` | 9102 | 9112 | — | 5000 (web), 8081 (Akka) |
|
||||
| Site-X A | `scadalink-env2-site-x-a` | — | 9121 | 9123 | 8082 (Akka), 8083 (gRPC) |
|
||||
| Site-X B | `scadalink-env2-site-x-b` | — | 9122 | 9124 | 8082 (Akka), 8083 (gRPC) |
|
||||
| Traefik LB | `scadabridge-env2-traefik` | 9100 | — | — | 80 (proxy), 8080 (dashboard host:8181) |
|
||||
| Central A | `scadabridge-env2-central-a` | 9101 | 9111 | — | 5000 (web), 8081 (Akka) |
|
||||
| Central B | `scadabridge-env2-central-b` | 9102 | 9112 | — | 5000 (web), 8081 (Akka) |
|
||||
| Site-X A | `scadabridge-env2-site-x-a` | — | 9121 | 9123 | 8082 (Akka), 8083 (gRPC) |
|
||||
| Site-X B | `scadabridge-env2-site-x-b` | — | 9122 | 9124 | 8082 (Akka), 8083 (gRPC) |
|
||||
|
||||
## Shared Infrastructure
|
||||
|
||||
Env2 attaches to the existing `scadalink-net` Docker bridge network and reuses these primary infra containers:
|
||||
Env2 attaches to the existing `scadabridge-net` Docker bridge network and reuses these primary infra containers:
|
||||
|
||||
| Service | Container | What env2 uses it for |
|
||||
|---------|-----------|-----------------------|
|
||||
| MS SQL | `scadalink-mssql` | Env2-specific databases `ScadaLinkConfig2` / `ScadaLinkMachineData2` |
|
||||
| LDAP | `scadalink-ldap` | Authentication (same test users) |
|
||||
| SMTP | `scadalink-smtp` | Notification capture in Mailpit (env2 emails distinguishable by `FromAddress`) |
|
||||
| OPC UA | `scadalink-opcua` | Simulated tags for site-x data connections |
|
||||
| REST API | `scadalink-restapi` | External REST API testing |
|
||||
| MS SQL | `scadabridge-mssql` | Env2-specific databases `ScadaBridgeConfig2` / `ScadaBridgeMachineData2` |
|
||||
| LDAP | `scadabridge-ldap` | Authentication (same test users) |
|
||||
| SMTP | `scadabridge-smtp` | Notification capture in Mailpit (env2 emails distinguishable by `FromAddress`) |
|
||||
| OPC UA | `scadabridge-opcua` | Simulated tags for site-x data connections |
|
||||
| REST API | `scadabridge-restapi` | External REST API testing |
|
||||
|
||||
## Commands
|
||||
|
||||
### First-Time Setup
|
||||
|
||||
```bash
|
||||
# 1. Make sure primary infra is up (creates scadalink-net, scadalink-mssql, etc.)
|
||||
# 1. Make sure primary infra is up (creates scadabridge-net, scadabridge-mssql, etc.)
|
||||
cd infra && docker compose up -d && cd ..
|
||||
|
||||
# 2. Build image + create env2 databases + deploy env2 containers
|
||||
@@ -859,15 +859,15 @@ bash docker-env2/teardown.sh
|
||||
Containers stop, volumes (data + logs) preserved. To also drop the env2 databases:
|
||||
|
||||
```bash
|
||||
docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
|
||||
-Q "DROP DATABASE ScadaLinkConfig2; DROP DATABASE ScadaLinkMachineData2;"
|
||||
docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C \
|
||||
-Q "DROP DATABASE ScadaBridgeConfig2; DROP DATABASE ScadaBridgeMachineData2;"
|
||||
```
|
||||
|
||||
### CLI Access
|
||||
|
||||
```bash
|
||||
dotnet run --project src/ScadaLink.CLI -- \
|
||||
dotnet run --project src/ZB.MOM.WW.ScadaBridge.CLI -- \
|
||||
--url http://localhost:9100 \
|
||||
--username multi-role --password password \
|
||||
template list
|
||||
@@ -877,7 +877,7 @@ dotnet run --project src/ScadaLink.CLI -- \
|
||||
|
||||
```bash
|
||||
docker compose -f docker-env2/docker-compose.yml logs -f
|
||||
docker logs -f scadalink-env2-central-a
|
||||
docker logs -f scadabridge-env2-central-a
|
||||
```
|
||||
|
||||
## Test Users
|
||||
@@ -892,8 +892,8 @@ See [`docs/plans/2026-05-24-second-environment-verification.md`](../docs/plans/2
|
||||
|
||||
- Single site (`site-x`) instead of three (`site-a/b/c`).
|
||||
- Host port range 91XX vs primary 90XX.
|
||||
- Container names prefixed `scadalink-env2-`.
|
||||
- Databases `ScadaLinkConfig2` / `ScadaLinkMachineData2` on the **shared** `scadalink-mssql`.
|
||||
- Container names prefixed `scadabridge-env2-`.
|
||||
- Databases `ScadaBridgeConfig2` / `ScadaBridgeMachineData2` on the **shared** `scadabridge-mssql`.
|
||||
- `Transport.SourceEnvironment` = `"docker-cluster-env2"` (stamped into exported bundle manifests).
|
||||
- Distinct `Security.JwtSigningKey` (sessions cannot cross envs).
|
||||
```
|
||||
@@ -965,12 +965,12 @@ Locate the "First-Time SQL Setup" section (line 22). The existing instructions c
|
||||
For the second environment (`docker-env2/`), also apply the env2 database setup:
|
||||
|
||||
```bash
|
||||
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
|
||||
docker exec -i scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C \
|
||||
-i /docker-entrypoint-initdb.d/setup-env2.sql
|
||||
```
|
||||
|
||||
This creates `ScadaLinkConfig2` and `ScadaLinkMachineData2` databases on the same MSSQL instance. The script is also invoked automatically by `docker-env2/deploy.sh` via `docker-env2/init-db.sh`, so manual application here is only needed if you want the databases ready before first env2 deploy.
|
||||
This creates `ScadaBridgeConfig2` and `ScadaBridgeMachineData2` databases on the same MSSQL instance. The script is also invoked automatically by `docker-env2/deploy.sh` via `docker-env2/init-db.sh`, so manual application here is only needed if you want the databases ready before first env2 deploy.
|
||||
```
|
||||
|
||||
**Step 4: Verify the three edits**
|
||||
@@ -1014,12 +1014,12 @@ The file mirrors the structure of `docs/plans/2026-05-24-transport-manual-verifi
|
||||
|
||||
- `infra/` services running: `cd infra && docker compose up -d`
|
||||
- Primary stack running and healthy: `bash docker/deploy.sh && bash docker/seed-sites.sh`
|
||||
- `scadalink-mssql` reachable (`docker ps | grep scadalink-mssql`)
|
||||
- `scadabridge-mssql` reachable (`docker ps | grep scadabridge-mssql`)
|
||||
|
||||
## Step 0: Bring up env2
|
||||
|
||||
- [ ] `bash docker-env2/deploy.sh` completes without error
|
||||
- [ ] `docker ps --format '{{.Names}}'` shows 5 new `scadalink-env2-*` containers
|
||||
- [ ] `docker ps --format '{{.Names}}'` shows 5 new `scadabridge-env2-*` containers
|
||||
- [ ] `curl -s http://localhost:9101/health/ready` returns 200 with `"status": "Healthy"`
|
||||
- [ ] `curl -s http://localhost:9100/health/active` is routed through Traefik
|
||||
- [ ] http://localhost:9100 loads the env2 login page in a browser
|
||||
@@ -1087,9 +1087,9 @@ The file mirrors the structure of `docs/plans/2026-05-24-transport-manual-verifi
|
||||
- [ ] `bash docker-env2/teardown.sh` stops env2 containers
|
||||
- [ ] Drop env2 databases if needed:
|
||||
```bash
|
||||
docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
|
||||
-Q "DROP DATABASE ScadaLinkConfig2; DROP DATABASE ScadaLinkMachineData2;"
|
||||
docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C \
|
||||
-Q "DROP DATABASE ScadaBridgeConfig2; DROP DATABASE ScadaBridgeMachineData2;"
|
||||
```
|
||||
|
||||
## Pass criteria
|
||||
@@ -1121,7 +1121,7 @@ This is the live verification that the whole stack actually comes up. The subage
|
||||
**Step 1: Confirm infra is up**
|
||||
|
||||
```bash
|
||||
docker ps --format '{{.Names}}' | grep -E 'scadalink-(mssql|ldap|smtp|opcua|restapi)' | sort
|
||||
docker ps --format '{{.Names}}' | grep -E 'scadabridge-(mssql|ldap|smtp|opcua|restapi)' | sort
|
||||
```
|
||||
|
||||
Expected: 5 (or 6 with opcua2) container names.
|
||||
@@ -1143,7 +1143,7 @@ Expected: 5 new containers up, output ends with the "Access points" block.
|
||||
**Step 3: Verify env2 container set**
|
||||
|
||||
```bash
|
||||
docker ps --format '{{.Names}}\t{{.Status}}' | grep '^scadalink-env2-'
|
||||
docker ps --format '{{.Names}}\t{{.Status}}' | grep '^scadabridge-env2-'
|
||||
```
|
||||
|
||||
Expected: 5 lines, all `Up …`.
|
||||
@@ -1163,23 +1163,23 @@ Expected: both `/health/ready` responses are JSON with `status` Healthy; `/healt
|
||||
**Step 5: Verify env2 databases exist**
|
||||
|
||||
```bash
|
||||
docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C -h -1 \
|
||||
-Q "SELECT name FROM sys.databases WHERE name LIKE 'ScadaLink%' ORDER BY name;"
|
||||
docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C -h -1 \
|
||||
-Q "SELECT name FROM sys.databases WHERE name LIKE 'ScadaBridge%' ORDER BY name;"
|
||||
```
|
||||
|
||||
Expected: four database names listed — `ScadaLinkConfig`, `ScadaLinkConfig2`, `ScadaLinkMachineData`, `ScadaLinkMachineData2`.
|
||||
Expected: four database names listed — `ScadaBridgeConfig`, `ScadaBridgeConfig2`, `ScadaBridgeMachineData`, `ScadaBridgeMachineData2`.
|
||||
|
||||
**Step 6: Verify EF migrations ran on env2's config DB**
|
||||
|
||||
```bash
|
||||
docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C -h -1 \
|
||||
-d ScadaLinkConfig2 \
|
||||
docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C -h -1 \
|
||||
-d ScadaBridgeConfig2 \
|
||||
-Q "SELECT COUNT(*) FROM __EFMigrationsHistory;"
|
||||
```
|
||||
|
||||
Expected: a non-zero integer (matches the count in primary's `ScadaLinkConfig`).
|
||||
Expected: a non-zero integer (matches the count in primary's `ScadaBridgeConfig`).
|
||||
|
||||
**Step 7: Seed env2's site**
|
||||
|
||||
@@ -1194,8 +1194,8 @@ Expected: site-x created (or "may already exist" on re-run).
|
||||
Wait ~30s after seeding, then:
|
||||
|
||||
```bash
|
||||
docker logs scadalink-env2-site-x-a 2>&1 | tail -20
|
||||
docker logs scadalink-env2-central-a 2>&1 | grep -i "site-x" | tail -5
|
||||
docker logs scadabridge-env2-site-x-a 2>&1 | tail -20
|
||||
docker logs scadabridge-env2-central-a 2>&1 | grep -i "site-x" | tail -5
|
||||
```
|
||||
|
||||
Expected: no fatal errors; central log shows site-x connection / health report ingest.
|
||||
@@ -1203,7 +1203,7 @@ Expected: no fatal errors; central log shows site-x connection / health report i
|
||||
**Step 9: Verify port-free coexistence with primary**
|
||||
|
||||
```bash
|
||||
docker ps --format '{{.Names}}' | grep -c '^scadalink-'
|
||||
docker ps --format '{{.Names}}' | grep -c '^scadabridge-'
|
||||
```
|
||||
|
||||
Expected: 14 (8 primary app + 1 primary Traefik + 5 env2). If primary isn't running, expect 6.
|
||||
@@ -1211,13 +1211,13 @@ Expected: 14 (8 primary app + 1 primary Traefik + 5 env2). If primary isn't runn
|
||||
**Step 10: Confirm cross-environment isolation**
|
||||
|
||||
```bash
|
||||
docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C -h -1 \
|
||||
-d ScadaLinkConfig \
|
||||
docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C -h -1 \
|
||||
-d ScadaBridgeConfig \
|
||||
-Q "SELECT COUNT(*) FROM Sites;"
|
||||
docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaLink_Dev1#' -C -h -1 \
|
||||
-d ScadaLinkConfig2 \
|
||||
docker exec scadabridge-mssql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P 'ScadaBridge_Dev1#' -C -h -1 \
|
||||
-d ScadaBridgeConfig2 \
|
||||
-Q "SELECT COUNT(*) FROM Sites;"
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user