mbproxy: cross-platform support — Linux/systemd alongside Windows

Make the service build, run, and install on Linux as a first-class
target while keeping the Windows Service + Event Log behaviour intact.

- Build: drop the hardcoded win-x64 RID — single-file publish now works
  for any RID. publish.ps1 gains -Rid; new publish.sh for Linux hosts.
- Diagnostics: DiagnosticSinkSelector picks the Error+ sink per host —
  Windows Event Log under the SCM, local syslog under systemd
  (Serilog.Sinks.SyslogMessages), none for interactive runs. The
  EventLog truncation helper is extracted so it is testable cross-OS.
- Host: Program.cs registers AddSystemd() alongside AddWindowsService().
- Config: a RID-conditioned appsettings template ships Windows or Unix
  paths; both templates are schema-validated by a test.
- Install: systemd unit (Type=exec) plus install.sh / uninstall.sh.
  Also fixes two cross-platform bugs found while testing: install.ps1
  and uninstall.ps1 used New-EventLog / Remove-EventLog (absent in
  PowerShell 7), and the E2E sim launcher hardcoded Windows venv paths.
- Docs updated across README, CLAUDE.md, and docs/ for dual-platform.

413 tests pass on Windows; 374 (all non-simulator) on Linux.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-15 09:41:59 -04:00
parent 0868613890
commit b330faff03
29 changed files with 1805 additions and 106 deletions
+13 -5
View File
@@ -48,9 +48,13 @@ if (-not $ProfileResolved) {
}
# ── 2. Locate Python ─────────────────────────────────────────────────────────
# Try 'python' first (standard PATH install), then the Windows-store launcher 'py'.
# Windows: 'python' (standard PATH install), then the 'py' launcher.
# Linux/macOS: 'python3' (the canonical name), then 'python'.
# The candidate order is platform-specific so Windows never matches the Microsoft
# Store 'python3' stub.
$pythonExe = $null
foreach ($candidate in 'python', 'py') {
$pythonCandidates = $IsWindows ? @('python', 'py') : @('python3', 'python')
foreach ($candidate in $pythonCandidates) {
try {
$ver = & $candidate --version 2>&1
if ($LASTEXITCODE -eq 0) {
@@ -77,9 +81,13 @@ or use the Windows Store launcher ('py').
$PYMODBUS_VERSION = '3.13.0'
$venvDir = Join-Path $PSScriptRoot '.venv'
$venvPython = Join-Path $venvDir 'Scripts\python.exe'
$pipExe = Join-Path $venvDir 'Scripts\pip.exe'
$simulatorExe = Join-Path $venvDir 'Scripts\pymodbus.simulator.exe' # sentinel for complete install
# venv executable layout differs by OS: Windows puts them in Scripts\ with a .exe
# extension; Linux/macOS put them in bin/ with no extension.
$venvBin = $IsWindows ? 'Scripts' : 'bin'
$exeExt = $IsWindows ? '.exe' : ''
$venvPython = Join-Path $venvDir $venvBin "python$exeExt"
$pipExe = Join-Path $venvDir $venvBin "pip$exeExt"
$simulatorExe = Join-Path $venvDir $venvBin "pymodbus.simulator$exeExt" # sentinel for complete install
# Provisioning is idempotent: we only skip it when pymodbus.simulator.exe exists.
# Checking only the .venv directory is not enough — a previous run killed mid-install