Fifth PR of the alarms-over-gateway epic (docs/plans/alarms-over-gateway.md). Depends on PR C.1 (AahClientManagedAlarmEventWriter), already merged. Today HistorianFrameHandler is constructed at Program.cs line 57 without an alarmWriter, so every WriteAlarmEvents frame replies "Sidecar not configured with an alarm-event writer" and the lmxopcua side keeps the row queued. C.2 wires a real writer behind a new OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED toggle. - Program.BuildAlarmWriter — gated on the env var (default true, fail-open under accidental misconfiguration). Constructs an AahClientManagedAlarmEventWriter wrapping a SdkAlarmHistorianWriteBackend with the same connection config the read path uses. - Install-Services.ps1 — appends OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true to the OtOpcUaWonderwareHistorian service env block when the sidecar is installed. Read-only deployments flip it to false at service-config edit time without re-installing. - HistorianFrameHandler signature already accepts IAlarmEventWriter? — supplying non-null at line 57 lights up the WriteAlarmEvents reply path that's been dormant since PR 3.3. Until PR D.1 pins the live aahClientManaged entry point, the SdkAlarmHistorianWriteBackend reports RetryPlease for every event with a structured diagnostic. The lmxopcua-side SqliteStoreAndForwardSink retains queued events; same effective behaviour as today's NullAlarmHistorianSink fallback but with visible diagnostics rather than silent discard. Tests: - 6 BuildAlarmWriter env-var cases — unset / true / false / unrecognized → default-on / capitalization variants. - Full sidecar test suite: 56 passed (was 48; 8 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
147 lines
6.3 KiB
PowerShell
147 lines
6.3 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Registers the v2 Windows services on a node: OtOpcUa (main server, net10) and
|
|
optionally OtOpcUaWonderwareHistorian (Wonderware historian sidecar).
|
|
|
|
.DESCRIPTION
|
|
PR 7.2 retired the legacy out-of-process OtOpcUaGalaxyHost service alongside the
|
|
GalaxyProxyDriver / GalaxyHost / GalaxyShared projects. Galaxy access now flows
|
|
through the in-process GalaxyDriver talking gRPC to a separately-installed
|
|
mxaccessgw. The mxaccessgw server runs out of its own repo
|
|
(`c:\Users\dohertj2\Desktop\mxaccessgw\`) — see
|
|
`docs/v2/Galaxy.ParityRig.md` for the gw setup recipe.
|
|
|
|
.PARAMETER InstallRoot
|
|
Where the binaries live (typically C:\Program Files\OtOpcUa).
|
|
|
|
.PARAMETER ServiceAccount
|
|
Service account SID or DOMAIN\name. The OtOpcUa service runs under this account.
|
|
|
|
.PARAMETER InstallWonderwareHistorian
|
|
Gate the OtOpcUaWonderwareHistorian sidecar install. Off by default; set when
|
|
the deployment uses the Wonderware historian for history reads + alarm-event
|
|
persistence.
|
|
|
|
.PARAMETER HistorianSharedSecret
|
|
Per-process secret passed to the Historian sidecar via env var. Generated
|
|
freshly per install when not supplied.
|
|
|
|
.EXAMPLE
|
|
.\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' -ServiceAccount 'OTOPCUA\svc-otopcua'
|
|
|
|
.EXAMPLE
|
|
.\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' -ServiceAccount 'OTOPCUA\svc-otopcua' `
|
|
-InstallWonderwareHistorian
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)] [string]$InstallRoot,
|
|
[Parameter(Mandatory)] [string]$ServiceAccount,
|
|
|
|
# PR 3.W — Wonderware historian sidecar. Optional; gates the
|
|
# OtOpcUaWonderwareHistorian service. Secret + pipe defaults match the server's
|
|
# Historian:Wonderware appsettings block.
|
|
[switch]$InstallWonderwareHistorian,
|
|
[string]$HistorianSharedSecret,
|
|
[string]$HistorianPipeName = 'OtOpcUaWonderwareHistorian',
|
|
[string]$HistorianServer = 'localhost',
|
|
[int]$HistorianPort = 32568,
|
|
[string[]]$AvevaServiceDependencies = @('NmxSvc', 'aaBootstrap', 'aaGR')
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
if (-not (Test-Path "$InstallRoot\OtOpcUa.Server.exe")) {
|
|
Write-Error "OtOpcUa.Server.exe not found at $InstallRoot — copy the publish output first"
|
|
exit 1
|
|
}
|
|
|
|
# Generate fresh shared secrets per install if not supplied.
|
|
function New-SharedSecret {
|
|
$bytes = New-Object byte[] 32
|
|
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
|
|
return [Convert]::ToBase64String($bytes)
|
|
}
|
|
if ($InstallWonderwareHistorian -and -not $HistorianSharedSecret) { $HistorianSharedSecret = New-SharedSecret }
|
|
|
|
if ($InstallWonderwareHistorian -and -not (Test-Path "$InstallRoot\WonderwareHistorian\OtOpcUa.Driver.Historian.Wonderware.exe")) {
|
|
Write-Error "OtOpcUa.Driver.Historian.Wonderware.exe not found at $InstallRoot\WonderwareHistorian — copy the publish output first"
|
|
exit 1
|
|
}
|
|
|
|
# Resolve the SID — the IPC ACL needs the SID, not the down-level name.
|
|
$sid = if ($ServiceAccount.StartsWith('S-1-')) {
|
|
$ServiceAccount
|
|
} else {
|
|
(New-Object System.Security.Principal.NTAccount $ServiceAccount).Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
}
|
|
|
|
# --- Install OtOpcUaWonderwareHistorian (PR 3.W) — separate sidecar that exposes the
|
|
# Wonderware Historian SDK via a named-pipe protocol consumed by the .NET 10 server.
|
|
# Optional: only installed when -InstallWonderwareHistorian is supplied. Depends on the
|
|
# hard AVEVA services that host the historian SDK runtime path.
|
|
$historianDepend = $null
|
|
if ($InstallWonderwareHistorian) {
|
|
$historianEnv = @(
|
|
"OTOPCUA_HISTORIAN_PIPE=$HistorianPipeName"
|
|
"OTOPCUA_ALLOWED_SID=$sid"
|
|
"OTOPCUA_HISTORIAN_SECRET=$HistorianSharedSecret"
|
|
"OTOPCUA_HISTORIAN_ENABLED=true"
|
|
# Default-on when the historian sidecar is installed; flip to false for a
|
|
# read-only deployment that still loads aahClientManaged for reads but
|
|
# rejects WriteAlarmEvents frames.
|
|
"OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true"
|
|
"OTOPCUA_HISTORIAN_SERVER=$HistorianServer"
|
|
"OTOPCUA_HISTORIAN_PORT=$HistorianPort"
|
|
) -join "`0"
|
|
$historianEnv += "`0`0"
|
|
|
|
Write-Host "Installing OtOpcUaWonderwareHistorian..."
|
|
& sc.exe create OtOpcUaWonderwareHistorian binPath= "`"$InstallRoot\WonderwareHistorian\OtOpcUa.Driver.Historian.Wonderware.exe`"" `
|
|
DisplayName= 'OtOpcUa Wonderware Historian Sidecar (out-of-process aahClient)' `
|
|
start= auto `
|
|
depend= ($AvevaServiceDependencies -join '/') `
|
|
obj= $ServiceAccount | Out-Null
|
|
& sc.exe config OtOpcUaWonderwareHistorian start= delayed-auto | Out-Null
|
|
|
|
$svcKey = "HKLM:\SYSTEM\CurrentControlSet\Services\OtOpcUaWonderwareHistorian"
|
|
$envValue = $historianEnv.Split("`0") | Where-Object { $_ -ne '' }
|
|
Set-ItemProperty -Path $svcKey -Name 'Environment' -Type MultiString -Value $envValue
|
|
|
|
$historianDepend = 'OtOpcUaWonderwareHistorian'
|
|
}
|
|
|
|
# --- Install OtOpcUa. Galaxy access flows through GalaxyDriver → mxaccessgw (gRPC),
|
|
# so OtOpcUa no longer depends on a sibling service for Galaxy connectivity. The
|
|
# mxaccessgw is installed separately. When the Wonderware sidecar is installed,
|
|
# depend on it for startup ordering.
|
|
$otOpcUaDepends = @()
|
|
if ($historianDepend) { $otOpcUaDepends += $historianDepend }
|
|
|
|
Write-Host "Installing OtOpcUa..."
|
|
$createArgs = @(
|
|
'create', 'OtOpcUa',
|
|
'binPath=', "`"$InstallRoot\OtOpcUa.Server.exe`"",
|
|
'DisplayName=', 'OtOpcUa Server',
|
|
'start=', 'auto',
|
|
'obj=', $ServiceAccount
|
|
)
|
|
if ($otOpcUaDepends.Count -gt 0) {
|
|
$createArgs += @('depend=', ($otOpcUaDepends -join '/'))
|
|
}
|
|
& sc.exe @createArgs | Out-Null
|
|
|
|
Write-Host ""
|
|
Write-Host "Installed. Start with:"
|
|
if ($InstallWonderwareHistorian) { Write-Host " sc.exe start OtOpcUaWonderwareHistorian" }
|
|
Write-Host " sc.exe start OtOpcUa"
|
|
if ($InstallWonderwareHistorian) {
|
|
Write-Host ""
|
|
Write-Host "Wonderware historian shared secret (configure into appsettings.json Historian:Wonderware:SharedSecret):"
|
|
Write-Host " $HistorianSharedSecret"
|
|
}
|
|
Write-Host ""
|
|
Write-Host "NOTE: Galaxy access flows through mxaccessgw — install + run that separately"
|
|
Write-Host " per docs/v2/Galaxy.ParityRig.md. OtOpcUa connects via the Galaxy.Gateway"
|
|
Write-Host " section of appsettings.json (default endpoint http://localhost:5120)."
|