Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions hermes_cli/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -2492,6 +2492,20 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):
hasn't fully exited yet.
"""
sys.path.insert(0, str(PROJECT_ROOT))

# Refresh the systemd unit definition on every boot so that restart
# settings (RestartSec, StartLimitIntervalSec, etc.) stay current even
# when the process was respawned via exit-code-75 (stale-code or
# /restart) rather than through `hermes gateway restart` which already
# calls refresh_systemd_unit_if_needed(). Without this, a code update
# that ships new unit settings won't take effect until the next manual
# `hermes gateway start/restart` — leaving the gateway vulnerable to
# the exact failure mode the new settings were meant to prevent.
if supports_systemd_services():
try:
refresh_systemd_unit_if_needed(system=False)
except Exception:
pass # best-effort; don't block gateway startup

from gateway.run import start_gateway

Expand Down
34 changes: 34 additions & 0 deletions tests/hermes_cli/test_gateway_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,40 @@ def fake_run(cmd, check=True, **kwargs):
]


def test_run_gateway_refreshes_outdated_unit_on_boot(self, tmp_path, monkeypatch):
"""run_gateway() should refresh the systemd unit on boot so that
restart settings take effect even when the process was respawned
via exit-code-75 (bypassing `hermes gateway restart`)."""
unit_path = tmp_path / "hermes-gateway.service"
unit_path.write_text("old unit\n", encoding="utf-8")

monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda system=False: unit_path)
monkeypatch.setattr(gateway_cli, "generate_systemd_unit", lambda system=False, run_as_user=None: "new unit\n")
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)

calls = []

def fake_run(cmd, check=True, **kwargs):
calls.append(cmd)
return SimpleNamespace(returncode=0, stdout="", stderr="")

monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run)

# Prevent run_gateway from actually starting the gateway
def fake_start_gateway(**kwargs):
import asyncio
f = asyncio.Future()
f.set_result(True)
return f

monkeypatch.setattr("gateway.run.start_gateway", fake_start_gateway)

gateway_cli.run_gateway()

assert unit_path.read_text(encoding="utf-8") == "new unit\n"
assert ["systemctl", "--user", "daemon-reload"] in calls


class TestGeneratedSystemdUnits:
def test_user_unit_avoids_recursive_execstop_and_uses_extended_stop_timeout(self):
unit = gateway_cli.generate_systemd_unit(system=False)
Expand Down
Loading