From 6d87ee3c3ba8491c935ec92d33fe188574b6473e Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 28 May 2026 09:27:43 -0400 Subject: [PATCH] docs: add deployments/ catalog with per-deployment markdown One file per local Docker cluster (docker-cluster, docker-cluster-env2) keyed by Transport.SourceEnvironment. README indexes the set. --- deployments/README.md | 15 ++++ deployments/docker-cluster-env2.md | 118 ++++++++++++++++++++++++++ deployments/docker-cluster.md | 129 +++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 deployments/README.md create mode 100644 deployments/docker-cluster-env2.md create mode 100644 deployments/docker-cluster.md diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 00000000..394e1500 --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,15 @@ +# Deployments + +Catalog of ScadaLink deployments. Each entry below has a corresponding markdown file with topology, ports, infrastructure dependencies, and operational commands. The deployment **ID** matches the `Transport.SourceEnvironment` value stamped into exported bundle manifests (Component #24), so an audit trail of "where did this bundle come from" maps 1:1 to a file in this folder. + +| Deployment ID | Type | Topology | Doc | +|---------------|------|----------|-----| +| `docker-cluster` | Local Docker (primary) | 2 central + 3 sites × 2 nodes (8 nodes) | [docker-cluster.md](docker-cluster.md) | +| `docker-cluster-env2` | Local Docker (secondary) | 2 central + 1 site × 2 nodes (4 nodes) | [docker-cluster-env2.md](docker-cluster-env2.md) | + +## Conventions + +- One markdown file per deployment, named for the deployment ID. +- Add a new file **and** a row above when introducing a new environment. +- Keep the deployment ID, container-name prefix, host-port range, and database names consistent across the doc, `appsettings.*.json`, and any `Transport.SourceEnvironment` value. +- When a deployment is decommissioned, move its row to a `## Decommissioned` section rather than deleting the file — the ID may still appear in historic Transport bundle manifests. diff --git a/deployments/docker-cluster-env2.md b/deployments/docker-cluster-env2.md new file mode 100644 index 00000000..e6db6fdb --- /dev/null +++ b/deployments/docker-cluster-env2.md @@ -0,0 +1,118 @@ +# docker-cluster-env2 + +Secondary local Docker deployment — a minimal ScadaLink topology that runs **concurrently with** [`docker-cluster`](docker-cluster.md) so the Transport (#24) feature can be exercised end-to-end across two real environments (export a bundle from one, import into the other). + +- **Deployment ID:** `docker-cluster-env2` +- **Transport.SourceEnvironment:** `docker-cluster-env2` +- **Source directory:** [`docker-env2/`](../docker-env2/) +- **Container-name prefix:** `scadalink-env2-` +- **Host-port range:** `91XX` (primary `90XX` + 100) + +## Topology + +``` + ┌───────────────────┐ + │ Traefik LB :9100 │ ◄── CLI / Browser + │ Dashboard :8181 │ + └────────┬──────────┘ + │ routes to active node +┌──────────────────────┼──────────────────────────────┐ +│ Env2 Central Cluster │ +│ env2-central-a (UI :9101, Akka :9111) │ +│ env2-central-b (UI :9102, Akka :9112) │ +└───────────┬─────────────────────────────────────────┘ + │ Akka.NET remoting + gRPC streaming + ▼ + Env2 Site-X (Env2 Site X) + Akka 9121/9122 + gRPC 9123/9124 +``` + +Same active/standby pattern as the primary, scaled down to a single site cluster to keep the footprint small. Built specifically for cross-environment Transport testing — not a general-purpose dev environment. + +## Nodes + +| Node | Container Name | Host Web | Host Akka | Host gRPC | +|------|----------------|----------|-----------|-----------| +| Traefik LB | `scadalink-env2-traefik` | 9100 | — | — | +| Central A | `scadalink-env2-central-a` | 9101 | 9111 | — | +| Central B | `scadalink-env2-central-b` | 9102 | 9112 | — | +| Site-X A | `scadalink-env2-site-x-a` | — | 9121 | 9123 | +| Site-X B | `scadalink-env2-site-x-b` | — | 9122 | 9124 | + +## Sites + +| Site Identifier | Central UI Name | +|-----------------|-----------------| +| `site-x` | Env2 Site X | + +## Infrastructure Dependencies + +Env2 reuses the primary's `infra/` containers via the shared `scadalink-net` bridge network — there is no separate `infra/` stack to start. + +| Service | Shared Container | What env2 uses it for | +|---------|------------------|-----------------------| +| MS SQL | `scadalink-mssql` | Env2 databases `ScadaLinkConfig2` / `ScadaLinkMachineData2` (separate DBs on the same instance) | +| LDAP | `scadalink-ldap` | Authentication (same test users) | +| SMTP | `scadalink-smtp` | Notification capture (env2 emails distinguishable by `FromAddress`) | +| OPC UA | `scadalink-opcua` | Simulated tags for `site-x` data connections | +| REST API | `scadalink-restapi` | External REST integration testing | + +## Databases + +| Database | Owner | +|----------|-------| +| `ScadaLinkConfig2` | Configuration Database (#17) — env2 instance | +| `ScadaLinkMachineData2` | Machine data — env2 instance | + +Created by `docker-env2/init-db.sh`, which `deploy.sh` runs automatically. + +## Commands + +```bash +# Primary infra must already be up (creates scadalink-net + MS SQL + LDAP + SMTP + OPC UA + REST) +cd infra && docker compose up -d && cd .. + +# Build image (shared with primary), create env2 DBs, deploy env2 containers +bash docker-env2/deploy.sh + +# First-time only: seed the env2 test site +bash docker-env2/seed-sites.sh + +# View logs +docker compose -f docker-env2/docker-compose.yml logs -f +docker logs -f scadalink-env2-central-a + +# Stop env2 (preserves SQLite + logs) +bash docker-env2/teardown.sh + +# Also drop env2 databases +docker exec scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P 'ScadaLink_Dev1#' -C \ + -Q "DROP DATABASE ScadaLinkConfig2; DROP DATABASE ScadaLinkMachineData2;" +``` + +## CLI Access + +```bash +dotnet run --project src/ScadaLink.CLI -- \ + --url http://localhost:9100 \ + --username multi-role --password password \ + template list +``` + +## What's Different from `docker-cluster` + +- Single site (`site-x`) instead of three (`site-a`/`site-b`/`site-c`). +- Host port range `91XX` instead of `90XX`. +- Container names prefixed `scadalink-env2-` instead of `scadalink-`. +- Databases `ScadaLinkConfig2` / `ScadaLinkMachineData2` on the **shared** `scadalink-mssql`. +- `Transport.SourceEnvironment = "docker-cluster-env2"` stamped into exported bundle manifests. +- Distinct `Security.JwtSigningKey` so sessions cannot cross environments. + +## Notes + +- Concurrent with [`docker-cluster`](docker-cluster.md) — both stacks share `scadalink-net` and the `infra/` services. Disjoint host ports + DB names + container names allow them to run side-by-side. +- Design rationale: [`docs/plans/2026-05-24-second-environment-design.md`](../docs/plans/2026-05-24-second-environment-design.md). +- Transport golden-path verification: [`docs/plans/2026-05-24-second-environment-verification.md`](../docs/plans/2026-05-24-second-environment-verification.md). +- Setup details, port table, and CLI examples in [`docker-env2/README.md`](../docker-env2/README.md). diff --git a/deployments/docker-cluster.md b/deployments/docker-cluster.md new file mode 100644 index 00000000..5ad2e9aa --- /dev/null +++ b/deployments/docker-cluster.md @@ -0,0 +1,129 @@ +# docker-cluster + +Primary local Docker deployment — the full ScadaLink reference topology used for day-to-day development and integration testing. Runs on the developer workstation under Docker / OrbStack. + +- **Deployment ID:** `docker-cluster` +- **Transport.SourceEnvironment:** `docker-cluster` +- **Source directory:** [`docker/`](../docker/) +- **Container-name prefix:** `scadalink-` +- **Host-port range:** `90XX` + +## Topology + +``` + ┌───────────────────┐ + │ Traefik LB :9000 │ ◄── CLI / Browser + │ Dashboard :8180 │ + └────────┬──────────┘ + │ routes to active node +┌──────────────────────┼──────────────────────────────┐ +│ Central Cluster │ +│ central-node-a (UI :9001, Akka :9011) │ +│ central-node-b (UI :9002, Akka :9012) │ +└───────────┬─────────────────────────────────────────┘ + │ Akka.NET remoting + gRPC streaming + ├──────────────────┬──────────────────┐ + ▼ ▼ ▼ + Site-A (Test Plant A) Site-B (Test Plant B) Site-C (Test Plant C) + Akka 9021/9022 Akka 9031/9032 Akka 9041/9042 + gRPC 9023/9024 gRPC 9033/9034 gRPC 9043/9044 +``` + +Two-node active/standby everywhere. Central runs the Blazor UI, Template Engine, Deployment Manager, Notification Outbox, Site Call Audit, Audit Log, and Inbound API. Each site runs the Site Runtime, Data Connection Layer, Store-and-Forward, and Site Event Logging, with a per-node gRPC streaming server (port 8083) that central nodes subscribe to for real-time attribute/alarm streams. + +## Nodes + +| Node | Container Name | Host Web | Host Akka | Host gRPC | +|------|----------------|----------|-----------|-----------| +| Traefik LB | `scadalink-traefik` | 9000 | — | — | +| Central A | `scadalink-central-a` | 9001 | 9011 | — | +| Central B | `scadalink-central-b` | 9002 | 9012 | — | +| Site-A A | `scadalink-site-a-a` | — | 9021 | 9023 | +| Site-A B | `scadalink-site-a-b` | — | 9022 | 9024 | +| Site-B A | `scadalink-site-b-a` | — | 9031 | 9033 | +| Site-B B | `scadalink-site-b-b` | — | 9032 | 9034 | +| Site-C A | `scadalink-site-c-a` | — | 9041 | 9043 | +| Site-C B | `scadalink-site-c-b` | — | 9042 | 9044 | + +Port pattern: `90X1`/`90X2` (Akka), `90X3`/`90X4` (gRPC), where X = 0 (central), 2 (site-a), 3 (site-b), 4 (site-c). + +## Sites + +| Site Identifier | Central UI Name | +|-----------------|-----------------| +| `site-a` | Test Plant A | +| `site-b` | Test Plant B | +| `site-c` | Test Plant C | + +## Infrastructure Dependencies + +All from `infra/docker-compose.yml`, attached via the shared `scadalink-net` bridge network: + +| Service | Container | Host Port | Purpose | +|---------|-----------|-----------|---------| +| MS SQL 2022 | `scadalink-mssql` | 1433 | `ScadaLinkConfig`, `ScadaLinkMachineData` | +| LDAP (GLAuth) | `scadalink-ldap` | 3893 | Authentication | +| SMTP (Mailpit) | `scadalink-smtp` | 1025 / 8025 | Notification capture | +| OPC UA | `scadalink-opcua` | 50000 / 8080 | Simulated device tags | +| REST API | `scadalink-restapi` | 5200 | External REST integration testing | + +## Databases + +| Database | Owner | +|----------|-------| +| `ScadaLinkConfig` | Configuration Database (#17) | +| `ScadaLinkMachineData` | Machine data (template/instance runtime data) | + +## Commands + +```bash +# Start infra first (creates scadalink-net, MS SQL, LDAP, SMTP, OPC UA, REST API) +cd infra && docker compose up -d && cd .. + +# Build image + deploy all 8 nodes +bash docker/deploy.sh + +# First-time only: seed test sites with Akka + gRPC addresses +bash docker/seed-sites.sh + +# View logs +docker compose -f docker/docker-compose.yml logs -f +docker logs -f scadalink-central-a + +# Health +curl -s http://localhost:9001/health/ready | python3 -m json.tool +curl -s http://localhost:9002/health/ready | python3 -m json.tool + +# Stop application nodes (preserves SQLite + logs) +bash docker/teardown.sh +``` + +## CLI Access + +The CLI talks to the Traefik LB, which routes to the active central node. + +```bash +dotnet run --project src/ScadaLink.CLI -- \ + --url http://localhost:9000 \ + --username multi-role --password password \ + template list +``` + +Direct node access: `http://localhost:9001` (central-a), `http://localhost:9002` (central-b). + +## Test Users + +All passwords are `password`. See `infra/glauth/config.toml` for the full list. + +| Username | Roles | +|----------|-------| +| `admin` | Admin | +| `designer` | Design | +| `deployer` | Deployment | +| `multi-role` | Admin, Design, Deployment | + +## Notes + +- Reference deployment for the project — when a change ships, it gets validated here first. +- Concurrent with [`docker-cluster-env2`](docker-cluster-env2.md) on the same host; the two stacks share the `scadalink-net` network and `infra/` services but use disjoint host ports (`90XX` vs `91XX`) and databases. +- Detailed setup, failover testing, and build-cache notes live in [`docker/README.md`](../docker/README.md).