fix(gateway): harden Windows gateway install lifecycle
Preserve Windows profile install decisions across UAC handoff, avoid visible console windows by launching via pythonw, make repeated install/start idempotent, recreate stale Scheduled Tasks, and separate start-now from login auto-start behavior. Add Windows gateway regression coverage and systemd setup tests for the shared install flow.
This commit is contained in:
@@ -1839,7 +1839,7 @@ def prompt_linux_gateway_install_scope() -> str | None:
|
||||
return {0: "user", 1: "system", 2: None}[choice]
|
||||
|
||||
|
||||
def install_linux_gateway_from_setup(force: bool = False) -> tuple[str | None, bool]:
|
||||
def install_linux_gateway_from_setup(force: bool = False, enable_on_startup: bool = True) -> tuple[str | None, bool]:
|
||||
scope = prompt_linux_gateway_install_scope()
|
||||
if scope is None:
|
||||
return None, False
|
||||
@@ -1863,10 +1863,10 @@ def install_linux_gateway_from_setup(force: bool = False) -> tuple[str | None, b
|
||||
break
|
||||
print_error(" Enter a username.")
|
||||
|
||||
systemd_install(force=force, system=True, run_as_user=run_as_user)
|
||||
systemd_install(force=force, system=True, run_as_user=run_as_user, enable_on_startup=enable_on_startup)
|
||||
return scope, True
|
||||
|
||||
systemd_install(force=force, system=False)
|
||||
systemd_install(force=force, system=False, enable_on_startup=enable_on_startup)
|
||||
return scope, True
|
||||
|
||||
|
||||
@@ -2437,7 +2437,12 @@ def _get_restart_drain_timeout() -> float:
|
||||
return parse_restart_drain_timeout(raw)
|
||||
|
||||
|
||||
def systemd_install(force: bool = False, system: bool = False, run_as_user: str | None = None):
|
||||
def systemd_install(
|
||||
force: bool = False,
|
||||
system: bool = False,
|
||||
run_as_user: str | None = None,
|
||||
enable_on_startup: bool = True,
|
||||
):
|
||||
if system:
|
||||
_require_root_for_system_service("install")
|
||||
|
||||
@@ -2461,7 +2466,8 @@ def systemd_install(force: bool = False, system: bool = False, run_as_user: str
|
||||
if not systemd_unit_is_current(system=system):
|
||||
print(f"↻ Repairing outdated {_service_scope_label(system)} systemd service at: {unit_path}")
|
||||
refresh_systemd_unit_if_needed(system=system)
|
||||
_run_systemctl(["enable", get_service_name()], system=system, check=True, timeout=30)
|
||||
if enable_on_startup:
|
||||
_run_systemctl(["enable", get_service_name()], system=system, check=True, timeout=30)
|
||||
print(f"✓ {_service_scope_label(system).capitalize()} service definition updated")
|
||||
return
|
||||
print(f"Service already installed at: {unit_path}")
|
||||
@@ -2473,10 +2479,12 @@ def systemd_install(force: bool = False, system: bool = False, run_as_user: str
|
||||
unit_path.write_text(generate_systemd_unit(system=system, run_as_user=run_as_user), encoding="utf-8")
|
||||
|
||||
_run_systemctl(["daemon-reload"], system=system, check=True, timeout=30)
|
||||
_run_systemctl(["enable", get_service_name()], system=system, check=True, timeout=30)
|
||||
if enable_on_startup:
|
||||
_run_systemctl(["enable", get_service_name()], system=system, check=True, timeout=30)
|
||||
|
||||
print()
|
||||
print(f"✓ {_service_scope_label(system).capitalize()} service installed and enabled!")
|
||||
enable_label = "installed and enabled" if enable_on_startup else "installed"
|
||||
print(f"✓ {_service_scope_label(system).capitalize()} service {enable_label}!")
|
||||
print()
|
||||
print("Next steps:")
|
||||
print(f" {'sudo ' if system else ''}hermes gateway start{scope_flag} # Start the service")
|
||||
@@ -4947,31 +4955,37 @@ def gateway_setup():
|
||||
else:
|
||||
platform_name = "Scheduled Task"
|
||||
wsl_note = " (note: services may not survive WSL restarts)" if is_wsl() else ""
|
||||
if prompt_yes_no(f" Install the gateway as a {platform_name} service?{wsl_note} (runs in background, starts on boot)", True):
|
||||
start_now = prompt_yes_no(" Start the gateway now?", True)
|
||||
start_on_login = prompt_yes_no(
|
||||
f" Start the gateway automatically on login/boot as a {platform_name} service?{wsl_note}",
|
||||
True,
|
||||
)
|
||||
if start_now or start_on_login:
|
||||
try:
|
||||
installed_scope = None
|
||||
did_install = False
|
||||
started_inline = False
|
||||
if supports_systemd_services():
|
||||
installed_scope, did_install = install_linux_gateway_from_setup(force=False)
|
||||
installed_scope, did_install = install_linux_gateway_from_setup(
|
||||
force=False,
|
||||
enable_on_startup=start_on_login,
|
||||
)
|
||||
elif is_macos():
|
||||
launchd_install(force=False)
|
||||
did_install = True
|
||||
else:
|
||||
# gateway_windows.install() registers the Scheduled
|
||||
# Task AND starts it (schtasks /Run or direct-spawn
|
||||
# fallback), so no separate start prompt is needed.
|
||||
from hermes_cli import gateway_windows
|
||||
gateway_windows.install(force=False)
|
||||
did_install = True
|
||||
started_inline = True
|
||||
print()
|
||||
if did_install and not started_inline and prompt_yes_no(" Start the service now?", True):
|
||||
if did_install and start_now:
|
||||
try:
|
||||
if supports_systemd_services():
|
||||
systemd_start(system=installed_scope == "system")
|
||||
else:
|
||||
elif is_macos():
|
||||
launchd_start()
|
||||
elif is_windows():
|
||||
from hermes_cli import gateway_windows
|
||||
gateway_windows.start()
|
||||
except UserSystemdUnavailableError as e:
|
||||
print_error(" Start failed — user systemd not reachable:")
|
||||
for line in str(e).splitlines():
|
||||
@@ -4982,6 +4996,7 @@ def gateway_setup():
|
||||
print_error(f" Install failed: {e}")
|
||||
print_info(" You can try manually: hermes gateway install")
|
||||
else:
|
||||
print_info(" Skipped start and auto-start setup.")
|
||||
print_info(" You can install later: hermes gateway install")
|
||||
if supports_systemd_services():
|
||||
print_info(" Or as a boot-time service: sudo hermes gateway install --system")
|
||||
@@ -5064,12 +5079,26 @@ def _gateway_command_inner(args):
|
||||
print_info(" Consider running in foreground instead: hermes gateway run")
|
||||
print_info(" Or use tmux/screen for persistence: tmux new -s hermes 'hermes gateway run'")
|
||||
print()
|
||||
systemd_install(force=force, system=system, run_as_user=run_as_user)
|
||||
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)
|
||||
systemd_install(
|
||||
force=force,
|
||||
system=system,
|
||||
run_as_user=run_as_user,
|
||||
enable_on_startup=start_on_login,
|
||||
)
|
||||
if start_now:
|
||||
systemd_start(system=system)
|
||||
elif is_macos():
|
||||
launchd_install(force)
|
||||
elif is_windows():
|
||||
from hermes_cli import gateway_windows
|
||||
gateway_windows.install(force=force)
|
||||
gateway_windows.install(
|
||||
force=force,
|
||||
start_now=getattr(args, 'start_now', None),
|
||||
start_on_login=getattr(args, 'start_on_login', None),
|
||||
elevated_handoff=getattr(args, 'elevated_handoff', False),
|
||||
)
|
||||
elif is_wsl():
|
||||
print("WSL detected but systemd is not running.")
|
||||
print("Either enable systemd (add systemd=true to /etc/wsl.conf and restart WSL)")
|
||||
|
||||
Reference in New Issue
Block a user