diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 8b360087c..6225b7480 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -1318,6 +1318,38 @@ def _hermes_home_for_target_user(target_home_dir: str) -> str: return str(current_hermes) +def _login_shell_hermes_home(username: str) -> str | None: + """Best-effort lookup of ``HERMES_HOME`` from ``username``'s login shell. + + This helps ``sudo hermes gateway install --system`` preserve a custom + ``HERMES_HOME`` that exists in the target user's login environment even + when sudo sanitizes it out of the root process environment. + """ + username = (username or "").strip() + if not username or username == "root": + return None + + try: + result = subprocess.run( + ["su", "-l", username, "-s", "/bin/sh", "-c", 'printf %s "$HERMES_HOME"'], + capture_output=True, + text=True, + check=False, + timeout=10, + ) + except (FileNotFoundError, subprocess.TimeoutExpired, PermissionError, OSError): + return None + + if result.returncode != 0: + return None + + value = (result.stdout or "").strip() + if not value: + return None + + return str(Path(value).expanduser()) + + def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) -> str: python_path = get_python_path() working_dir = str(PROJECT_ROOT) @@ -1339,6 +1371,13 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) if system: username, group_name, home_dir = _system_service_identity(run_as_user) hermes_home = _hermes_home_for_target_user(home_dir) + if ( + not os.getenv("HERMES_HOME", "").strip() + and hermes_home == str(Path(home_dir) / ".hermes") + ): + login_shell_home = _login_shell_hermes_home(username) + if login_shell_home: + hermes_home = login_shell_home profile_arg = _profile_arg(hermes_home) # Remap all paths that may resolve under the calling user's home # (e.g. /root/) to the target user's home so the service can diff --git a/tests/hermes_cli/test_gateway_service.py b/tests/hermes_cli/test_gateway_service.py index fda893e1e..0ac234c30 100644 --- a/tests/hermes_cli/test_gateway_service.py +++ b/tests/hermes_cli/test_gateway_service.py @@ -883,6 +883,27 @@ class TestSystemUnitHermesHome: assert 'HERMES_HOME=/opt/hermes-shared' in unit + def test_system_unit_uses_target_login_shell_hermes_home_when_sudo_drops_env(self, monkeypatch): + monkeypatch.setattr(Path, "home", staticmethod(lambda: Path("/root"))) + monkeypatch.delenv("HERMES_HOME", raising=False) + monkeypatch.setattr( + gateway_cli, "_system_service_identity", + lambda run_as_user=None: ("alice", "alice", "/home/alice"), + ) + monkeypatch.setattr( + gateway_cli, "_build_user_local_paths", + lambda home, existing: [], + ) + monkeypatch.setattr( + gateway_cli, + "_login_shell_hermes_home", + lambda username: "/srv/hermes-shared", + ) + + unit = gateway_cli.generate_systemd_unit(system=True, run_as_user="alice") + + assert 'HERMES_HOME=/srv/hermes-shared' in unit + def test_user_unit_unaffected_by_change(self): # User-scope units should still use the calling user's HERMES_HOME unit = gateway_cli.generate_systemd_unit(system=False)