Files
wwtools/mbproxy/install/install.sh
Joseph Doherty b330faff03 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>
2026-05-15 09:41:59 -04:00

135 lines
5.8 KiB
Bash

#!/usr/bin/env bash
#
# install.sh — install the mbproxy service on a Linux / systemd host.
#
# The Linux counterpart of install.ps1. Copies the published binary to
# /opt/mbproxy, seeds the config at /etc/mbproxy/appsettings.json (preserving any
# existing one), creates the log and bundle-cache directories and the mbproxy
# service account, installs the systemd unit, and enables + starts the service.
#
# Re-running on an already-installed service is safe (idempotent): the binary is
# refreshed, an existing /etc/mbproxy/appsettings.json is preserved, and the
# service is restarted.
#
# Usage:
# sudo ./install.sh [--publish-dir DIR] [--no-start]
#
# --publish-dir DIR directory containing the published Mbproxy binary.
# Default: <repo>/publish-out/self-contained
# --no-start install and enable the unit but do not start it.
#
set -euo pipefail
# ── 0. Settings ──────────────────────────────────────────────────────────────
SERVICE_NAME="mbproxy"
SERVICE_USER="mbproxy"
INSTALL_DIR="/opt/mbproxy"
CONFIG_DIR="/etc/mbproxy"
LOG_DIR="/var/log/mbproxy"
CACHE_DIR="/var/cache/mbproxy"
UNIT_DEST="/etc/systemd/system/${SERVICE_NAME}.service"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(dirname "$script_dir")"
publish_dir="${repo_root}/publish-out/self-contained"
start_service=1
while [[ $# -gt 0 ]]; do
case "$1" in
--publish-dir) publish_dir="$2"; shift 2 ;;
--no-start) start_service=0; shift ;;
*) echo "Unknown argument: $1" >&2; exit 2 ;;
esac
done
# ── 1. Pre-flight checks ─────────────────────────────────────────────────────
if [[ "$(id -u)" -ne 0 ]]; then
echo "install.sh must run as root (use sudo)." >&2
exit 1
fi
binary_src="${publish_dir}/Mbproxy"
if [[ ! -f "$binary_src" ]]; then
echo "Mbproxy binary not found at '${binary_src}'." >&2
echo "Run install/publish.sh first, or pass --publish-dir." >&2
exit 1
fi
unit_src="${script_dir}/mbproxy.service"
config_src="${publish_dir}/appsettings.json"
if [[ ! -f "$unit_src" ]]; then
echo "Unit file not found at '${unit_src}'." >&2
exit 1
fi
echo "Installing ${SERVICE_NAME} service..."
echo " Publish dir : ${publish_dir}"
echo " Install dir : ${INSTALL_DIR}"
echo " Config dir : ${CONFIG_DIR}"
# ── 2. Service account ───────────────────────────────────────────────────────
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
echo "Creating service account '${SERVICE_USER}'..."
useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER"
else
echo "Service account '${SERVICE_USER}' already exists."
fi
# ── 3. Stop the service if running (so the binary can be replaced) ───────────
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
echo "Stopping running service '${SERVICE_NAME}'..."
systemctl stop "$SERVICE_NAME"
fi
# ── 4. Directories ───────────────────────────────────────────────────────────
install -d -m 0755 "$INSTALL_DIR"
install -d -m 0755 "$CONFIG_DIR"
install -d -m 0750 -o "$SERVICE_USER" -g "$SERVICE_USER" "$LOG_DIR"
install -d -m 0750 -o "$SERVICE_USER" -g "$SERVICE_USER" "$CACHE_DIR"
# ── 5. Binary ────────────────────────────────────────────────────────────────
echo "Copying binary to '${INSTALL_DIR}/Mbproxy'..."
install -m 0755 "$binary_src" "${INSTALL_DIR}/Mbproxy"
# ── 6. Config (preserve an existing one) ─────────────────────────────────────
config_dest="${CONFIG_DIR}/appsettings.json"
if [[ -f "$config_dest" ]]; then
echo "Preserving existing config at '${config_dest}'."
elif [[ -f "$config_src" ]]; then
echo "Seeding config template to '${config_dest}'..."
install -m 0644 "$config_src" "$config_dest"
else
echo "WARNING: no appsettings.json in '${publish_dir}' — create '${config_dest}' manually." >&2
fi
# ── 7. systemd unit ──────────────────────────────────────────────────────────
echo "Installing systemd unit to '${UNIT_DEST}'..."
install -m 0644 "$unit_src" "$UNIT_DEST"
systemctl daemon-reload
systemctl enable "$SERVICE_NAME" >/dev/null
# ── 8. Start ─────────────────────────────────────────────────────────────────
if [[ "$start_service" -eq 1 ]]; then
echo "Starting service '${SERVICE_NAME}'..."
systemctl start "$SERVICE_NAME"
sleep 1
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo "Service '${SERVICE_NAME}' is running."
else
echo "WARNING: service '${SERVICE_NAME}' did not reach active state." >&2
echo "Check: journalctl -u ${SERVICE_NAME} -e" >&2
fi
fi
echo ""
echo "Install complete."
echo " Config : ${config_dest}"
echo " Logs : ${LOG_DIR}"
echo " Binary : ${INSTALL_DIR}/Mbproxy"
echo ""
echo "Next steps:"
echo " 1. Edit '${config_dest}' to configure your PLC list and BCD tags."
echo " 2. Restart: sudo systemctl restart ${SERVICE_NAME}"
echo " 3. Logs: journalctl -u ${SERVICE_NAME} -f"
echo " 4. Status: http://localhost:8080/"