docs(btdb): register BTDB Manager in the Overview docker registry; record the local rig

- ZB.MOM.WW.Overview appsettings.Docker.json: application "BTDB Manager", instance manager at
  http://zb-btdb-manager:9877 (HasActiveRole false); docker/README.md fleet row.
- CLAUDE.md BTDB row and sister-project-history/btdb.md §7/§9: docker/ layout, the
  dacpac + SQL CLR side-project build, scadabridge-mssql as the BT host.

Claude-Session: https://claude.ai/code/session_015dYqtaTxgf6NK2KuwriMSg
This commit is contained in:
Joseph Doherty
2026-09-04 06:14:22 -04:00
parent 370a9eea96
commit 54f111e8b1
4 changed files with 35 additions and 3 deletions
+16 -1
View File
@@ -110,7 +110,13 @@ dotnet run --project src/ZB.MOM.WW.BTDB.Manager # appsettings.Development.json
pwsh deploy/publish.ps1 # self-contained win-x64 → deploy/out/ (+ NSSM install scripts)
```
On `master` nothing builds on macOS (SSDT/CLR need Windows MSBuild, the MVC Manager needs Telerik + `System.DirectoryServices`). On the branch the app and its tests build and run on macOS; only `Database.sqlproj` (kept alone in `BTDB.sln`) still needs Windows.
```bash
# Local Docker rig (2026-09-04, any OS) — see §9
docker/deploy-db.sh # BT on scadabridge-mssql from Database/ (dacpac + SQL CLR); --recreate drops it first
docker/deploy.sh # publish FDD on the host → zb-btdb-manager:local, up on :9877 (glauth login enabled)
```
On `master` nothing builds on macOS (SSDT/CLR need Windows MSBuild, the MVC Manager needs Telerik + `System.DirectoryServices`). On the branch the app and its tests build and run on macOS; only `Database.sqlproj` (kept alone in `BTDB.sln`) still needs Windows — but §9's side projects build the same sources into a dacpac and a CLR assembly anywhere, for the local rig.
## 8. Blazor conversion — branch `feat/manager-blazor` (2026-09-04)
@@ -121,3 +127,12 @@ Plan: `btdb/docs/plans/manager_blazor_conversion.md` (+ `.tasks.json` tracker be
- **Behavioural deltas vs the MVC app worth knowing:** config import derives the group set from the selected machines' memberships (no separate group pick) and has a dry-run preview (`MachineConfigImportPreview.sql`: same MERGEs, `OUTPUT` into a table variable, always `ROLLBACK`); batch-edit timestamps are 7-fraction UTC strings validated on blur (no Kendo mask); the three audit reports are HTML tables with a print stylesheet instead of Telerik PDFs; `MachineAlarm` DTO property order/types were wrong in the MVC app (alarm import always threw) and are fixed against `MachineAlarmType`; every `Save*` runs in a transaction and `THROW 5001x` maps to `BtdbSaveException`.
- **Cut-over material:** `docs/operations/cut-over.md` (parity checklist per route + THROW paths + audit-username check, secrets seed via the `secret` console + rotation, NSSM side-by-side on `:9877`, port swap to `:9876`, Overview registry entry), `deploy/` (`publish.ps1`, `install-service.bat` / `uninstall-service.bat` for NSSM service `ZB.MOM.WW.BTDB.Manager`), `tools/smoke-new-queries.sql` (runs the six never-executed queries read-only against DEV with sqlcmd). Still open after cut-over: Overview lacks the mockup's per-machine "open > 24 h" rows; Ldap `Server`/`SearchBase`/`ServiceAccountDn` are blank in `appsettings.json` by design (per-environment).
## 9. Local Docker rig — `docker/` (2026-09-04)
Mirrors the HistorianGateway / Overview pattern: publish framework-dependent on the host (Gitea feed creds), copy into `aspnet:10.0`, never restore in Docker. Compose project `zb-btdb`, container `zb-btdb-manager`, image `zb-btdb-manager:local`, port `:9877` (same as the DEV side-by-side port, so `appsettings.json`'s Kestrel section is the single binding authority; the Dockerfile clears `ASPNETCORE_URLS`/`ASPNETCORE_HTTP_PORTS`). `ASPNETCORE_ENVIRONMENT=Production`; all environment-specific values are compose env vars, dev-only and committed under the family's rig exception.
- **Database:** no MSSQL of its own — joins the external `scadabridge-net` and uses ScadaBridge's `scadabridge-mssql` (the instance that already carries `ScadaBridgeConfig` + `ZbSecretsHub`). `docker/deploy-db.sh`: (1) `docker/db/BT.Clr.csproj` compiles `Database/**/*.cs` to `Database.dll` (net472 via the auto-referenced reference assemblies — builds on macOS); (2) `docker/db/BT.Local.sqlproj` (SDK-style `Microsoft.Build.Sql/2.2.0`, `EnableDefaultSqlItems=false`, globs the four `Database/` folders, Sql130 model, suppresses the legacy build's own SQL71562/71558/71502) → `BT.Local.dacpac`; (3) `db/setup.sql` — `BT` + login `btdb_app` (db_owner); (4) `db/clr-objects.sql` — `clr enabled` on the instance, `sp_add_trusted_assembly` by SHA2-512 (strict security stays on), `CREATE ASSEMBLY [Database] … SAFE` from the bytes streamed as 2000-char `0x` chunks (a single 57 KB literal gets split by sqlcmd), then `StringJoin` aggregate, `StringCompare`/`CheckTriggered`/`WhenTriggered`/`WhenCleared`/`GetAggregateValues`, `GetTSV`/`GetMachineBatchTSV`/`GetCycleTSV`/`GetLinkedBatchTSV` with SSDT's type mapping; (5) `sqlpackage /Action:Publish` from a one-shot container (`db/Dockerfile.sqlpackage`, SDK image + the dotnet tool — the host's 10.0.5 runtime could not run the tool's 10.0.11 pin) with `DropObjectsNotInSource=false`, `BlockOnPossibleDataLoss=false`, `AllowIncompatiblePlatform=true`; (6) smoke. Result on the rig: 23 tables, 37 T-SQL procs + 1 TVF, 10 CLR objects, `Database` assembly `SAFE_ACCESS`. The script diffs the legacy `<Build Include>` list against the folders and warns on drift (found: `ElmaData_Nitric.sql` + `Elma_Bulk_Query_Nitric.sql` were on disk but not in `Database.sqlproj` — added). `ScadaBridge/infra/teardown.sh`/`reseed.sh` drop the volume and BT with it; re-run `deploy-db.sh`.
- **Why not convert `Database.sqlproj` itself:** `Microsoft.Build.Sql` does not compile SQL CLR, and the Manager depends on `dbo.StringJoin` (batch search) and `dbo.StringCompare` (machine saves). The legacy SSDT project stays the artifact of record; the two side projects reference the same files, nothing is copied.
- **Login:** enabled, `host.docker.internal:3893` + `FallbackServers` `:3894` (glauth-redundant), bind `cn=serviceaccount,dc=zb,dc=local`; `GroupToRole` `SCADA-Admins→Administrator`, `SCADA-Designers→Operator`, `SCADA-Viewers→Viewer`. Verified: `admin`/`designer` sign in, `gw-viewer` refused (fail closed). Literal `ConnectionStrings__BT` / LDAP password env vars override the `${secret:}` refs, so the `/data/btdb-secrets.db` store stays empty and no master key is set (confirmed to boot without one). The two config-import sources point at the local BT.
- **Health / Overview:** `/healthz` 200, `/health/ready` 200 with the `sql` entry (503 when BT is down), `/health/active` 200, `/metrics`. Compose `healthcheck` = bash `/dev/tcp` GET of `/healthz` (liveness on purpose). Registered in `ZB.MOM.WW.Overview/src/…/appsettings.Docker.json` as application **BTDB Manager**, instance `manager`, `BaseUrl http://zb-btdb-manager:9877`, `HasActiveRole false`; the containerised Overview was republished and shows the tile Up with `sql` healthy (the dashboard already sits on `scadabridge-net`, so no extra network).
+1 -1
View File
@@ -40,7 +40,7 @@ own `CLAUDE.md` for the full picture. See [Refreshing this index](#refreshing-th
| **MxAccessGateway** (`mxaccessgw`) | `~/Desktop/MxAccessGateway` | .NET 10 gateway (x64) + .NET 4.8 worker (**x86**), gRPC | `gitea.dohertylan.com/dohertj2/mxaccessgw` | gRPC gateway giving modern clients full MXAccess parity without loading 32-bit COM. Two-process: gateway (ASP.NET Core gRPC + Blazor dashboard) + per-session x86 worker that owns the MXAccess COM STA. **OtOpcUa depends on this.** |
| **ScadaBridge** | `~/Desktop/ScadaBridge` | .NET 10, Akka.NET, Docker | `gitea.dohertylan.com/dohertj2/ScadaBridge` | Full implementation of the distributed SCADA platform — hub-and-spoke (1 central cluster + N site clusters). Projects prefixed `ZB.MOM.WW.ScadaBridge.*`; solution `ZB.MOM.WW.ScadaBridge.slnx`. Ships `src/`, `tests/`, `docker/` topology, and the design docs that are the spec. **Inbound API keys live on the shared SQL Server store on a central pair** (design 2026-09-02, ScadaBridge register row 108): ScadaBridge is Auth's first consumer of the new `ZB.MOM.WW.Auth.ApiKeys.SqlServer` package at `0.3.0`, pointing both central nodes at one `zbauth` schema in the configuration database instead of each node's own per-node SQLite file. |
| **HistorianGateway** | `~/Desktop/HistorianGateway` | .NET 10 x64, gRPC, Blazor | `gitea.dohertylan.com/dohertj2/historiangw` | Single-process gRPC sidecar exposing (1) a full read/write API to the AVEVA Historian (5 gRPC services; 15 retrieval modes; historical/backfill writes; tag-config lifecycle; SQL live-value path; store-forward + redundancy resilience; all default-disabled) and (2) read-only Galaxy object-hierarchy browse via the shared `ZB.MOM.WW.GalaxyRepository` lib. No COM, no x86 worker. **Owns `AVEVA.Historian.Client`** at `histsdk/` (ordinary owned source, edited in place — the re-vendor model is retired). Dev: dashboard `:5220` (HTTP/1.1) + gRPC h2c `:5221`; production: one `Kestrel:Endpoints:Https` with `Protocols: Http1AndHttp2` (ALPN). Default-on leased session pools amortize the historian handshake (v6 pool + a separate v8 event-session pool). Client lib `ZB.MOM.WW.HistorianGateway.Client` + Contracts are on the Gitea feed at `0.3.0`; **OtOpcUa is the first consumer** (its sole historian backend since PR #423); ScadaBridge has no coupling. Known 2023 R2 server-side gates: native event reads return 0 rows (C2, won't-fix — SQL-path `ReadEvents` via `Runtime.dbo.Events` is the shipped workaround), `Int1`/`UInt1` analog-tag creation is degenerate (OtOpcUa maps Boolean → `Int2`), string historical writes non-convergent (follow-ups #18–#25). Only remaining archreview item: the secure-by-default TLS flip, deferred to 1.0.0. **Full change history (ArchReview cycles 1–2, pending.md items, PR list): `.claude/skills/sister-project-history/historiangateway.md`.** |
| **BTDB** (Batch Tracker) | `~/Desktop/btdb` (git checkout of `master` @ `d147c10e`, 2023-07-26, since 2026-09-03; local branch `feat/machine-batch-transfer`, **not pushed**) | SQL Server SSDT project + SQL CLR (net472); manager = **Blazor Web App net10.0** on the family libs (Auth/Theme/Health/Telemetry/Configuration/Secrets) on branch `feat/manager-blazor` (worktree `~/.config/superpowers/worktrees/BTDB/feat-manager-blazor`, uncommitted 2026-09-04; `master` still has the EOL net6.0 MVC app, Windows service `:9876`) | **`ZimmerBiomet/BTDB`** on the corporate Gitea `wonder-app-vd03:3000` (not on `gitea.dohertylan.com`) | The `BT` SQL Server database that is the plant's **batch / cycle / work-order system of record for data collection**. Galaxy machine objects write to it through `StartMachineBatch` / `StartMachineCycle` / `End*` / `AddMachineBatchWorkOrder` / `AddOtherData*` (auto-closes the prior batch/cycle unless `MultipleBatch`/`MultipleCycle`; all `GETUTCDATE()`); reports pull per-batch time-series **out of the AVEVA Historian through CLR sprocs** (`OPENQUERY(INSQL, … WideHistory …)` + `Runtime.dbo.*`), so **BT must be co-tenant with the Historian's `Runtime` DB** (`wonder-sql-*`, not the app-tier SQL). MESAPI and **ScadaBridge's inbound API** (`IpsenMESMoveIn`, MES alarm-status; db-connection `BTDB`) resolve `SAPID → Machine.Code` from `dbo.Machine`; ScadaBridge site scripts *can* write via `Database.Connection`, but no checked-in ScadaBridge script calls a BT sproc and the rig seeds no `BT`. `BTDBManager` = LDAP-gated machine/group config, audited batch edits, cross-environment config import — **re-platformed to Blazor/.NET 10 on `feat/manager-blazor` (2026-09-04, phases 1–3 done, 334 tests; phase-4 cut-over needs the DEV box: runbook `docs/operations/cut-over.md`, NSSM scripts under `deploy/`)**; the MVC app, Telerik reports and the mis-named `install.bat` are deleted on that branch. The DARS sprint-1 schema work (`MachineBatchTransfer`, `ContainerID`/`MoveOutTimestamp` on `MachineBatchWO`, `GetLinkedBatchTSV`) **was migrated from the unversioned `~/Desktop/DARS/WW_ARCH/BatchTracker_DB/` fork onto btdb branch `feat/machine-batch-transfer`** on 2026-09-03 (plus the plan under `btdb/docs/plans/`); the fork is now superseded and the branch awaits push/review. Checked-in plaintext SQL + service-account credentials (do not reproduce). **Full schema/contract notes, family edges and the 2026-09-03 gap analysis: `.claude/skills/sister-project-history/btdb.md`.** |
| **BTDB** (Batch Tracker) | `~/Desktop/btdb` (git checkout of `master` @ `d147c10e`, 2023-07-26, since 2026-09-03; local branch `feat/machine-batch-transfer`, **not pushed**) | SQL Server SSDT project + SQL CLR (net472); manager = **Blazor Web App net10.0** on the family libs (Auth/Theme/Health/Telemetry/Configuration/Secrets) on branch `feat/manager-blazor` (worktree `~/.config/superpowers/worktrees/BTDB/feat-manager-blazor`, uncommitted 2026-09-04; `master` still has the EOL net6.0 MVC app, Windows service `:9876`) | **`ZimmerBiomet/BTDB`** on the corporate Gitea `wonder-app-vd03:3000` (not on `gitea.dohertylan.com`) | The `BT` SQL Server database that is the plant's **batch / cycle / work-order system of record for data collection**. Galaxy machine objects write to it through `StartMachineBatch` / `StartMachineCycle` / `End*` / `AddMachineBatchWorkOrder` / `AddOtherData*` (auto-closes the prior batch/cycle unless `MultipleBatch`/`MultipleCycle`; all `GETUTCDATE()`); reports pull per-batch time-series **out of the AVEVA Historian through CLR sprocs** (`OPENQUERY(INSQL, … WideHistory …)` + `Runtime.dbo.*`), so **BT must be co-tenant with the Historian's `Runtime` DB** (`wonder-sql-*`, not the app-tier SQL). MESAPI and **ScadaBridge's inbound API** (`IpsenMESMoveIn`, MES alarm-status; db-connection `BTDB`) resolve `SAPID → Machine.Code` from `dbo.Machine`; ScadaBridge site scripts *can* write via `Database.Connection`, but no checked-in ScadaBridge script calls a BT sproc and the rig seeds no `BT`. `BTDBManager` = LDAP-gated machine/group config, audited batch edits, cross-environment config import — **re-platformed to Blazor/.NET 10 on `feat/manager-blazor` (2026-09-04, phases 1–3 done, 334 tests; phase-4 cut-over needs the DEV box: runbook `docs/operations/cut-over.md`, NSSM scripts under `deploy/`)**; the MVC app, Telerik reports and the mis-named `install.bat` are deleted on that branch. The DARS sprint-1 schema work (`MachineBatchTransfer`, `ContainerID`/`MoveOutTimestamp` on `MachineBatchWO`, `GetLinkedBatchTSV`) **was migrated from the unversioned `~/Desktop/DARS/WW_ARCH/BatchTracker_DB/` fork onto btdb branch `feat/machine-batch-transfer`** on 2026-09-03 (plus the plan under `btdb/docs/plans/`); the fork is now superseded and the branch awaits push/review. Checked-in plaintext SQL + service-account credentials (do not reproduce). **Full schema/contract notes, family edges and the 2026-09-03 gap analysis: `.claude/skills/sister-project-history/btdb.md`.** **Local Docker rig (2026-09-04):** `btdb/docker/` — runtime-only image (HistorianGateway pattern) on `:9877` joined to `scadabridge-net`, login via the local glauth pair, registered in Overview's `appsettings.Docker.json` as **BTDB Manager**; `docker/deploy-db.sh` builds `Database/` cross-platform (SDK-style dacpac + the SQL CLR assembly as net472 via two side projects under `docker/db/`) and publishes `BT` into ScadaBridge's `scadabridge-mssql` container (login `btdb_app`; `clr enabled` flipped on that instance). |
## Cross-project relationships
+4 -1
View File
@@ -25,11 +25,14 @@ The dev rigs publish only some node ports to the host:
| OtOpcUa central-1/2 | container `:9000` | **no** — only Traefik `:9200`, load-balanced across the pair |
| OtOpcUa site-\* (4) | container `:8080` | **no** — not published |
| HistorianGateway | container `:5220` | yes — `localhost:5220` |
| BTDB Manager | container `:9877` | yes — `localhost:9877` |
| MxAccessGateway | windev `:5130` | yes, with the VPN up |
So a host-run dashboard can only see part of the fleet. This stack joins the three rig networks
(`otopcua-dev_default`, `scadabridge-net`, `zb-historiangw_default`, all declared `external`) and
addresses every node by container DNS name, which is how it sees all of them.
addresses every node by container DNS name, which is how it sees all of them. BTDB Manager
(`~/Desktop/BTDB/docker`) sits on `scadabridge-net` too, because its BT database lives on
ScadaBridge's infra MSSQL container, so no fourth network is needed for it.
Ports above were established by probing the running rigs on 2026-07-24, **not** read off the
compose files — the two disagree. In particular OtOpcUa's site nodes serve health on `:8080`, not
@@ -25,6 +25,8 @@
// LocalDb sync re-bind the primary HTTP port)
// ScadaBridge central :5000
// ScadaBridge site :8084 (MetricsPort; MapZbHealth lands here from the site-health work)
// BTDB Manager :9877 (BTDB/docker; single instance, joins scadabridge-net for BT;
// /health/ready is 503 whenever that database is unreachable)
//
// ManagementUrl values stay host-relative (localhost:*) on purpose: they are followed by the
// OPERATOR'S BROWSER, not by this container, so container DNS names would be dead links.
@@ -146,6 +148,18 @@
}
]
},
{
"Name": "BTDB Manager",
"ManagementLabel": "Manager",
"Instances": [
{
"Name": "manager",
"BaseUrl": "http://zb-btdb-manager:9877",
"ManagementUrl": "http://localhost:9877/",
"HasActiveRole": false
}
]
},
{
"Name": "MxAccessGateway",
"ManagementLabel": "Dashboard",