fix: setup gateway skips the standalone service for a multiplex-served profile
`hermes -p <profile> setup gateway` (and `hermes setup` / `hermes import`) reach the service step through `ensure_gateway_service`, which only knew "is THIS profile's unit running" — a satellite served by the default multiplexer has no unit of its own, so the step printed "Installing the gateway background service ..." and registered a launchd plist / systemd unit that the #97120 start guard then refused, leaving a stray dead service the user had to find and remove by hand (#111958). Route both setup surfaces through one shared predicate: `_served_profile_needs_no_service` wraps `named_profile_served_by_running_multiplexer` (the same probe `profile create`, cron liveness and the run/start/install guards use), prints the "already served" note and returns True so `ensure_gateway_service` and the `hermes gateway setup` wizard (#111962's hunk) skip the install. Default profile and non-multiplex hosts are unchanged. Adds the invariant for the `ensure_gateway_service` path (served → no install, unserved → still installs). Docs: multi-profile-gateways.md names the skipped step. Co-authored-by: kvnloo <7121943+kvnloo@users.noreply.github.com>
This commit is contained in:
+19
-5
@@ -2562,6 +2562,8 @@ def ensure_gateway_service(context: str = "setup") -> bool:
|
||||
try:
|
||||
if _is_service_running():
|
||||
return True
|
||||
if _served_profile_needs_no_service():
|
||||
return True
|
||||
if not _is_service_installed():
|
||||
if supports_systemd and has_conflicting_systemd_units():
|
||||
# Both units would fight over bot tokens; don't pile a fresh install onto a conflicted state.
|
||||
@@ -4471,6 +4473,22 @@ def named_profile_served_by_running_multiplexer(profile_name: str | None = None)
|
||||
return False
|
||||
|
||||
|
||||
def _served_profile_needs_no_service() -> bool:
|
||||
"""Print the "already served" note and return True when a setup flow must not install a standalone
|
||||
service: a live multiplexing default gateway already serves this named profile, so the unit/plist it
|
||||
would register can only sit dead (the start guard refuses it) or double-bind its platforms.
|
||||
Shared by ``hermes setup gateway`` / ``hermes setup`` / ``hermes import`` (``ensure_gateway_service``)
|
||||
and the ``hermes gateway setup`` wizard. See #111958."""
|
||||
if not named_profile_served_by_running_multiplexer():
|
||||
return False
|
||||
print_success(
|
||||
f"Profile '{_current_profile_name()}' is already served by the default multiplexer."
|
||||
)
|
||||
print_info(" (served now by the running multiplexed gateway — add its bot token and it connects)")
|
||||
print_info(" No standalone gateway service was installed or started.")
|
||||
return True
|
||||
|
||||
|
||||
def _named_profile_refused_under_multiplexer(force: bool = False) -> bool:
|
||||
"""Print the served-profile refusal and return True when a named-profile gateway must not start:
|
||||
a multiplexing default gateway already serves it (a second one would double-bind its platforms: two
|
||||
@@ -5783,11 +5801,7 @@ def _wizard_post_setup() -> None:
|
||||
"""Offer to install/start/restart the gateway once at least one platform has progress."""
|
||||
print()
|
||||
print(color("─" * 58, Colors.DIM))
|
||||
if named_profile_served_by_running_multiplexer():
|
||||
print_success(
|
||||
f"Profile '{_current_profile_name()}' is already served by the default multiplexer."
|
||||
)
|
||||
print_info("No standalone gateway service was installed or started.")
|
||||
if _served_profile_needs_no_service():
|
||||
return
|
||||
service_installed = _is_service_installed()
|
||||
service_running = _is_service_running()
|
||||
|
||||
@@ -96,6 +96,29 @@ def test_setup_wizard_skips_service_install_for_profile_served_by_multiplexer(
|
||||
assert "already served by the default multiplexer" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_setup_gateway_service_step_skips_install_for_served_profile(served_root, monkeypatch, capsys):
|
||||
"""``hermes -p <profile> setup gateway`` (and ``hermes setup`` / ``hermes import``) reach the service
|
||||
step through ``ensure_gateway_service``: a served profile gets the multiplexer note and no unit/plist,
|
||||
while a profile the live record does not list is still installed (#111958)."""
|
||||
import hermes_cli.gateway as gw
|
||||
|
||||
calls: list[str] = []
|
||||
monkeypatch.setattr(gw, "supports_systemd_services", lambda: True)
|
||||
monkeypatch.setattr(gw, "_is_service_running", lambda: False)
|
||||
monkeypatch.setattr(gw, "_is_service_installed", lambda: False)
|
||||
monkeypatch.setattr(gw, "has_conflicting_systemd_units", lambda: False)
|
||||
monkeypatch.setattr(gw, "systemd_install", lambda **kwargs: calls.append("install"))
|
||||
monkeypatch.setattr(gw, "systemd_start", lambda *args, **kwargs: calls.append("start"))
|
||||
|
||||
assert gw.ensure_gateway_service(context="setup") is True
|
||||
assert calls == []
|
||||
assert "already served by the default multiplexer" in capsys.readouterr().out
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(served_root / "profiles" / "other")) # not in the live record
|
||||
assert gw.ensure_gateway_service(context="setup") is True
|
||||
assert calls == ["install", "start"]
|
||||
|
||||
|
||||
def test_recycled_pid_does_not_lend_a_stale_record_its_served_profiles(served_root):
|
||||
"""A stale default record whose PID now belongs to an unrelated process (start time differs, command
|
||||
line is not a gateway's) must not make its ``served_profiles`` authoritative: bare PID existence
|
||||
|
||||
@@ -147,6 +147,13 @@ default home's `gateway_state.json`), so it stays correct when the multiplexer w
|
||||
enabled only through `GATEWAY_MULTIPLEX_PROFILES` in the default profile's
|
||||
environment, or when profiles were added after the gateway started.
|
||||
|
||||
The setup flows follow the same rule: `hermes -p coder setup gateway`, `hermes -p coder setup`,
|
||||
`hermes -p coder gateway setup` and `hermes -p coder import` configure the profile's bots but
|
||||
skip the "install the gateway background service" step for a served profile, printing
|
||||
*"Profile 'coder' is already served by the default multiplexer"* instead of registering a
|
||||
stray unit or plist that could only sit dead. Add the bot token and the running multiplexer
|
||||
picks it up.
|
||||
|
||||
The multiplexer is the single inbound process; a second profile gateway would
|
||||
double-bind that profile's platforms. Pass `--force` (accepted by `run`, `start`,
|
||||
`install` and `restart`) only if you deliberately want a separate process for that
|
||||
|
||||
Reference in New Issue
Block a user