From 4e7a55704d2a2c94b49995d5c05376dd5f6da190 Mon Sep 17 00:00:00 2001 From: Hermes & Sam Date: Sun, 21 Jun 2026 11:06:12 +0200 Subject: [PATCH] feat(gateway): real FreeBSD rc.d detection in get_gateway_runtime_snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hermes status` reported the gateway as "manual process" on FreeBSD because get_gateway_runtime_snapshot() only had systemd/launchd branches and fell through. Add a proper FreeBSD rc.d path so status reports real state: - is_freebsd() helper (sys.platform.startswith("freebsd")). - _freebsd_rcd_service_path() + _probe_freebsd_service_running() — probes the hermes_daemon rc.d service via `service hermes_daemon onestatus` (reports running regardless of the rcvar enable flag; works without root; fail-safe on timeout/missing binary), mirroring the launchd probe. - snapshot branch: manager="rc.d (hermes_daemon)", service_installed (rc.d file present), service_running (onestatus), service_scope="rc.d". This is the deeper fix behind the status.py fallback message (PR #6). Not changed: get_managed_gateway_pids() restart-drain path (systemd/launchd only) — gateway liveness already works via find_gateway_pids(); rc.d restart-drain is a separate follow-up. py_compile clean; wiring + branch-ordering verified. Co-Authored-By: Claude Opus 4.8 --- hermes_cli/gateway.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index c1f7c04d7..706b5209d 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -1079,6 +1079,30 @@ def _probe_launchd_service_running() -> bool: return result.returncode == 0 +_FREEBSD_RCD_SERVICE = "hermes_daemon" + + +def _freebsd_rcd_service_path() -> Path: + return Path("/usr/local/etc/rc.d") / _FREEBSD_RCD_SERVICE + + +def _probe_freebsd_service_running() -> bool: + if not _freebsd_rcd_service_path().exists(): + return False + try: + # `onestatus` reports running state regardless of the rcvar enable flag, + # and works without root. + result = subprocess.run( + ["service", _FREEBSD_RCD_SERVICE, "onestatus"], + capture_output=True, + text=True, + timeout=10, + ) + except (subprocess.TimeoutExpired, FileNotFoundError, OSError): + return False + return result.returncode == 0 + + def get_gateway_runtime_snapshot(system: bool = False) -> GatewayRuntimeSnapshot: """Return a unified view of gateway liveness for the current profile.""" gateway_pids = tuple(find_gateway_pids()) @@ -1128,6 +1152,15 @@ def get_gateway_runtime_snapshot(system: bool = False) -> GatewayRuntimeSnapshot service_scope="launchd", ) + if is_freebsd(): + return GatewayRuntimeSnapshot( + manager=f"rc.d ({_FREEBSD_RCD_SERVICE})", + service_installed=_freebsd_rcd_service_path().exists(), + service_running=_probe_freebsd_service_running(), + gateway_pids=gateway_pids, + service_scope="rc.d", + ) + return GatewayRuntimeSnapshot( manager="manual process", gateway_pids=gateway_pids, @@ -1369,6 +1402,10 @@ def is_macos() -> bool: return sys.platform == "darwin" +def is_freebsd() -> bool: + return sys.platform.startswith("freebsd") + + def is_windows() -> bool: return sys.platform == "win32" -- 2.45.3