Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions hermes_cli/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def is_linux() -> bool:


def supports_systemd_services() -> bool:
return is_linux() and not is_termux()
return is_linux() and not is_termux() and shutil.which("systemctl") is not None


def is_macos() -> bool:
Expand Down Expand Up @@ -1871,7 +1871,7 @@ def _is_service_running() -> bool:
)
if result.stdout.strip() == "active":
return True
except subprocess.TimeoutExpired:
except (FileNotFoundError, subprocess.TimeoutExpired):
pass

if system_unit_exists:
Expand All @@ -1882,7 +1882,7 @@ def _is_service_running() -> bool:
)
if result.stdout.strip() == "active":
return True
except subprocess.TimeoutExpired:
except (FileNotFoundError, subprocess.TimeoutExpired):
pass

return False
Expand Down
19 changes: 11 additions & 8 deletions hermes_cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2243,6 +2243,7 @@ def setup_gateway(config: dict):
from hermes_cli.gateway import (
_is_service_installed,
_is_service_running,
supports_systemd_services,
has_conflicting_systemd_units,
install_linux_gateway_from_setup,
print_systemd_scope_conflict_warning,
Expand All @@ -2255,16 +2256,18 @@ def setup_gateway(config: dict):

service_installed = _is_service_installed()
service_running = _is_service_running()
supports_systemd = supports_systemd_services()
supports_service_manager = supports_systemd or _is_macos

print()
if _is_linux and has_conflicting_systemd_units():
if supports_systemd and has_conflicting_systemd_units():
print_systemd_scope_conflict_warning()
print()

if service_running:
if prompt_yes_no(" Restart the gateway to pick up changes?", True):
try:
if _is_linux:
if supports_systemd:
systemd_restart()
elif _is_macos:
launchd_restart()
Expand All @@ -2273,30 +2276,30 @@ def setup_gateway(config: dict):
elif service_installed:
if prompt_yes_no(" Start the gateway service?", True):
try:
if _is_linux:
if supports_systemd:
systemd_start()
elif _is_macos:
launchd_start()
except Exception as e:
print_error(f" Start failed: {e}")
elif _is_linux or _is_macos:
svc_name = "systemd" if _is_linux else "launchd"
elif supports_service_manager:
svc_name = "systemd" if supports_systemd else "launchd"
if prompt_yes_no(
f" Install the gateway as a {svc_name} service? (runs in background, starts on boot)",
True,
):
try:
installed_scope = None
did_install = False
if _is_linux:
if supports_systemd:
installed_scope, did_install = install_linux_gateway_from_setup(force=False)
else:
launchd_install(force=False)
did_install = True
print()
if did_install and prompt_yes_no(" Start the service now?", True):
try:
if _is_linux:
if supports_systemd:
systemd_start(system=installed_scope == "system")
elif _is_macos:
launchd_start()
Expand All @@ -2307,7 +2310,7 @@ def setup_gateway(config: dict):
print_info(" You can try manually: hermes gateway install")
else:
print_info(" You can install later: hermes gateway install")
if _is_linux:
if supports_systemd:
print_info(" Or as a boot-time service: sudo hermes gateway install --system")
print_info(" Or run in foreground: hermes gateway")
else:
Expand Down
24 changes: 24 additions & 0 deletions tests/hermes_cli/test_gateway_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@ def test_launchd_status_reports_local_stale_plist_when_unloaded(self, tmp_path,


class TestGatewayServiceDetection:
def test_supports_systemd_services_requires_systemctl_binary(self, monkeypatch):
monkeypatch.setattr(gateway_cli, "is_linux", lambda: True)
monkeypatch.setattr(gateway_cli, "is_termux", lambda: False)
monkeypatch.setattr(gateway_cli.shutil, "which", lambda name: None)

assert gateway_cli.supports_systemd_services() is False

def test_is_service_running_checks_system_scope_when_user_scope_is_inactive(self, monkeypatch):
user_unit = SimpleNamespace(exists=lambda: True)
system_unit = SimpleNamespace(exists=lambda: True)
Expand All @@ -335,6 +342,23 @@ def fake_run(cmd, capture_output=True, text=True, **kwargs):

assert gateway_cli._is_service_running() is True

def test_is_service_running_returns_false_when_systemctl_missing(self, monkeypatch):
unit = SimpleNamespace(exists=lambda: True)

monkeypatch.setattr(gateway_cli, "supports_systemd_services", lambda: True)
monkeypatch.setattr(
gateway_cli,
"get_systemd_unit_path",
lambda system=False: unit,
)

def fake_run(*args, **kwargs):
raise FileNotFoundError("systemctl")

monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run)

assert gateway_cli._is_service_running() is False


class TestGatewaySystemServiceRouting:
def test_gateway_install_passes_system_flags(self, monkeypatch):
Expand Down
41 changes: 39 additions & 2 deletions tests/hermes_cli/test_setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Tests for setup_model_provider — verifies the delegation to
select_provider_and_model() and config dict sync."""
"""Tests for setup.py configuration flows."""
import json
import sys
import types

from hermes_cli.auth import get_active_provider
from hermes_cli.config import load_config, save_config
from hermes_cli import setup as setup_mod
from hermes_cli.setup import setup_model_provider


Expand Down Expand Up @@ -142,6 +142,43 @@ def fake_select():
assert reloaded.get("custom_providers") == [{"name": "Local", "base_url": "http://localhost:8080/v1"}]


def test_setup_gateway_skips_service_install_when_systemctl_missing(monkeypatch, capsys):
env = {
"TELEGRAM_BOT_TOKEN": "",
"TELEGRAM_HOME_CHANNEL": "",
"DISCORD_BOT_TOKEN": "",
"DISCORD_HOME_CHANNEL": "",
"SLACK_BOT_TOKEN": "",
"SLACK_HOME_CHANNEL": "",
"MATRIX_HOMESERVER": "https://matrix.example.com",
"MATRIX_USER_ID": "@alice:example.com",
"MATRIX_PASSWORD": "",
"MATRIX_ACCESS_TOKEN": "token",
"BLUEBUBBLES_SERVER_URL": "",
"BLUEBUBBLES_HOME_CHANNEL": "",
"WHATSAPP_ENABLED": "",
"WEBHOOK_ENABLED": "",
}

monkeypatch.setattr(setup_mod, "get_env_value", lambda key: env.get(key, ""))
monkeypatch.setattr(setup_mod, "prompt_yes_no", lambda *args, **kwargs: False)
monkeypatch.setattr("platform.system", lambda: "Linux")

import hermes_cli.gateway as gateway_mod

monkeypatch.setattr(gateway_mod, "supports_systemd_services", lambda: False)
monkeypatch.setattr(gateway_mod, "is_macos", lambda: False)
monkeypatch.setattr(gateway_mod, "_is_service_installed", lambda: False)
monkeypatch.setattr(gateway_mod, "_is_service_running", lambda: False)

setup_mod.setup_gateway({})

out = capsys.readouterr().out
assert "Messaging platforms configured!" in out
assert "Start the gateway to bring your bots online:" in out
assert "hermes gateway" in out


def test_setup_syncs_custom_provider_removal_from_disk(tmp_path, monkeypatch):
"""Removing the last custom provider in model setup should persist."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
Expand Down
Loading