1be06502c7
v2-ci / build (push) Failing after 43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
231 lines
10 KiB
PowerShell
231 lines
10 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Registers the v2 Windows service on a node: OtOpcUaHost (fused binary, .NET 10)
|
|
and optionally OtOpcUaWonderwareHistorian (Wonderware historian sidecar, net48 x86
|
|
— communicates over TCP, optionally TLS, instead of a Windows named pipe).
|
|
|
|
.DESCRIPTION
|
|
v2 consolidates the legacy OtOpcUa + OtOpcUaAdmin services into a single role-gated
|
|
OtOpcUaHost binary. The -Roles parameter sets the OTOPCUA_ROLES service env so
|
|
Program.cs decides what to mount (admin / driver / both). The Wonderware historian
|
|
sidecar is installed with -InstallWonderwareHistorian; it listens on a TCP port
|
|
(default 32569, configurable via -HistorianTcpPort) protected by the shared secret.
|
|
Optional TLS (-HistorianUseTls) is recommended for production cross-host deployments.
|
|
|
|
Galaxy access flows through the mxaccessgw sibling repo (separate service); see
|
|
docs/v2/Galaxy.ParityRig.md for the gateway setup.
|
|
|
|
.PARAMETER InstallRoot
|
|
Where the binaries live (typically C:\Program Files\OtOpcUa). The OtOpcUaHost
|
|
service runs OtOpcUa.Host.exe from this directory; publish the Host project there
|
|
with `dotnet publish -c Release -r win-x64 --self-contained` first.
|
|
|
|
.PARAMETER ServiceAccount
|
|
Service account SID or DOMAIN\name. The OtOpcUaHost service runs under this account.
|
|
|
|
.PARAMETER Roles
|
|
Comma-separated cluster roles for this node. One of:
|
|
- "admin,driver" — single-node dev or all-in-one production node
|
|
- "admin" — admin-only HA pair member (Blazor + control-plane singletons)
|
|
- "driver" — driver-only node (OPC UA endpoint + per-node actors)
|
|
Written to the service env as OTOPCUA_ROLES.
|
|
|
|
.PARAMETER HttpPort
|
|
HTTP port for the AdminUI + auth endpoints. Default 9000. Written as ASPNETCORE_URLS.
|
|
Ignored on driver-only nodes (no Blazor surface).
|
|
|
|
.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.
|
|
|
|
.PARAMETER HistorianTcpPort
|
|
TCP port the sidecar listens on (OTOPCUA_HISTORIAN_TCP_PORT). Default 32569. A
|
|
matching Windows Firewall inbound rule is created automatically.
|
|
|
|
.PARAMETER HistorianBind
|
|
IP address the sidecar binds to (OTOPCUA_HISTORIAN_BIND). Default 0.0.0.0
|
|
(all interfaces). Set to 127.0.0.1 for loopback-only on same-host deployments.
|
|
|
|
.PARAMETER HistorianUseTls
|
|
When set, enables TLS on the sidecar TCP listener. Supply -HistorianTlsCert with
|
|
a certificate path or store thumbprint; recommended for cross-host production use.
|
|
|
|
.PARAMETER HistorianTlsCert
|
|
Certificate source for TLS. Either:
|
|
- Absolute path to a .pfx file (e.g. C:\ProgramData\OtOpcUa\pki\historian.pfx),
|
|
exported with MachineKeySet so the service account can read the private key; or
|
|
- A LocalMachine\My store thumbprint (40-hex-char string, no spaces).
|
|
Do NOT embed a cert path or thumbprint in automation scripts for unattended installs;
|
|
pass it at call time or from a secrets vault.
|
|
|
|
.PARAMETER HistorianTlsCertPassword
|
|
Password for the .pfx file (if using a pfx path). Leave empty when using a store
|
|
thumbprint. Never store passwords in scripts or source control.
|
|
|
|
.EXAMPLE
|
|
.\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' `
|
|
-ServiceAccount 'OTOPCUA\svc-otopcua' -Roles 'admin,driver'
|
|
|
|
.EXAMPLE
|
|
.\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' `
|
|
-ServiceAccount 'OTOPCUA\svc-otopcua' -Roles 'driver' `
|
|
-InstallWonderwareHistorian
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)] [string]$InstallRoot,
|
|
[Parameter(Mandatory)] [string]$ServiceAccount,
|
|
[Parameter(Mandatory)] [ValidateSet('admin', 'driver', 'admin,driver', 'driver,admin')]
|
|
[string]$Roles,
|
|
[int]$HttpPort = 9000,
|
|
|
|
# Wonderware historian sidecar. Optional; gates the OtOpcUaWonderwareHistorian
|
|
# service. Secret defaults match the server's AlarmHistorian appsettings.
|
|
[switch]$InstallWonderwareHistorian,
|
|
[string]$HistorianSharedSecret,
|
|
[string]$HistorianServer = 'localhost',
|
|
[int]$HistorianPort = 32568, # SDK port (OTOPCUA_HISTORIAN_PORT) — Wonderware InSQL connection
|
|
[int]$HistorianTcpPort = 32569, # TCP listen port for the IPC channel (OTOPCUA_HISTORIAN_TCP_PORT)
|
|
[string]$HistorianBind = '0.0.0.0',
|
|
[switch]$HistorianUseTls,
|
|
[string]$HistorianTlsCert,
|
|
[string]$HistorianTlsCertPassword,
|
|
[string[]]$AvevaServiceDependencies = @('NmxSvc', 'aaBootstrap', 'aaGR')
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
if (-not (Test-Path "$InstallRoot\OtOpcUa.Host.exe")) {
|
|
Write-Error "OtOpcUa.Host.exe not found at $InstallRoot — copy the publish output first"
|
|
exit 1
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# --- OtOpcUaWonderwareHistorian sidecar (optional, TCP transport) -----------
|
|
$historianDepend = $null
|
|
if ($InstallWonderwareHistorian) {
|
|
# Build the TCP/TLS env block.
|
|
# The Windows named-pipe transport and OTOPCUA_ALLOWED_SID have been retired;
|
|
# the sidecar now listens on a TCP port protected by the shared secret.
|
|
# TLS is optional but strongly recommended for cross-host production deployments.
|
|
#
|
|
# Cert provisioning for prod (when -HistorianUseTls):
|
|
# Option A — pfx file: export with -KeyExportPolicy Exportable -KeyStorageFlags MachineKeySet;
|
|
# place at e.g. C:\ProgramData\OtOpcUa\pki\historian.pfx;
|
|
# ACL the file so the service account has Read access.
|
|
# Option B — store thumbprint: import into LocalMachine\My; grant the service account
|
|
# Read on the private key via certlm.msc → Manage Private Keys.
|
|
# Either value is passed via -HistorianTlsCert at install time; NEVER hard-code it here.
|
|
$historianEnv = @(
|
|
"OTOPCUA_HISTORIAN_SECRET=$HistorianSharedSecret"
|
|
"OTOPCUA_HISTORIAN_ENABLED=true"
|
|
"OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true"
|
|
"OTOPCUA_HISTORIAN_SERVER=$HistorianServer"
|
|
"OTOPCUA_HISTORIAN_PORT=$HistorianPort"
|
|
"OTOPCUA_HISTORIAN_TCP_PORT=$HistorianTcpPort"
|
|
"OTOPCUA_HISTORIAN_BIND=$HistorianBind"
|
|
)
|
|
if ($HistorianUseTls) {
|
|
$historianEnv += "OTOPCUA_HISTORIAN_TLS_ENABLED=true"
|
|
$historianEnv += "OTOPCUA_HISTORIAN_TLS_CERT=$HistorianTlsCert"
|
|
if ($HistorianTlsCertPassword) {
|
|
$historianEnv += "OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD=$HistorianTlsCertPassword"
|
|
}
|
|
} else {
|
|
$historianEnv += "OTOPCUA_HISTORIAN_TLS_ENABLED=false"
|
|
}
|
|
|
|
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, TCP)' `
|
|
start= auto `
|
|
depend= ($AvevaServiceDependencies -join '/') `
|
|
obj= $ServiceAccount | Out-Null
|
|
& sc.exe config OtOpcUaWonderwareHistorian start= delayed-auto | Out-Null
|
|
|
|
$svcKey = "HKLM:\SYSTEM\CurrentControlSet\Services\OtOpcUaWonderwareHistorian"
|
|
Set-ItemProperty -Path $svcKey -Name 'Environment' -Type MultiString -Value $historianEnv
|
|
|
|
& sc.exe failure OtOpcUaWonderwareHistorian reset= 86400 actions= restart/5000/restart/30000/restart/60000 | Out-Null
|
|
|
|
# Idempotent Windows Firewall rule for the sidecar TCP port.
|
|
$fwName = "OtOpcUa Wonderware Historian (TCP $HistorianTcpPort)"
|
|
Get-NetFirewallRule -DisplayName $fwName -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue
|
|
New-NetFirewallRule -DisplayName $fwName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $HistorianTcpPort | Out-Null
|
|
|
|
$historianDepend = 'OtOpcUaWonderwareHistorian'
|
|
}
|
|
|
|
# --- OtOpcUaHost (the fused v2 binary) --------------------------------------
|
|
$normalisedRoles = ($Roles -split ',' | ForEach-Object { $_.Trim() } | Sort-Object -Unique) -join ','
|
|
|
|
$hasAdmin = $normalisedRoles -split ',' -contains 'admin'
|
|
|
|
$hostEnv = @(
|
|
"OTOPCUA_ROLES=$normalisedRoles",
|
|
'DOTNET_ENVIRONMENT=Production'
|
|
)
|
|
if ($hasAdmin) {
|
|
$hostEnv += "ASPNETCORE_URLS=http://+:$HttpPort"
|
|
}
|
|
|
|
$hostDepends = @()
|
|
if ($historianDepend) { $hostDepends += $historianDepend }
|
|
|
|
Write-Host "Installing OtOpcUaHost (roles=$normalisedRoles)..."
|
|
$createArgs = @(
|
|
'create', 'OtOpcUaHost',
|
|
'binPath=', "`"$InstallRoot\OtOpcUa.Host.exe`"",
|
|
'DisplayName=', "OtOpcUa Host ($normalisedRoles)",
|
|
'start=', 'auto',
|
|
'obj=', $ServiceAccount
|
|
)
|
|
if ($hostDepends.Count -gt 0) {
|
|
$createArgs += @('depend=', ($hostDepends -join '/'))
|
|
}
|
|
& sc.exe @createArgs | Out-Null
|
|
|
|
# Env block via registry MultiString (sc.exe doesn't take env directly).
|
|
$svcKey = "HKLM:\SYSTEM\CurrentControlSet\Services\OtOpcUaHost"
|
|
Set-ItemProperty -Path $svcKey -Name 'Environment' -Type MultiString -Value $hostEnv
|
|
|
|
# Restart-on-failure: 5s, 30s, 60s; reset counter after a clean 24h run.
|
|
& sc.exe failure OtOpcUaHost reset= 86400 actions= restart/5000/restart/30000/restart/60000 | Out-Null
|
|
|
|
Write-Host ""
|
|
Write-Host "Installed OtOpcUaHost:"
|
|
Write-Host " Roles: $normalisedRoles"
|
|
if ($hasAdmin) { Write-Host " HTTP port: $HttpPort" }
|
|
Write-Host " Binary: $InstallRoot\OtOpcUa.Host.exe"
|
|
Write-Host " Account: $ServiceAccount"
|
|
Write-Host ""
|
|
Write-Host "Start with:"
|
|
if ($InstallWonderwareHistorian) { Write-Host " sc.exe start OtOpcUaWonderwareHistorian" }
|
|
Write-Host " sc.exe start OtOpcUaHost"
|
|
if ($InstallWonderwareHistorian) {
|
|
Write-Host ""
|
|
Write-Host "Wonderware historian shared secret (configure into appsettings.json AlarmHistorian: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. OtOpcUaHost connects via the"
|
|
Write-Host " Galaxy.Gateway section of appsettings.json (default http://localhost:5120)."
|