mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(termux): disable gateway service flows on android
This commit is contained in:
parent
4e40e93b98
commit
3878495972
7 changed files with 153 additions and 48 deletions
|
|
@ -39,7 +39,7 @@ def _get_service_pids() -> set:
|
||||||
pids: set = set()
|
pids: set = set()
|
||||||
|
|
||||||
# --- systemd (Linux): user and system scopes ---
|
# --- systemd (Linux): user and system scopes ---
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
for scope_args in [["systemctl", "--user"], ["systemctl"]]:
|
for scope_args in [["systemctl", "--user"], ["systemctl"]]:
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|
@ -225,6 +225,16 @@ def stop_profile_gateway() -> bool:
|
||||||
def is_linux() -> bool:
|
def is_linux() -> bool:
|
||||||
return sys.platform.startswith('linux')
|
return sys.platform.startswith('linux')
|
||||||
|
|
||||||
|
|
||||||
|
def is_termux() -> bool:
|
||||||
|
prefix = os.getenv("PREFIX", "")
|
||||||
|
return bool(os.getenv("TERMUX_VERSION") or "com.termux/files/usr" in prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def supports_systemd_services() -> bool:
|
||||||
|
return is_linux() and not is_termux()
|
||||||
|
|
||||||
|
|
||||||
def is_macos() -> bool:
|
def is_macos() -> bool:
|
||||||
return sys.platform == 'darwin'
|
return sys.platform == 'darwin'
|
||||||
|
|
||||||
|
|
@ -477,13 +487,15 @@ def install_linux_gateway_from_setup(force: bool = False) -> tuple[str | None, b
|
||||||
|
|
||||||
|
|
||||||
def get_systemd_linger_status() -> tuple[bool | None, str]:
|
def get_systemd_linger_status() -> tuple[bool | None, str]:
|
||||||
"""Return whether systemd user lingering is enabled for the current user.
|
"""Return systemd linger status for the current user.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(True, "") when linger is enabled.
|
(True, "") when linger is enabled.
|
||||||
(False, "") when linger is disabled.
|
(False, "") when linger is disabled.
|
||||||
(None, detail) when the status could not be determined.
|
(None, detail) when the status could not be determined.
|
||||||
"""
|
"""
|
||||||
|
if is_termux():
|
||||||
|
return None, "not supported in Termux"
|
||||||
if not is_linux():
|
if not is_linux():
|
||||||
return None, "not supported on this platform"
|
return None, "not supported on this platform"
|
||||||
|
|
||||||
|
|
@ -766,7 +778,7 @@ def _print_linger_enable_warning(username: str, detail: str | None = None) -> No
|
||||||
|
|
||||||
def _ensure_linger_enabled() -> None:
|
def _ensure_linger_enabled() -> None:
|
||||||
"""Enable linger when possible so the user gateway survives logout."""
|
"""Enable linger when possible so the user gateway survives logout."""
|
||||||
if not is_linux():
|
if is_termux() or not is_linux():
|
||||||
return
|
return
|
||||||
|
|
||||||
import getpass
|
import getpass
|
||||||
|
|
@ -1801,7 +1813,7 @@ def _setup_whatsapp():
|
||||||
|
|
||||||
def _is_service_installed() -> bool:
|
def _is_service_installed() -> bool:
|
||||||
"""Check if the gateway is installed as a system service."""
|
"""Check if the gateway is installed as a system service."""
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
return get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()
|
return get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
return get_launchd_plist_path().exists()
|
return get_launchd_plist_path().exists()
|
||||||
|
|
@ -1810,7 +1822,7 @@ def _is_service_installed() -> bool:
|
||||||
|
|
||||||
def _is_service_running() -> bool:
|
def _is_service_running() -> bool:
|
||||||
"""Check if the gateway service is currently running."""
|
"""Check if the gateway service is currently running."""
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
user_unit_exists = get_systemd_unit_path(system=False).exists()
|
user_unit_exists = get_systemd_unit_path(system=False).exists()
|
||||||
system_unit_exists = get_systemd_unit_path(system=True).exists()
|
system_unit_exists = get_systemd_unit_path(system=True).exists()
|
||||||
|
|
||||||
|
|
@ -1983,7 +1995,7 @@ def gateway_setup():
|
||||||
service_installed = _is_service_installed()
|
service_installed = _is_service_installed()
|
||||||
service_running = _is_service_running()
|
service_running = _is_service_running()
|
||||||
|
|
||||||
if is_linux() and has_conflicting_systemd_units():
|
if supports_systemd_services() and has_conflicting_systemd_units():
|
||||||
print_systemd_scope_conflict_warning()
|
print_systemd_scope_conflict_warning()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
@ -1993,7 +2005,7 @@ def gateway_setup():
|
||||||
print_warning("Gateway service is installed but not running.")
|
print_warning("Gateway service is installed but not running.")
|
||||||
if prompt_yes_no(" Start it now?", True):
|
if prompt_yes_no(" Start it now?", True):
|
||||||
try:
|
try:
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
systemd_start()
|
systemd_start()
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
launchd_start()
|
launchd_start()
|
||||||
|
|
@ -2044,7 +2056,7 @@ def gateway_setup():
|
||||||
if service_running:
|
if service_running:
|
||||||
if prompt_yes_no(" Restart the gateway to pick up changes?", True):
|
if prompt_yes_no(" Restart the gateway to pick up changes?", True):
|
||||||
try:
|
try:
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
systemd_restart()
|
systemd_restart()
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
launchd_restart()
|
launchd_restart()
|
||||||
|
|
@ -2056,7 +2068,7 @@ def gateway_setup():
|
||||||
elif service_installed:
|
elif service_installed:
|
||||||
if prompt_yes_no(" Start the gateway service?", True):
|
if prompt_yes_no(" Start the gateway service?", True):
|
||||||
try:
|
try:
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
systemd_start()
|
systemd_start()
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
launchd_start()
|
launchd_start()
|
||||||
|
|
@ -2064,13 +2076,13 @@ def gateway_setup():
|
||||||
print_error(f" Start failed: {e}")
|
print_error(f" Start failed: {e}")
|
||||||
else:
|
else:
|
||||||
print()
|
print()
|
||||||
if is_linux() or is_macos():
|
if supports_systemd_services() or is_macos():
|
||||||
platform_name = "systemd" if is_linux() else "launchd"
|
platform_name = "systemd" if supports_systemd_services() else "launchd"
|
||||||
if prompt_yes_no(f" Install the gateway as a {platform_name} service? (runs in background, starts on boot)", True):
|
if prompt_yes_no(f" Install the gateway as a {platform_name} service? (runs in background, starts on boot)", True):
|
||||||
try:
|
try:
|
||||||
installed_scope = None
|
installed_scope = None
|
||||||
did_install = False
|
did_install = False
|
||||||
if is_linux():
|
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)
|
||||||
else:
|
else:
|
||||||
launchd_install(force=False)
|
launchd_install(force=False)
|
||||||
|
|
@ -2078,7 +2090,7 @@ def gateway_setup():
|
||||||
print()
|
print()
|
||||||
if did_install and prompt_yes_no(" Start the service now?", True):
|
if did_install and prompt_yes_no(" Start the service now?", True):
|
||||||
try:
|
try:
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
systemd_start(system=installed_scope == "system")
|
systemd_start(system=installed_scope == "system")
|
||||||
else:
|
else:
|
||||||
launchd_start()
|
launchd_start()
|
||||||
|
|
@ -2089,12 +2101,18 @@ def gateway_setup():
|
||||||
print_info(" You can try manually: hermes gateway install")
|
print_info(" You can try manually: hermes gateway install")
|
||||||
else:
|
else:
|
||||||
print_info(" You can install later: hermes gateway install")
|
print_info(" You can install later: hermes gateway install")
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
print_info(" Or as a boot-time service: sudo hermes gateway install --system")
|
print_info(" Or as a boot-time service: sudo hermes gateway install --system")
|
||||||
print_info(" Or run in foreground: hermes gateway")
|
print_info(" Or run in foreground: hermes gateway")
|
||||||
else:
|
else:
|
||||||
print_info(" Service install not supported on this platform.")
|
if is_termux():
|
||||||
print_info(" Run in foreground: hermes gateway")
|
from hermes_constants import display_hermes_home as _dhh
|
||||||
|
print_info(" Termux does not use systemd/launchd services.")
|
||||||
|
print_info(" Run in foreground: hermes gateway")
|
||||||
|
print_info(f" Or start it manually in the background (best effort): nohup hermes gateway >{_dhh()}/logs/gateway.log 2>&1 &")
|
||||||
|
else:
|
||||||
|
print_info(" Service install not supported on this platform.")
|
||||||
|
print_info(" Run in foreground: hermes gateway")
|
||||||
else:
|
else:
|
||||||
print()
|
print()
|
||||||
print_info("No platforms configured. Run 'hermes gateway setup' when ready.")
|
print_info("No platforms configured. Run 'hermes gateway setup' when ready.")
|
||||||
|
|
@ -2130,7 +2148,11 @@ def gateway_command(args):
|
||||||
force = getattr(args, 'force', False)
|
force = getattr(args, 'force', False)
|
||||||
system = getattr(args, 'system', False)
|
system = getattr(args, 'system', False)
|
||||||
run_as_user = getattr(args, 'run_as_user', None)
|
run_as_user = getattr(args, 'run_as_user', None)
|
||||||
if is_linux():
|
if is_termux():
|
||||||
|
print("Gateway service installation is not supported on Termux.")
|
||||||
|
print("Run manually: hermes gateway")
|
||||||
|
sys.exit(1)
|
||||||
|
if supports_systemd_services():
|
||||||
systemd_install(force=force, system=system, run_as_user=run_as_user)
|
systemd_install(force=force, system=system, run_as_user=run_as_user)
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
launchd_install(force)
|
launchd_install(force)
|
||||||
|
|
@ -2144,7 +2166,11 @@ def gateway_command(args):
|
||||||
managed_error("uninstall gateway service (managed by NixOS)")
|
managed_error("uninstall gateway service (managed by NixOS)")
|
||||||
return
|
return
|
||||||
system = getattr(args, 'system', False)
|
system = getattr(args, 'system', False)
|
||||||
if is_linux():
|
if is_termux():
|
||||||
|
print("Gateway service uninstall is not supported on Termux because there is no managed service to remove.")
|
||||||
|
print("Stop manual runs with: hermes gateway stop")
|
||||||
|
sys.exit(1)
|
||||||
|
if supports_systemd_services():
|
||||||
systemd_uninstall(system=system)
|
systemd_uninstall(system=system)
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
launchd_uninstall()
|
launchd_uninstall()
|
||||||
|
|
@ -2154,7 +2180,11 @@ def gateway_command(args):
|
||||||
|
|
||||||
elif subcmd == "start":
|
elif subcmd == "start":
|
||||||
system = getattr(args, 'system', False)
|
system = getattr(args, 'system', False)
|
||||||
if is_linux():
|
if is_termux():
|
||||||
|
print("Gateway service start is not supported on Termux because there is no system service manager.")
|
||||||
|
print("Run manually: hermes gateway")
|
||||||
|
sys.exit(1)
|
||||||
|
if supports_systemd_services():
|
||||||
systemd_start(system=system)
|
systemd_start(system=system)
|
||||||
elif is_macos():
|
elif is_macos():
|
||||||
launchd_start()
|
launchd_start()
|
||||||
|
|
@ -2169,7 +2199,7 @@ def gateway_command(args):
|
||||||
if stop_all:
|
if stop_all:
|
||||||
# --all: kill every gateway process on the machine
|
# --all: kill every gateway process on the machine
|
||||||
service_available = False
|
service_available = False
|
||||||
if is_linux() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
if supports_systemd_services() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
||||||
try:
|
try:
|
||||||
systemd_stop(system=system)
|
systemd_stop(system=system)
|
||||||
service_available = True
|
service_available = True
|
||||||
|
|
@ -2190,7 +2220,7 @@ def gateway_command(args):
|
||||||
else:
|
else:
|
||||||
# Default: stop only the current profile's gateway
|
# Default: stop only the current profile's gateway
|
||||||
service_available = False
|
service_available = False
|
||||||
if is_linux() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
if supports_systemd_services() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
||||||
try:
|
try:
|
||||||
systemd_stop(system=system)
|
systemd_stop(system=system)
|
||||||
service_available = True
|
service_available = True
|
||||||
|
|
@ -2218,7 +2248,7 @@ def gateway_command(args):
|
||||||
system = getattr(args, 'system', False)
|
system = getattr(args, 'system', False)
|
||||||
service_configured = False
|
service_configured = False
|
||||||
|
|
||||||
if is_linux() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
if supports_systemd_services() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
||||||
service_configured = True
|
service_configured = True
|
||||||
try:
|
try:
|
||||||
systemd_restart(system=system)
|
systemd_restart(system=system)
|
||||||
|
|
@ -2235,7 +2265,7 @@ def gateway_command(args):
|
||||||
|
|
||||||
if not service_available:
|
if not service_available:
|
||||||
# systemd/launchd restart failed — check if linger is the issue
|
# systemd/launchd restart failed — check if linger is the issue
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
linger_ok, _detail = get_systemd_linger_status()
|
linger_ok, _detail = get_systemd_linger_status()
|
||||||
if linger_ok is not True:
|
if linger_ok is not True:
|
||||||
import getpass
|
import getpass
|
||||||
|
|
@ -2272,7 +2302,7 @@ def gateway_command(args):
|
||||||
system = getattr(args, 'system', False)
|
system = getattr(args, 'system', False)
|
||||||
|
|
||||||
# Check for service first
|
# Check for service first
|
||||||
if is_linux() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
if supports_systemd_services() and (get_systemd_unit_path(system=False).exists() or get_systemd_unit_path(system=True).exists()):
|
||||||
systemd_status(deep, system=system)
|
systemd_status(deep, system=system)
|
||||||
elif is_macos() and get_launchd_plist_path().exists():
|
elif is_macos() and get_launchd_plist_path().exists():
|
||||||
launchd_status(deep)
|
launchd_status(deep)
|
||||||
|
|
@ -2289,9 +2319,13 @@ def gateway_command(args):
|
||||||
for line in runtime_lines:
|
for line in runtime_lines:
|
||||||
print(f" {line}")
|
print(f" {line}")
|
||||||
print()
|
print()
|
||||||
print("To install as a service:")
|
if is_termux():
|
||||||
print(" hermes gateway install")
|
print("Termux note:")
|
||||||
print(" sudo hermes gateway install --system")
|
print(" Android may stop background jobs when Termux is suspended")
|
||||||
|
else:
|
||||||
|
print("To install as a service:")
|
||||||
|
print(" hermes gateway install")
|
||||||
|
print(" sudo hermes gateway install --system")
|
||||||
else:
|
else:
|
||||||
print("✗ Gateway is not running")
|
print("✗ Gateway is not running")
|
||||||
runtime_lines = _runtime_health_lines()
|
runtime_lines = _runtime_health_lines()
|
||||||
|
|
@ -2303,5 +2337,8 @@ def gateway_command(args):
|
||||||
print()
|
print()
|
||||||
print("To start:")
|
print("To start:")
|
||||||
print(" hermes gateway # Run in foreground")
|
print(" hermes gateway # Run in foreground")
|
||||||
print(" hermes gateway install # Install as user service")
|
if is_termux():
|
||||||
print(" sudo hermes gateway install --system # Install as boot-time system service")
|
print(" nohup hermes gateway > ~/.hermes/logs/gateway.log 2>&1 & # Best-effort background start")
|
||||||
|
else:
|
||||||
|
print(" hermes gateway install # Install as user service")
|
||||||
|
print(" sudo hermes gateway install --system # Install as boot-time system service")
|
||||||
|
|
|
||||||
|
|
@ -3763,7 +3763,7 @@ def cmd_update(args):
|
||||||
# running gateway needs restarting to pick up the new code.
|
# running gateway needs restarting to pick up the new code.
|
||||||
try:
|
try:
|
||||||
from hermes_cli.gateway import (
|
from hermes_cli.gateway import (
|
||||||
is_macos, is_linux, _ensure_user_systemd_env,
|
is_macos, supports_systemd_services, _ensure_user_systemd_env,
|
||||||
find_gateway_pids,
|
find_gateway_pids,
|
||||||
_get_service_pids,
|
_get_service_pids,
|
||||||
)
|
)
|
||||||
|
|
@ -3774,7 +3774,7 @@ def cmd_update(args):
|
||||||
|
|
||||||
# --- Systemd services (Linux) ---
|
# --- Systemd services (Linux) ---
|
||||||
# Discover all hermes-gateway* units (default + profiles)
|
# Discover all hermes-gateway* units (default + profiles)
|
||||||
if is_linux():
|
if supports_systemd_services():
|
||||||
try:
|
try:
|
||||||
_ensure_user_systemd_env()
|
_ensure_user_systemd_env()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ def uninstall_gateway_service():
|
||||||
if platform.system() != "Linux":
|
if platform.system() != "Linux":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
prefix = os.getenv("PREFIX", "")
|
||||||
|
if os.getenv("TERMUX_VERSION") or "com.termux/files/usr" in prefix:
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from hermes_cli.gateway import get_service_name
|
from hermes_cli.gateway import get_service_name
|
||||||
svc_name = get_service_name()
|
svc_name = get_service_name()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import hermes_cli.gateway as gateway
|
||||||
class TestSystemdLingerStatus:
|
class TestSystemdLingerStatus:
|
||||||
def test_reports_enabled(self, monkeypatch):
|
def test_reports_enabled(self, monkeypatch):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setenv("USER", "alice")
|
monkeypatch.setenv("USER", "alice")
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway.subprocess,
|
gateway.subprocess,
|
||||||
|
|
@ -22,6 +23,7 @@ class TestSystemdLingerStatus:
|
||||||
|
|
||||||
def test_reports_disabled(self, monkeypatch):
|
def test_reports_disabled(self, monkeypatch):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setenv("USER", "alice")
|
monkeypatch.setenv("USER", "alice")
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway.subprocess,
|
gateway.subprocess,
|
||||||
|
|
@ -32,6 +34,11 @@ class TestSystemdLingerStatus:
|
||||||
|
|
||||||
assert gateway.get_systemd_linger_status() == (False, "")
|
assert gateway.get_systemd_linger_status() == (False, "")
|
||||||
|
|
||||||
|
def test_reports_termux_as_not_supported(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: True)
|
||||||
|
|
||||||
|
assert gateway.get_systemd_linger_status() == (None, "not supported in Termux")
|
||||||
|
|
||||||
|
|
||||||
def test_systemd_status_warns_when_linger_disabled(monkeypatch, tmp_path, capsys):
|
def test_systemd_status_warns_when_linger_disabled(monkeypatch, tmp_path, capsys):
|
||||||
unit_path = tmp_path / "hermes-gateway.service"
|
unit_path = tmp_path / "hermes-gateway.service"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import hermes_cli.gateway as gateway
|
||||||
class TestEnsureLingerEnabled:
|
class TestEnsureLingerEnabled:
|
||||||
def test_linger_already_enabled_via_file(self, monkeypatch, capsys):
|
def test_linger_already_enabled_via_file(self, monkeypatch, capsys):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
||||||
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: True))
|
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: True))
|
||||||
|
|
||||||
|
|
@ -22,6 +23,7 @@ class TestEnsureLingerEnabled:
|
||||||
|
|
||||||
def test_status_enabled_skips_enable(self, monkeypatch, capsys):
|
def test_status_enabled_skips_enable(self, monkeypatch, capsys):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
||||||
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
||||||
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (True, ""))
|
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (True, ""))
|
||||||
|
|
@ -37,6 +39,7 @@ class TestEnsureLingerEnabled:
|
||||||
|
|
||||||
def test_loginctl_success_enables_linger(self, monkeypatch, capsys):
|
def test_loginctl_success_enables_linger(self, monkeypatch, capsys):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
||||||
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
||||||
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (False, ""))
|
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (False, ""))
|
||||||
|
|
@ -59,6 +62,7 @@ class TestEnsureLingerEnabled:
|
||||||
|
|
||||||
def test_missing_loginctl_shows_manual_guidance(self, monkeypatch, capsys):
|
def test_missing_loginctl_shows_manual_guidance(self, monkeypatch, capsys):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
||||||
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
||||||
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (None, "loginctl not found"))
|
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (None, "loginctl not found"))
|
||||||
|
|
@ -76,6 +80,7 @@ class TestEnsureLingerEnabled:
|
||||||
|
|
||||||
def test_loginctl_failure_shows_manual_guidance(self, monkeypatch, capsys):
|
def test_loginctl_failure_shows_manual_guidance(self, monkeypatch, capsys):
|
||||||
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway, "is_linux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
monkeypatch.setattr("getpass.getuser", lambda: "testuser")
|
||||||
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
monkeypatch.setattr(gateway, "Path", lambda _path: SimpleNamespace(exists=lambda: False))
|
||||||
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (False, ""))
|
monkeypatch.setattr(gateway, "get_systemd_linger_status", lambda: (False, ""))
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,8 @@ class TestGatewayStopCleanup:
|
||||||
unit_path = tmp_path / "hermes-gateway.service"
|
unit_path = tmp_path / "hermes-gateway.service"
|
||||||
unit_path.write_text("unit\n", encoding="utf-8")
|
unit_path.write_text("unit\n", encoding="utf-8")
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda system=False: unit_path)
|
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda system=False: unit_path)
|
||||||
|
|
||||||
|
|
@ -134,7 +135,8 @@ class TestGatewayStopCleanup:
|
||||||
unit_path = tmp_path / "hermes-gateway.service"
|
unit_path = tmp_path / "hermes-gateway.service"
|
||||||
unit_path.write_text("unit\n", encoding="utf-8")
|
unit_path.write_text("unit\n", encoding="utf-8")
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda system=False: unit_path)
|
monkeypatch.setattr(gateway_cli, "get_systemd_unit_path", lambda system=False: unit_path)
|
||||||
|
|
||||||
|
|
@ -256,7 +258,8 @@ class TestGatewayServiceDetection:
|
||||||
user_unit = SimpleNamespace(exists=lambda: True)
|
user_unit = SimpleNamespace(exists=lambda: True)
|
||||||
system_unit = SimpleNamespace(exists=lambda: True)
|
system_unit = SimpleNamespace(exists=lambda: True)
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway_cli,
|
gateway_cli,
|
||||||
|
|
@ -278,7 +281,8 @@ class TestGatewayServiceDetection:
|
||||||
|
|
||||||
class TestGatewaySystemServiceRouting:
|
class TestGatewaySystemServiceRouting:
|
||||||
def test_gateway_install_passes_system_flags(self, monkeypatch):
|
def test_gateway_install_passes_system_flags(self, monkeypatch):
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
|
|
||||||
calls = []
|
calls = []
|
||||||
|
|
@ -294,11 +298,30 @@ class TestGatewaySystemServiceRouting:
|
||||||
|
|
||||||
assert calls == [(True, True, "alice")]
|
assert calls == [(True, True, "alice")]
|
||||||
|
|
||||||
|
def test_gateway_install_reports_termux_manual_mode(self, monkeypatch, capsys):
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: False)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
gateway_cli.gateway_command(
|
||||||
|
SimpleNamespace(gateway_command="install", force=False, system=False, run_as_user=None)
|
||||||
|
)
|
||||||
|
except SystemExit as exc:
|
||||||
|
assert exc.code == 1
|
||||||
|
else:
|
||||||
|
raise AssertionError("Expected gateway_command to exit on unsupported Termux service install")
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "not supported on Termux" in out
|
||||||
|
assert "Run manually: hermes gateway" in out
|
||||||
|
|
||||||
def test_gateway_status_prefers_system_service_when_only_system_unit_exists(self, monkeypatch):
|
def test_gateway_status_prefers_system_service_when_only_system_unit_exists(self, monkeypatch):
|
||||||
user_unit = SimpleNamespace(exists=lambda: False)
|
user_unit = SimpleNamespace(exists=lambda: False)
|
||||||
system_unit = SimpleNamespace(exists=lambda: True)
|
system_unit = SimpleNamespace(exists=lambda: True)
|
||||||
|
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway_cli,
|
gateway_cli,
|
||||||
|
|
@ -313,6 +336,20 @@ class TestGatewaySystemServiceRouting:
|
||||||
|
|
||||||
assert calls == [(False, False)]
|
assert calls == [(False, False)]
|
||||||
|
|
||||||
|
def test_gateway_status_on_termux_shows_manual_guidance(self, monkeypatch, capsys):
|
||||||
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: False)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
|
monkeypatch.setattr(gateway_cli, "find_gateway_pids", lambda exclude_pids=None: [])
|
||||||
|
monkeypatch.setattr(gateway_cli, "_runtime_health_lines", lambda: [])
|
||||||
|
|
||||||
|
gateway_cli.gateway_command(SimpleNamespace(gateway_command="status", deep=False, system=False))
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "Gateway is not running" in out
|
||||||
|
assert "nohup hermes gateway" in out
|
||||||
|
assert "install as user service" not in out
|
||||||
|
|
||||||
def test_gateway_restart_does_not_fallback_to_foreground_when_launchd_restart_fails(self, tmp_path, monkeypatch):
|
def test_gateway_restart_does_not_fallback_to_foreground_when_launchd_restart_fails(self, tmp_path, monkeypatch):
|
||||||
plist_path = tmp_path / "ai.hermes.gateway.plist"
|
plist_path = tmp_path / "ai.hermes.gateway.plist"
|
||||||
plist_path.write_text("plist\n", encoding="utf-8")
|
plist_path.write_text("plist\n", encoding="utf-8")
|
||||||
|
|
@ -513,12 +550,22 @@ class TestGeneratedUnitUsesDetectedVenv:
|
||||||
class TestGeneratedUnitIncludesLocalBin:
|
class TestGeneratedUnitIncludesLocalBin:
|
||||||
"""~/.local/bin must be in PATH so uvx/pipx tools are discoverable."""
|
"""~/.local/bin must be in PATH so uvx/pipx tools are discoverable."""
|
||||||
|
|
||||||
def test_user_unit_includes_local_bin_in_path(self):
|
def test_user_unit_includes_local_bin_in_path(self, monkeypatch):
|
||||||
|
home = Path.home()
|
||||||
|
monkeypatch.setattr(
|
||||||
|
gateway_cli,
|
||||||
|
"_build_user_local_paths",
|
||||||
|
lambda home_path, existing: [str(home / ".local" / "bin")],
|
||||||
|
)
|
||||||
unit = gateway_cli.generate_systemd_unit(system=False)
|
unit = gateway_cli.generate_systemd_unit(system=False)
|
||||||
home = str(Path.home())
|
|
||||||
assert f"{home}/.local/bin" in unit
|
assert f"{home}/.local/bin" in unit
|
||||||
|
|
||||||
def test_system_unit_includes_local_bin_in_path(self):
|
def test_system_unit_includes_local_bin_in_path(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
gateway_cli,
|
||||||
|
"_build_user_local_paths",
|
||||||
|
lambda home_path, existing: [str(home_path / ".local" / "bin")],
|
||||||
|
)
|
||||||
unit = gateway_cli.generate_systemd_unit(system=True)
|
unit = gateway_cli.generate_systemd_unit(system=True)
|
||||||
# System unit uses the resolved home dir from _system_service_identity
|
# System unit uses the resolved home dir from _system_service_identity
|
||||||
assert "/.local/bin" in unit
|
assert "/.local/bin" in unit
|
||||||
|
|
|
||||||
|
|
@ -368,9 +368,8 @@ class TestCmdUpdateLaunchdRestart:
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
gateway_cli, "is_macos", lambda: False,
|
gateway_cli, "is_macos", lambda: False,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
gateway_cli, "is_linux", lambda: True,
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
)
|
|
||||||
|
|
||||||
mock_run.side_effect = _make_run_side_effect(
|
mock_run.side_effect = _make_run_side_effect(
|
||||||
commit_count="3",
|
commit_count="3",
|
||||||
|
|
@ -429,7 +428,8 @@ class TestCmdUpdateSystemService:
|
||||||
):
|
):
|
||||||
"""When user systemd is inactive but a system service exists, restart via system scope."""
|
"""When user systemd is inactive but a system service exists, restart via system scope."""
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
|
|
||||||
mock_run.side_effect = _make_run_side_effect(
|
mock_run.side_effect = _make_run_side_effect(
|
||||||
commit_count="3",
|
commit_count="3",
|
||||||
|
|
@ -458,7 +458,8 @@ class TestCmdUpdateSystemService:
|
||||||
):
|
):
|
||||||
"""When system service restart fails, show the failure message."""
|
"""When system service restart fails, show the failure message."""
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
|
|
||||||
mock_run.side_effect = _make_run_side_effect(
|
mock_run.side_effect = _make_run_side_effect(
|
||||||
commit_count="3",
|
commit_count="3",
|
||||||
|
|
@ -480,7 +481,8 @@ class TestCmdUpdateSystemService:
|
||||||
):
|
):
|
||||||
"""When both user and system services are active, both are restarted."""
|
"""When both user and system services are active, both are restarted."""
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
|
|
||||||
mock_run.side_effect = _make_run_side_effect(
|
mock_run.side_effect = _make_run_side_effect(
|
||||||
commit_count="3",
|
commit_count="3",
|
||||||
|
|
@ -563,7 +565,8 @@ class TestServicePidExclusion:
|
||||||
):
|
):
|
||||||
"""After systemd restart, the sweep must exclude the service PID."""
|
"""After systemd restart, the sweep must exclude the service PID."""
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
|
|
||||||
SERVICE_PID = 55000
|
SERVICE_PID = 55000
|
||||||
|
|
||||||
|
|
@ -642,7 +645,8 @@ class TestGetServicePids:
|
||||||
"""Unit tests for _get_service_pids()."""
|
"""Unit tests for _get_service_pids()."""
|
||||||
|
|
||||||
def test_returns_systemd_main_pid(self, monkeypatch):
|
def test_returns_systemd_main_pid(self, monkeypatch):
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
|
|
||||||
def fake_run(cmd, **kwargs):
|
def fake_run(cmd, **kwargs):
|
||||||
|
|
@ -691,7 +695,8 @@ class TestGetServicePids:
|
||||||
|
|
||||||
def test_excludes_zero_pid(self, monkeypatch):
|
def test_excludes_zero_pid(self, monkeypatch):
|
||||||
"""systemd returns MainPID=0 for stopped services; skip those."""
|
"""systemd returns MainPID=0 for stopped services; skip those."""
|
||||||
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
|
monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
|
||||||
|
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
|
||||||
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
monkeypatch.setattr(gateway_cli, "is_macos", lambda: False)
|
||||||
|
|
||||||
def fake_run(cmd, **kwargs):
|
def fake_run(cmd, **kwargs):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue