From 831d443b03d6a29aa70852e2636280cb4ad0edb0 Mon Sep 17 00:00:00 2001 From: liuhao1024 Date: Mon, 8 Jun 2026 20:08:06 +0800 Subject: [PATCH] fix(gateway): honor --start-now/--start-on-login flags and support non-TTY headless installs When running `hermes gateway install` on Linux/systemd, the command unconditionally prompts with two `prompt_yes_no` questions, breaking headless installs (SSH, CI, provisioning scripts) and ignoring the existing --start-now / --start-on-login CLI flags that the Windows branch already respects. The fix mirrors the Windows path: read CLI flags first, prompt only when flags are not provided AND stdin is a TTY, and fall back to True defaults for non-TTY contexts. The argparse help strings are promoted from SUPPRESS to visible so users can discover the flags. Fixes #42065 --- hermes_cli/gateway.py | 20 +++++++- hermes_cli/subcommands/gateway.py | 8 +-- tests/hermes_cli/test_gateway.py | 83 +++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 6 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 4eb360d988e..9239c310efe 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -6268,8 +6268,24 @@ def _gateway_command_inner(args): " Or use tmux/screen for persistence: tmux new -s hermes 'hermes gateway run'" ) print() - start_now = prompt_yes_no("Start the gateway now after installing the service?", True) - start_on_login = prompt_yes_no("Start the gateway automatically on login/boot with systemd?", True) + # 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). + _sn = getattr(args, "start_now", None) + if _sn is not None: + start_now = _sn + elif sys.stdin.isatty(): + start_now = prompt_yes_no("Start the gateway now after installing the service?", True) + else: + start_now = True + + _sol = getattr(args, "start_on_login", None) + if _sol is not None: + start_on_login = _sol + elif sys.stdin.isatty(): + start_on_login = prompt_yes_no("Start the gateway automatically on login/boot with systemd?", True) + else: + start_on_login = True systemd_install( force=force, system=system, diff --git a/hermes_cli/subcommands/gateway.py b/hermes_cli/subcommands/gateway.py index edf0629cfc3..6b975d4390d 100644 --- a/hermes_cli/subcommands/gateway.py +++ b/hermes_cli/subcommands/gateway.py @@ -169,26 +169,26 @@ def build_gateway_parser( dest="start_now", action="store_true", default=None, - help=argparse.SUPPRESS, + help="Start the gateway service immediately after installing", ) gateway_install.add_argument( "--no-start-now", dest="start_now", action="store_false", - help=argparse.SUPPRESS, + help="Do not start the gateway service after installing", ) gateway_install.add_argument( "--start-on-login", dest="start_on_login", action="store_true", default=None, - help=argparse.SUPPRESS, + help="Enable the service to start automatically on login/boot", ) gateway_install.add_argument( "--no-start-on-login", dest="start_on_login", action="store_false", - help=argparse.SUPPRESS, + help="Do not enable the service to start on login/boot", ) gateway_install.add_argument( "--elevated-handoff", diff --git a/tests/hermes_cli/test_gateway.py b/tests/hermes_cli/test_gateway.py index e28c57e273e..47f0bca2b94 100644 --- a/tests/hermes_cli/test_gateway.py +++ b/tests/hermes_cli/test_gateway.py @@ -444,6 +444,7 @@ def test_gateway_install_in_container_with_operational_systemd_uses_systemd(monk monkeypatch.setattr(gateway, "is_wsl", lambda: False) monkeypatch.setattr(gateway, "is_macos", lambda: False) monkeypatch.setattr(gateway, "is_managed", lambda: False) + monkeypatch.setattr("sys.stdin.isatty", lambda: True) calls = [] monkeypatch.setattr(gateway, "prompt_yes_no", lambda question, default=True: calls.append(("prompt", question, default)) or True) @@ -796,6 +797,7 @@ def test_gateway_install_can_decline_start_now_and_startup(monkeypatch): monkeypatch.setattr(gateway, "is_wsl", lambda: False) monkeypatch.setattr(gateway, "is_macos", lambda: False) monkeypatch.setattr(gateway, "is_managed", lambda: False) + monkeypatch.setattr("sys.stdin.isatty", lambda: True) answers = iter([False, False]) calls = [] @@ -817,6 +819,87 @@ def test_gateway_install_can_decline_start_now_and_startup(monkeypatch): ] +def test_gateway_install_systemd_honors_start_now_flag(monkeypatch): + """--start-now / --no-start-now should bypass the interactive prompt.""" + monkeypatch.setattr(gateway, "supports_systemd_services", lambda: True) + monkeypatch.setattr(gateway, "is_wsl", lambda: False) + monkeypatch.setattr(gateway, "is_macos", lambda: False) + monkeypatch.setattr(gateway, "is_managed", lambda: False) + + calls = [] + monkeypatch.setattr(gateway, "prompt_yes_no", lambda question, default=True: calls.append(("prompt", question))) + monkeypatch.setattr( + gateway, + "systemd_install", + lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", enable_on_startup)), + ) + monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start",))) + + args = SimpleNamespace( + gateway_command="install", force=False, system=False, + run_as_user=None, start_now=True, start_on_login=False, + ) + gateway.gateway_command(args) + + assert ("prompt", "Start the gateway now after installing the service?") not in calls + assert ("start",) in calls + assert ("install", False) in calls + + +def test_gateway_install_systemd_non_tty_uses_defaults(monkeypatch): + """Non-TTY stdin (headless/CI) should use True defaults without prompting.""" + monkeypatch.setattr(gateway, "supports_systemd_services", lambda: True) + monkeypatch.setattr(gateway, "is_wsl", lambda: False) + monkeypatch.setattr(gateway, "is_macos", lambda: False) + monkeypatch.setattr(gateway, "is_managed", lambda: False) + monkeypatch.setattr("sys.stdin.isatty", lambda: False) + + calls = [] + monkeypatch.setattr(gateway, "prompt_yes_no", lambda question, default=True: calls.append(("prompt", question))) + monkeypatch.setattr( + gateway, + "systemd_install", + lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", enable_on_startup)), + ) + monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start",))) + + args = SimpleNamespace(gateway_command="install", force=False, system=False, run_as_user=None) + gateway.gateway_command(args) + + # No prompts — defaults used (start_now=True, start_on_login=True) + assert all(c[0] != "prompt" for c in calls) + assert ("install", True) in calls + assert ("start",) in calls + + +def test_gateway_install_systemd_no_start_now_flag_non_tty(monkeypatch): + """--no-start-now in non-TTY should skip starting the service.""" + monkeypatch.setattr(gateway, "supports_systemd_services", lambda: True) + monkeypatch.setattr(gateway, "is_wsl", lambda: False) + monkeypatch.setattr(gateway, "is_macos", lambda: False) + monkeypatch.setattr(gateway, "is_managed", lambda: False) + monkeypatch.setattr("sys.stdin.isatty", lambda: False) + + calls = [] + monkeypatch.setattr(gateway, "prompt_yes_no", lambda question, default=True: calls.append(("prompt", question))) + monkeypatch.setattr( + gateway, + "systemd_install", + lambda force=False, system=False, run_as_user=None, enable_on_startup=True: calls.append(("install", enable_on_startup)), + ) + monkeypatch.setattr(gateway, "systemd_start", lambda system=False: calls.append(("start",))) + + args = SimpleNamespace( + gateway_command="install", force=False, system=False, + run_as_user=None, start_now=False, start_on_login=True, + ) + gateway.gateway_command(args) + + assert all(c[0] != "prompt" for c in calls) + assert ("install", True) in calls + assert ("start",) not 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)