From 463225caf17e740c098028cc3f5b7b3d270b7ffa Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sun, 28 Jun 2026 02:53:30 -0700 Subject: [PATCH] fix(gateway): bypass legacy-unit prompt in non-TTY systemd install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Folds in PR #42124 (kyssta-exe): systemd_install gained a non_interactive flag so the 'Remove the legacy unit(s)?' prompt — the second hidden prompt not guarded by --start-now/--start-on-login — is also skipped in headless contexts. Updates systemd_install test mocks to accept the new kwarg and adds coverage for the legacy-unit-skip path. --- hermes_cli/gateway.py | 9 ++++--- tests/hermes_cli/test_gateway.py | 46 +++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 9239c310efe..caa0b6133a6 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -3044,6 +3044,7 @@ def systemd_install( system: bool = False, run_as_user: str | None = None, enable_on_startup: bool = True, + non_interactive: bool = False, ): if system: _require_root_for_system_service("install") @@ -3057,7 +3058,7 @@ def systemd_install( print() print_legacy_unit_warning() print() - if prompt_yes_no("Remove the legacy unit(s) before installing?", True): + if non_interactive or prompt_yes_no("Remove the legacy unit(s) before installing?", True): remove_legacy_hermes_units(interactive=False) print() @@ -6271,10 +6272,11 @@ def _gateway_command_inner(args): # Honor CLI flags (--start-now / --no-start-now, --start-on-login / # --no-start-on-login). When not provided, prompt interactively or # fall back to True for non-TTY / headless contexts (SSH, CI, pipes). + non_interactive = not (hasattr(sys.stdin, "isatty") and sys.stdin.isatty()) _sn = getattr(args, "start_now", None) if _sn is not None: start_now = _sn - elif sys.stdin.isatty(): + elif not non_interactive: start_now = prompt_yes_no("Start the gateway now after installing the service?", True) else: start_now = True @@ -6282,7 +6284,7 @@ def _gateway_command_inner(args): _sol = getattr(args, "start_on_login", None) if _sol is not None: start_on_login = _sol - elif sys.stdin.isatty(): + elif not non_interactive: start_on_login = prompt_yes_no("Start the gateway automatically on login/boot with systemd?", True) else: start_on_login = True @@ -6291,6 +6293,7 @@ def _gateway_command_inner(args): system=system, run_as_user=run_as_user, enable_on_startup=start_on_login, + non_interactive=non_interactive, ) if start_now: systemd_start(system=system) diff --git a/tests/hermes_cli/test_gateway.py b/tests/hermes_cli/test_gateway.py index 47f0bca2b94..7fd71b3c40a 100644 --- a/tests/hermes_cli/test_gateway.py +++ b/tests/hermes_cli/test_gateway.py @@ -451,7 +451,7 @@ def test_gateway_install_in_container_with_operational_systemd_uses_systemd(monk monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", force, system, run_as_user, enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append(("install", force, system, run_as_user, enable_on_startup)), ) monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start", system))) @@ -767,7 +767,7 @@ def test_install_linux_gateway_from_setup_system_choice_as_root_installs(monkeyp monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append((force, system, run_as_user, enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append((force, system, run_as_user, enable_on_startup)), ) scope, did_install = gateway.install_linux_gateway_from_setup(force=True) @@ -783,7 +783,7 @@ def test_install_linux_gateway_from_setup_passes_startup_choice(monkeypatch): monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append((force, system, run_as_user, enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append((force, system, run_as_user, enable_on_startup)), ) scope, did_install = gateway.install_linux_gateway_from_setup(force=False, enable_on_startup=False) @@ -805,7 +805,7 @@ def test_gateway_install_can_decline_start_now_and_startup(monkeypatch): monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", force, system, run_as_user, enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append(("install", force, system, run_as_user, enable_on_startup)), ) monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start", system))) @@ -831,7 +831,7 @@ def test_gateway_install_systemd_honors_start_now_flag(monkeypatch): monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append(("install", enable_on_startup)), ) monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start",))) @@ -859,7 +859,7 @@ def test_gateway_install_systemd_non_tty_uses_defaults(monkeypatch): monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append(("install", enable_on_startup)), ) monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start",))) @@ -885,7 +885,7 @@ def test_gateway_install_systemd_no_start_now_flag_non_tty(monkeypatch): monkeypatch.setattr( gateway, "systemd_install", - lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", enable_on_startup)), + lambda force=False, system=False, run_as_user=None, enable_on_startup=True, **kw: calls.append(("install", enable_on_startup)), ) monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start",))) @@ -900,6 +900,38 @@ def test_gateway_install_systemd_no_start_now_flag_non_tty(monkeypatch): assert ("start",) not in calls +def test_gateway_install_noninteractive_skips_legacy_unit_prompt(monkeypatch, tmp_path): + """In non-TTY, the legacy-unit removal prompt in systemd_install is skipped. + + Covers the second hidden prompt that --start-now/--start-on-login do not + guard. Originally contributed via PR #42124 (kyssta-exe). + """ + monkeypatch.setattr(gateway, "has_legacy_hermes_units", lambda: True) + + calls = [] + monkeypatch.setattr( + gateway, + "prompt_yes_no", + lambda question, default=True: calls.append(("prompt", question)) or True, + ) + monkeypatch.setattr(gateway, "remove_legacy_hermes_units", lambda interactive=False: calls.append(("remove_legacy",))) + monkeypatch.setattr(gateway, "print_legacy_unit_warning", lambda: None) + + fake_path = tmp_path / "hermes-gateway.service" + monkeypatch.setattr(gateway, "get_systemd_unit_path", lambda system=False: fake_path) + monkeypatch.setattr(gateway, "generate_systemd_unit", lambda system=False, run_as_user=None: "[Service]") + monkeypatch.setattr(gateway, "_run_systemctl", lambda *a, **kw: None) + monkeypatch.setattr(gateway, "_ensure_linger_enabled", lambda: None) + monkeypatch.setattr(gateway, "print_systemd_scope_conflict_warning", lambda: None) + monkeypatch.setattr(gateway, "_service_scope_label", lambda system=False: "user") + + gateway.systemd_install(non_interactive=True) + + # Legacy units removed without prompting. + assert ("remove_legacy",) in calls + assert all(c[0] != "prompt" for c in calls) + + def test_find_gateway_pids_falls_back_to_pid_file_when_process_scan_fails(monkeypatch): monkeypatch.setattr(gateway, "_get_service_pids", lambda: set()) monkeypatch.setattr(gateway, "is_windows", lambda: False)