mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(gateway): only offer system-scope gateway install to root sessions (#53975)
Non-root users picking 'System service' in the setup wizard were handed a 'sudo hermes gateway install --system --run-as-user <you>' recipe that fails on most distros: sudo's secure_path strips ~/.local/bin (pipx/uv installs), so 'sudo hermes' is command-not-found. Worse, it funnels a non-root user toward a system install they shouldn't be doing from a user session. Now prompt_linux_gateway_install_scope() only offers system scope when os.geteuid()==0. Non-root sessions get user-service or skip, with a tip to re-run as root for a boot service. The non-root branch in install_linux_gateway_from_setup becomes a defensive guard that refuses without printing any self-elevation recipe. Gated the matching deferral hint in setup.py behind root too.
This commit is contained in:
parent
b304023fc6
commit
4626ceb747
3 changed files with 56 additions and 16 deletions
|
|
@ -2265,11 +2265,33 @@ def _default_system_service_user() -> str | None:
|
|||
|
||||
|
||||
def prompt_linux_gateway_install_scope() -> str | None:
|
||||
# A boot-time system service has to be created by root (writing the unit to
|
||||
# /etc/systemd/system). We only offer that scope when the session is already
|
||||
# root — a non-root user is never handed a "re-run yourself under sudo"
|
||||
# recipe, since that just funnels them into a system install they can't
|
||||
# actually perform from here. Non-root sessions get the user service.
|
||||
is_root = os.geteuid() == 0 # windows-footgun: ok — Linux systemd install wizard, never invoked on Windows
|
||||
if not is_root:
|
||||
choice = prompt_choice(
|
||||
" Choose how the gateway should run in the background:",
|
||||
[
|
||||
"User service (no sudo; best for laptops/dev boxes; may need linger after logout)",
|
||||
"Skip service install for now",
|
||||
],
|
||||
default=0,
|
||||
)
|
||||
if choice == 0:
|
||||
print_info(
|
||||
" Tip: for a boot-time system service, re-run setup as root "
|
||||
"(e.g. from a root shell or `sudo -i`)."
|
||||
)
|
||||
return {0: "user", 1: None}[choice]
|
||||
|
||||
choice = prompt_choice(
|
||||
" Choose how the gateway should run in the background:",
|
||||
[
|
||||
"User service (no sudo; best for laptops/dev boxes; may need linger after logout)",
|
||||
"System service (starts on boot; requires sudo; still runs as your user)",
|
||||
"System service (starts on boot; runs as your chosen user)",
|
||||
"Skip service install for now",
|
||||
],
|
||||
default=0,
|
||||
|
|
@ -2285,18 +2307,13 @@ def install_linux_gateway_from_setup(force: bool = False, enable_on_startup: boo
|
|||
if scope == "system":
|
||||
run_as_user = _default_system_service_user()
|
||||
if os.geteuid() != 0: # windows-footgun: ok — Linux systemd install wizard, never invoked on Windows
|
||||
# Unreachable from the wizard: prompt_linux_gateway_install_scope()
|
||||
# only offers "system" to root sessions. Defensive guard for any
|
||||
# direct caller — we do NOT print a self-elevation recipe.
|
||||
print_warning(
|
||||
" System service install requires sudo, so Hermes can't create it from this user session."
|
||||
" System service install requires root. Re-run setup from a "
|
||||
"root shell, or install a user service instead: hermes gateway install"
|
||||
)
|
||||
if run_as_user:
|
||||
print_info(
|
||||
f" After setup, run: sudo hermes gateway install --system --run-as-user {run_as_user}"
|
||||
)
|
||||
else:
|
||||
print_info(
|
||||
" After setup, run: sudo hermes gateway install --system --run-as-user <your-user>"
|
||||
)
|
||||
print_info(" Then start it with: sudo hermes gateway start --system")
|
||||
return scope, False
|
||||
|
||||
if not run_as_user:
|
||||
|
|
|
|||
|
|
@ -2171,8 +2171,8 @@ 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 supports_systemd:
|
||||
print_info(" Or as a boot-time service: sudo hermes gateway install --system")
|
||||
if supports_systemd and os.geteuid() == 0: # windows-footgun: ok — guarded by supports_systemd (Linux only)
|
||||
print_info(" Or as a boot-time service: hermes gateway install --system")
|
||||
print_info(" Or run in foreground: hermes gateway")
|
||||
else:
|
||||
from hermes_constants import is_container
|
||||
|
|
|
|||
|
|
@ -720,7 +720,30 @@ def test_conflicting_systemd_units_warning(monkeypatch, tmp_path, capsys):
|
|||
assert "--system" in out
|
||||
|
||||
|
||||
def test_install_linux_gateway_from_setup_system_choice_without_root_prints_followup(monkeypatch, capsys):
|
||||
def test_install_linux_gateway_from_setup_non_root_never_offers_system(monkeypatch, capsys):
|
||||
# Non-root sessions must not be offered system scope, and must never be
|
||||
# handed a `sudo hermes …` self-elevation recipe.
|
||||
captured = {}
|
||||
|
||||
def fake_prompt_choice(_msg, options, default=0):
|
||||
captured["options"] = options
|
||||
return 0 # pick "user"
|
||||
|
||||
monkeypatch.setattr(gateway.os, "geteuid", lambda: 1000)
|
||||
monkeypatch.setattr(gateway, "prompt_choice", fake_prompt_choice)
|
||||
monkeypatch.setattr(gateway, "systemd_install", lambda *a, **k: None)
|
||||
|
||||
scope = gateway.prompt_linux_gateway_install_scope()
|
||||
out = capsys.readouterr().out
|
||||
|
||||
assert scope == "user"
|
||||
assert not any("System service" in opt for opt in captured["options"])
|
||||
assert "sudo hermes" not in out
|
||||
|
||||
|
||||
def test_install_linux_gateway_from_setup_system_choice_without_root_no_sudo_recipe(monkeypatch, capsys):
|
||||
# Defensive guard: if "system" is forced non-root (not reachable via wizard),
|
||||
# we refuse and do NOT print a self-elevation recipe.
|
||||
monkeypatch.setattr(gateway, "prompt_linux_gateway_install_scope", lambda: "system")
|
||||
monkeypatch.setattr(gateway.os, "geteuid", lambda: 1000)
|
||||
monkeypatch.setattr(gateway, "_default_system_service_user", lambda: "alice")
|
||||
|
|
@ -730,8 +753,8 @@ def test_install_linux_gateway_from_setup_system_choice_without_root_prints_foll
|
|||
|
||||
out = capsys.readouterr().out
|
||||
assert (scope, did_install) == ("system", False)
|
||||
assert "sudo hermes gateway install --system --run-as-user alice" in out
|
||||
assert "sudo hermes gateway start --system" in out
|
||||
assert "sudo hermes" not in out
|
||||
assert "requires root" in out
|
||||
|
||||
|
||||
def test_install_linux_gateway_from_setup_system_choice_as_root_installs(monkeypatch):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue