From 6d20c321d6ab11352cba9ce34c75cfc58dec7855 Mon Sep 17 00:00:00 2001 From: Season Date: Mon, 20 Apr 2026 19:37:17 +0800 Subject: [PATCH] fix(gateway): enable linger on systemd user-service repair path (#12863) The repair branch in `systemd_install()` exits as soon as it rewrites an outdated unit and re-runs `systemctl enable`, bypassing `_ensure_linger_enabled()`. On headless Linux the command reports success, but the repaired user service still stops at logout. Call `_ensure_linger_enabled()` before the early return when the install is user-scoped, mirroring what the fresh-install path already does. Adds two regression tests in `tests/hermes_cli/test_gateway_linger.py`: - repair path (user scope) calls the linger helper - repair path (system scope) does not call it --- hermes_cli/gateway.py | 3 +++ tests/hermes_cli/test_gateway_linger.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 78604b34e5..421180eb33 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -3234,9 +3234,12 @@ def systemd_install( else: print(f"Service already installed at: {unit_path}") print("Use --force to reinstall") + # Same post-install guarantee as a fresh install: a repaired user unit must survive logout too. configured_user = _read_systemd_user_from_unit(unit_path) if system else None if configured_user: _ensure_system_service_linger(configured_user) + elif not system: + _ensure_linger_enabled() return unit_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/tests/hermes_cli/test_gateway_linger.py b/tests/hermes_cli/test_gateway_linger.py index 3182f87f74..7a70ef8b38 100644 --- a/tests/hermes_cli/test_gateway_linger.py +++ b/tests/hermes_cli/test_gateway_linger.py @@ -215,3 +215,25 @@ def test_existing_system_install_repairs_linger_for_configured_user(monkeypatch, gateway.systemd_install(system=True, run_as_user="alice") assert helper_calls == ["alice"] + + +@pytest.mark.parametrize("system", [False, True]) +def test_systemd_install_repair_path_keeps_linger_guarantee(monkeypatch, tmp_path, system): + """Repairing a stale unit used to return before the linger step, so an upgraded headless + user service died at logout (#12863). System scope never touches user linger.""" + unit_path = tmp_path / "hermes-gateway.service" + unit_path.write_text("old unit\n", encoding="utf-8") + monkeypatch.setattr(gateway, "get_systemd_unit_path", lambda system=False: unit_path) + monkeypatch.setattr(gateway, "systemd_unit_is_current", lambda system=False: False) + monkeypatch.setattr(gateway, "has_legacy_hermes_units", lambda: False) + monkeypatch.setattr(gateway, "_require_root_for_system_service", lambda _action: None) + monkeypatch.setattr(gateway, "_sync_hermes_home_from_systemd_unit", lambda system=False: None) + monkeypatch.setattr(gateway, "_read_systemd_user_from_unit", lambda path: None) + monkeypatch.setattr(gateway, "refresh_systemd_unit_if_needed", lambda system=False: None) + monkeypatch.setattr(gateway, "_run_systemctl", lambda *args, **kwargs: None) + helper_calls = [] + monkeypatch.setattr(gateway, "_ensure_linger_enabled", lambda: helper_calls.append(True)) + + gateway.systemd_install(force=False, system=system) + + assert helper_calls == ([] if system else [True])