diff --git a/scripts/install/Install-Services.ps1 b/scripts/install/Install-Services.ps1 index 32e6bc3d..d2d2fbb1 100644 --- a/scripts/install/Install-Services.ps1 +++ b/scripts/install/Install-Services.ps1 @@ -1,13 +1,16 @@ <# .SYNOPSIS Registers the v2 Windows service on a node: OtOpcUaHost (fused binary, .NET 10) - and optionally OtOpcUaWonderwareHistorian (Wonderware historian sidecar, net48 x86). + 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 logic is unchanged from v1; install it with -InstallWonderwareHistorian. + 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. @@ -39,6 +42,30 @@ 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' @@ -57,12 +84,16 @@ param( [int]$HttpPort = 9000, # Wonderware historian sidecar. Optional; gates the OtOpcUaWonderwareHistorian - # service. Secret + pipe defaults match the server's Historian:Wonderware appsettings. + # service. Secret defaults match the server's Historian:Wonderware appsettings. [switch]$InstallWonderwareHistorian, [string]$HistorianSharedSecret, - [string]$HistorianPipeName = 'OtOpcUaWonderwareHistorian', [string]$HistorianServer = 'localhost', - [int]$HistorianPort = 32568, + [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') ) @@ -87,29 +118,43 @@ if ($InstallWonderwareHistorian -and -not (Test-Path "$InstallRoot\WonderwareHis 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 -} - -# --- OtOpcUaWonderwareHistorian sidecar (optional, unchanged from v1) ------- +# --- 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_PIPE=$HistorianPipeName" - "OTOPCUA_ALLOWED_SID=$sid" "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)' ` + DisplayName= 'OtOpcUa Wonderware Historian Sidecar (out-of-process aahClient, TCP)' ` start= auto ` depend= ($AvevaServiceDependencies -join '/') ` obj= $ServiceAccount | Out-Null @@ -120,6 +165,11 @@ if ($InstallWonderwareHistorian) { & 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' } diff --git a/scripts/install/Refresh-Services.ps1 b/scripts/install/Refresh-Services.ps1 index 40c441e4..6df55168 100644 --- a/scripts/install/Refresh-Services.ps1 +++ b/scripts/install/Refresh-Services.ps1 @@ -125,7 +125,7 @@ Run { # `dotnet publish` above) silently drops the net48 binding-redirect # .exe.config and the transitive runtime DLLs MessagePack needs — most # notably System.Memory.dll. The sidecar then JIT-load-faults inside -# PipeServer.RunOneConnectionAsync ("FileNotFoundException: System.Memory, +# TcpFrameServer.RunOneConnectionAsync ("FileNotFoundException: System.Memory, # Version=4.0.1.2") and NSSM crash-loops it (exit 2 every ~2 min, ~120 s of # retry backoff before it gives up). Fail loudly here so an incomplete deploy # is caught at publish time instead of by a production crash-loop. @@ -158,6 +158,11 @@ if (-not $WhatIf) { # ------------------------------------------------------------------------ # Step 5: Service env block — ensure OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED # is set on the Wonderware historian service (PR C.2 toggle). +# Note: the TCP transport env (OTOPCUA_HISTORIAN_TCP_PORT, OTOPCUA_HISTORIAN_BIND, +# OTOPCUA_HISTORIAN_TLS_ENABLED, OTOPCUA_HISTORIAN_TLS_CERT) and the Windows +# Firewall rule are set once at install time by Install-Services.ps1 and are +# not touched here on refresh; re-run Install-Services.ps1 to change them. +# OTOPCUA_ALLOWED_SID is no longer used (TCP transport retired the named pipe). # ------------------------------------------------------------------------ if (Test-NssmService 'OtOpcUaWonderwareHistorian') {