From 1be06502c7bf6ff36d726c74ff6557f64ce57c87 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 12 Jun 2026 12:25:13 -0400 Subject: [PATCH] fix(historian): correct AlarmHistorian config-key refs in docs + install (review) --- docs/AlarmHistorian.md | 8 ++++---- docs/Configuration.md | 15 ++++++++++----- docs/DriverLifecycle.md | 4 ++-- docs/ServiceHosting.md | 4 ++-- docs/drivers/Historian.Wonderware.md | 4 ++-- scripts/install/Install-Services.ps1 | 4 ++-- scripts/install/Refresh-Services.ps1 | 2 +- .../Historian/HistorianAdapterActor.cs | 4 ++-- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/AlarmHistorian.md b/docs/AlarmHistorian.md index 4f87098f..5da3a023 100644 --- a/docs/AlarmHistorian.md +++ b/docs/AlarmHistorian.md @@ -196,10 +196,10 @@ When `Enabled` is `false` (the default), `AddAlarmHistorian` registers | `DatabasePath` | string | — | Absolute path to the SQLite queue file. Created on first use (WAL mode). Required when `Enabled`. | | `SharedSecret` | string | — | Shared secret token the sidecar expects on every connection. Required when `Enabled`. | | `BatchSize` | int | `100` | Max rows per drain cycle handed to `IAlarmHistorianWriter.WriteBatchAsync`. | -| `Historian:Wonderware:Host` | string | `localhost` | DNS name or IP of the machine running the historian sidecar. | -| `Historian:Wonderware:Port` | int | `32569` | TCP port the sidecar listens on (`OTOPCUA_HISTORIAN_TCP_PORT`). | -| `Historian:Wonderware:UseTls` | bool | `false` | Wrap the TCP stream in TLS before the Hello handshake. | -| `Historian:Wonderware:ServerCertThumbprint` | string | — | Optional SHA-1 thumbprint to pin the sidecar's TLS server certificate. Leave empty to use normal CA-chain validation. | +| `AlarmHistorian:Host` | string | `localhost` | DNS name or IP of the machine running the historian sidecar. | +| `AlarmHistorian:Port` | int | `32569` | TCP port the sidecar listens on (`OTOPCUA_HISTORIAN_TCP_PORT`). | +| `AlarmHistorian:UseTls` | bool | `false` | Wrap the TCP stream in TLS before the Hello handshake. | +| `AlarmHistorian:ServerCertThumbprint` | string | — | Optional SHA-1 thumbprint to pin the sidecar's TLS server certificate. Leave empty to use normal CA-chain validation. | > Dev and docker-dev deployments leave `Enabled` unset (defaults to `false`) so alarm transitions historize to nowhere unless a historian sidecar is present. diff --git a/docs/Configuration.md b/docs/Configuration.md index 399283cb..ea0f2df0 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -119,9 +119,11 @@ The Galaxy/MxAccess connection settings are **not an `appsettings` section.** Th > The `OTOPCUA_GALAXY_*` environment variables that v1's in-process `Galaxy.Host` consumed **no longer live in this repo** — they moved into the separately-installed mxaccessgw gateway's own config (see the v1 archive pointer in `docs/README.md` and the Galaxy overview at [`docs/drivers/Galaxy.md`](drivers/Galaxy.md)). The only Galaxy connection secret this repo touches is the gateway API key via `ApiKeySecretRef` above. -### Historian config (env-driven sidecar) +### Historian config (TCP sidecar) -The Wonderware Historian runs as a supervised sidecar process whose configuration arrives **entirely through environment variables**, not an `appsettings` section. The sidecar entry point (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs`) reads them at spawn time. See the `OTOPCUA_HISTORIAN_*` rows in the environment-variable table below. The in-process client-side options POCO is `WonderwareHistorianClientOptions` (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/WonderwareHistorianClientOptions.cs`): `PipeName`, `SharedSecret`, `PeerName` (`OtOpcUa`), `ConnectTimeout` (default 10s), `CallTimeout` (default 30s), `ProbeTimeoutSeconds` (`15`). +The Wonderware Historian sidecar (`OtOpcUaWonderwareHistorian`) is an independent Windows service that the OtOpcUa host connects to over TCP. It is **not** spawned as a child process by the host — the two services are started independently (e.g. by NSSM / `sc.exe`). The sidecar entry point (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs`) reads its configuration from environment variables; the OtOpcUa host side reads the `AlarmHistorian` appsettings section. See the `OTOPCUA_HISTORIAN_*` rows in the environment-variable table below. + +The in-process **client-side** options POCO is `WonderwareHistorianClientOptions` (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/WonderwareHistorianClientOptions.cs`), bound from the `AlarmHistorian` section: `Host`, `Port`, `UseTls`, `ServerCertThumbprint`, `SharedSecret`, `ConnectTimeout` (default 10s), `CallTimeout` (default 30s), `ProbeTimeoutSeconds` (`15`). --- @@ -135,7 +137,6 @@ All names are read in this repo's source via `Environment.GetEnvironmentVariable |---|---|---| | `OTOPCUA_ROLES` | `src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs` (`RoleParser.Parse`) | Comma-separated cluster roles for the node (`admin`, `driver`, `dev`). Drives the conditional wiring and the per-role appsettings overlay. Used when `Cluster:Roles` is empty. | | `OTOPCUA_CONFIG_CONNECTION` | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/DesignTimeDbContextFactory.cs` (design-time / `dotnet ef` only) | Read at **design time** by `DesignTimeDbContextFactory.cs` for `dotnet ef` migrations. At **runtime** the server resolves the connection string from `ConnectionStrings:ConfigDb` (env form: `ConnectionStrings__ConfigDb`) via `configuration.GetConnectionString("ConfigDb")` in `ServiceCollectionExtensions.cs` — `OTOPCUA_CONFIG_CONNECTION` appears there only as a hint in an error message, not via `GetEnvironmentVariable`. No credential is embedded in source. | -| `OTOPCUA_ALLOWED_SID` | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs` | SID of the server principal allowed to connect to the historian sidecar's named pipe (passed by the supervisor at spawn). Required — sidecar throws if unset. | | `ASPNETCORE_ENVIRONMENT` | ASP.NET host builder (framework) | Selects `appsettings.{Environment}.json` (e.g. `Development`). | ### Historian sidecar (`OTOPCUA_HISTORIAN_*`) @@ -144,8 +145,12 @@ All read in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.c | Variable | Effect / default | |---|---| -| `OTOPCUA_HISTORIAN_PIPE` | Named-pipe name the sidecar listens on. Required (throws if unset). | -| `OTOPCUA_HISTORIAN_SECRET` | Per-process shared secret verified in the pipe Hello frame. Required (throws if unset). | +| `OTOPCUA_HISTORIAN_TCP_PORT` | TCP port the sidecar listens on. Default `32569`. Corresponds to `AlarmHistorian:Port` on the host side. | +| `OTOPCUA_HISTORIAN_BIND` | TCP bind address for the sidecar. Default `0.0.0.0`. | +| `OTOPCUA_HISTORIAN_TLS_ENABLED` | `true` enables TLS on the sidecar's TCP listener. Default `false`. Corresponds to `AlarmHistorian:UseTls` on the host side. | +| `OTOPCUA_HISTORIAN_TLS_CERT` | PFX file path **or** `LocalMachine\My\` to load the sidecar TLS server certificate from the machine store. | +| `OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD` | Password for a PFX-file certificate. Omit when using a machine-store cert. Never commit a value. | +| `OTOPCUA_HISTORIAN_SECRET` | Per-process shared secret verified in the TCP Hello frame. Required (throws if unset). Corresponds to `AlarmHistorian:SharedSecret` on the host side. | | `OTOPCUA_HISTORIAN_ENABLED` | `true` opens the real Wonderware SDK connection; anything else → pipe-only mode (smoke/IPC tests). Default: not-true → pipe-only. | | `OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED` | `false` disables the alarm-event writer (sidecar rejects `WriteAlarmEvents`). Default `true` (when `ENABLED=true`). | | `OTOPCUA_HISTORIAN_INTEGRATED` | `false` → SQL auth (use `USER`/`PASS`); any other value → integrated security. Default: integrated. | diff --git a/docs/DriverLifecycle.md b/docs/DriverLifecycle.md index e07770b5..3b57d3ae 100644 --- a/docs/DriverLifecycle.md +++ b/docs/DriverLifecycle.md @@ -234,8 +234,8 @@ Implementations: - `WonderwareHistorianClient` ([`Driver.Historian.Wonderware.Client/WonderwareHistorianClient.cs`](../src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/WonderwareHistorianClient.cs)) - — the .NET 10 client that talks to the Wonderware historian sidecar over a - named pipe. It implements both `IHistorianDataSource` (read paths) and + — the .NET 10 client that talks to the Wonderware historian sidecar over + TCP (optional TLS). It implements both `IHistorianDataSource` (read paths) and `IAlarmHistorianWriter` (the alarm-event drain target; see [AlarmHistorian.md](AlarmHistorian.md)). - `HistorianDataSource` diff --git a/docs/ServiceHosting.md b/docs/ServiceHosting.md index 48a0b93c..872d0448 100644 --- a/docs/ServiceHosting.md +++ b/docs/ServiceHosting.md @@ -7,7 +7,7 @@ A production OtOpcUa deployment runs **one binary per node**, plus the optional | Process | Project | Runtime | Platform | Responsibility | |---|---|---|---|---| | **OtOpcUa Host** | `src/Server/ZB.MOM.WW.OtOpcUa.Host` | .NET 10 | AnyCPU | Single fused binary. `OTOPCUA_ROLES` env decides what to mount: `admin` (Blazor + auth + control-plane singletons), `driver` (OPC UA endpoint + per-driver actors), or both. | -| **OtOpcUa Wonderware Historian** *(optional)* | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware` | .NET Framework 4.8 | x64 (64-bit) | Out-of-process sidecar exposing the Wonderware Historian SDK over TCP (optional TLS). Required only when `Historian:Wonderware:Enabled=true`. May run on the same machine or a remote host. | +| **OtOpcUa Wonderware Historian** *(optional)* | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware` | .NET Framework 4.8 | x64 (64-bit) | Out-of-process sidecar exposing the Wonderware Historian SDK over TCP (optional TLS). Required only when `AlarmHistorian:Enabled=true`. May run on the same machine or a remote host. | Galaxy access still uses the separately-installed **mxaccessgw** sidecar (see `docs/v2/Galaxy.ParityRig.md`); the gateway owns the MXAccess COM bitness constraint (its worker is x86 net48). Nothing in the OtOpcUa repo carries that constraint anymore. @@ -70,7 +70,7 @@ Used by Traefik for the active-leader-only routing pattern (see [Architecture-v2 ## OtOpcUa Wonderware Historian (optional) -IPC contract types live in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/`; sidecar TCP server in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Ipc/`. The sidecar listens on TCP port 32569 by default; `Install-Services.ps1 -InstallWonderwareHistorian` adds the Windows Firewall inbound rule. The host and sidecar may run on different machines — configure `Historian:Wonderware:Host` + `Port` (and optionally `UseTls`) on the OtOpcUa host side. See [Historian.Wonderware.md](drivers/Historian.Wonderware.md) for the full transport and security reference. +IPC contract types live in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/`; sidecar TCP server in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Ipc/`. The sidecar listens on TCP port 32569 by default; `Install-Services.ps1 -InstallWonderwareHistorian` adds the Windows Firewall inbound rule. The host and sidecar may run on different machines — configure `AlarmHistorian:Host` + `AlarmHistorian:Port` (and optionally `AlarmHistorian:UseTls`) on the OtOpcUa host side. See [Historian.Wonderware.md](drivers/Historian.Wonderware.md) for the full transport and security reference. ## Install / Uninstall diff --git a/docs/drivers/Historian.Wonderware.md b/docs/drivers/Historian.Wonderware.md index 5ef6c0a2..7c00a5a8 100644 --- a/docs/drivers/Historian.Wonderware.md +++ b/docs/drivers/Historian.Wonderware.md @@ -4,7 +4,7 @@ The Wonderware Historian backend is **not a tag driver** — it has no address space, no `IDriver` lifecycle, and exposes no PLC. It is a **server-side historian sink**: an optional sidecar that gives OtOpcUa read access to AVEVA System Platform (Wonderware) Historian history and a write-back path for alarm -events. It runs only when `Historian:Wonderware:Enabled=true`. +events. It runs only when `AlarmHistorian:Enabled=true`. The host connects to the sidecar over **TCP** (plaintext in dev, optional TLS in prod), so the OtOpcUa host no longer needs to be on the same machine as the @@ -55,7 +55,7 @@ Set `OTOPCUA_HISTORIAN_TLS_ENABLED=true` on the sidecar and supply the server certificate via `OTOPCUA_HISTORIAN_TLS_CERT` (PFX file path, or `LocalMachine\My\` for a cert already in the machine store) and `OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD` if the PFX is password-protected. On the -client/host side set `Historian:Wonderware:UseTls=true`; optionally set +client/host side set `AlarmHistorian:UseTls=true`; optionally set `ServerCertThumbprint` to pin the server certificate's SHA-1 thumbprint instead of relying on normal CA-chain validation. diff --git a/scripts/install/Install-Services.ps1 b/scripts/install/Install-Services.ps1 index d2d2fbb1..817e60bd 100644 --- a/scripts/install/Install-Services.ps1 +++ b/scripts/install/Install-Services.ps1 @@ -84,7 +84,7 @@ param( [int]$HttpPort = 9000, # Wonderware historian sidecar. Optional; gates the OtOpcUaWonderwareHistorian - # service. Secret defaults match the server's Historian:Wonderware appsettings. + # service. Secret defaults match the server's AlarmHistorian appsettings. [switch]$InstallWonderwareHistorian, [string]$HistorianSharedSecret, [string]$HistorianServer = 'localhost', @@ -221,7 +221,7 @@ if ($InstallWonderwareHistorian) { Write-Host " sc.exe start OtOpcUaWonderwareH Write-Host " sc.exe start OtOpcUaHost" if ($InstallWonderwareHistorian) { Write-Host "" - Write-Host "Wonderware historian shared secret (configure into appsettings.json Historian:Wonderware:SharedSecret):" + Write-Host "Wonderware historian shared secret (configure into appsettings.json AlarmHistorian:SharedSecret):" Write-Host " $HistorianSharedSecret" } Write-Host "" diff --git a/scripts/install/Refresh-Services.ps1 b/scripts/install/Refresh-Services.ps1 index 6df55168..cc9ac06f 100644 --- a/scripts/install/Refresh-Services.ps1 +++ b/scripts/install/Refresh-Services.ps1 @@ -147,7 +147,7 @@ if (-not $WhatIf) { if ($missing.Count -gt 0) { $msg = "Wonderware historian sidecar deploy at '$sidecarDir' is INCOMPLETE — missing: " + - "$($missing -join ', '). It would crash-loop with a FileNotFoundException in PipeServer. " + + "$($missing -join ', '). It would crash-loop with a FileNotFoundException in TcpFrameServer. " + "Re-run the sidecar 'dotnet publish' into that folder (Step 4) — never hand-copy a partial build output." throw $msg } diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs index 9c99dfdc..130c984f 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/HistorianAdapterActor.cs @@ -16,9 +16,9 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian; /// Galaxy native alarm bridge, AB CIP ALMD reader) tells s to this /// actor; the actor enqueues them on the sink fire-and-forget. Production deployments register /// against IAlarmHistorianSink; the sink owns the -/// durable queue + drain-to-Wonderware-pipe loop. The actor here owns nothing operational beyond +/// durable queue + drain-to-Wonderware-TCP-channel loop. The actor here owns nothing operational beyond /// the message contract — its job is to keep the engine actors on Akka's mailbox without blocking -/// them on disk I/O or pipe handshakes. +/// them on disk I/O or TCP channel handshakes. /// /// Query queue depth + drain health via . ///