fix(gateway): detect virtualenv path instead of hardcoding venv/ (#2797)
Fixes #2492. `generate_systemd_unit()` and `get_python_path()` hardcoded `venv` as the virtualenv directory name. When the virtualenv is `.venv` (which `setup-hermes.sh` and `.gitignore` both reference), the generated systemd unit had incorrect VIRTUAL_ENV and PATH variables. Introduce `_detect_venv_dir()` which: 1. Checks `sys.prefix` vs `sys.base_prefix` to detect the active venv 2. Falls back to probing `.venv` then `venv` under PROJECT_ROOT Both `get_python_path()` and `generate_systemd_unit()` now use this detection instead of hardcoded paths. Co-authored-by: Hermes <hermes@nousresearch.ai>
This commit is contained in:
parent
18cbd18fa9
commit
ce39f9cc44
2 changed files with 105 additions and 8 deletions
|
|
@ -371,13 +371,37 @@ def print_systemd_linger_guidance() -> None:
|
||||||
def get_launchd_plist_path() -> Path:
|
def get_launchd_plist_path() -> Path:
|
||||||
return Path.home() / "Library" / "LaunchAgents" / "ai.hermes.gateway.plist"
|
return Path.home() / "Library" / "LaunchAgents" / "ai.hermes.gateway.plist"
|
||||||
|
|
||||||
|
def _detect_venv_dir() -> Path | None:
|
||||||
|
"""Detect the active virtualenv directory.
|
||||||
|
|
||||||
|
Checks ``sys.prefix`` first (works regardless of the directory name),
|
||||||
|
then falls back to probing common directory names under PROJECT_ROOT.
|
||||||
|
Returns ``None`` when no virtualenv can be found.
|
||||||
|
"""
|
||||||
|
# If we're running inside a virtualenv, sys.prefix points to it.
|
||||||
|
if sys.prefix != sys.base_prefix:
|
||||||
|
venv = Path(sys.prefix)
|
||||||
|
if venv.is_dir():
|
||||||
|
return venv
|
||||||
|
|
||||||
|
# Fallback: check common virtualenv directory names under the project root.
|
||||||
|
for candidate in (".venv", "venv"):
|
||||||
|
venv = PROJECT_ROOT / candidate
|
||||||
|
if venv.is_dir():
|
||||||
|
return venv
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_python_path() -> str:
|
def get_python_path() -> str:
|
||||||
if is_windows():
|
venv = _detect_venv_dir()
|
||||||
venv_python = PROJECT_ROOT / "venv" / "Scripts" / "python.exe"
|
if venv is not None:
|
||||||
else:
|
if is_windows():
|
||||||
venv_python = PROJECT_ROOT / "venv" / "bin" / "python"
|
venv_python = venv / "Scripts" / "python.exe"
|
||||||
if venv_python.exists():
|
else:
|
||||||
return str(venv_python)
|
venv_python = venv / "bin" / "python"
|
||||||
|
if venv_python.exists():
|
||||||
|
return str(venv_python)
|
||||||
return sys.executable
|
return sys.executable
|
||||||
|
|
||||||
def get_hermes_cli_path() -> str:
|
def get_hermes_cli_path() -> str:
|
||||||
|
|
@ -399,8 +423,9 @@ def get_hermes_cli_path() -> str:
|
||||||
def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) -> str:
|
def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) -> str:
|
||||||
python_path = get_python_path()
|
python_path = get_python_path()
|
||||||
working_dir = str(PROJECT_ROOT)
|
working_dir = str(PROJECT_ROOT)
|
||||||
venv_dir = str(PROJECT_ROOT / "venv")
|
detected_venv = _detect_venv_dir()
|
||||||
venv_bin = str(PROJECT_ROOT / "venv" / "bin")
|
venv_dir = str(detected_venv) if detected_venv else str(PROJECT_ROOT / "venv")
|
||||||
|
venv_bin = str(detected_venv / "bin") if detected_venv else str(PROJECT_ROOT / "venv" / "bin")
|
||||||
node_bin = str(PROJECT_ROOT / "node_modules" / ".bin")
|
node_bin = str(PROJECT_ROOT / "node_modules" / ".bin")
|
||||||
|
|
||||||
path_entries = [venv_bin, node_bin]
|
path_entries = [venv_bin, node_bin]
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,78 @@ class TestGatewaySystemServiceRouting:
|
||||||
assert run_calls == []
|
assert run_calls == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestDetectVenvDir:
|
||||||
|
"""Tests for _detect_venv_dir() virtualenv detection."""
|
||||||
|
|
||||||
|
def test_detects_active_virtualenv_via_sys_prefix(self, tmp_path, monkeypatch):
|
||||||
|
venv_path = tmp_path / "my-custom-venv"
|
||||||
|
venv_path.mkdir()
|
||||||
|
monkeypatch.setattr("sys.prefix", str(venv_path))
|
||||||
|
monkeypatch.setattr("sys.base_prefix", "/usr")
|
||||||
|
|
||||||
|
result = gateway_cli._detect_venv_dir()
|
||||||
|
assert result == venv_path
|
||||||
|
|
||||||
|
def test_falls_back_to_dot_venv_directory(self, tmp_path, monkeypatch):
|
||||||
|
# Not inside a virtualenv
|
||||||
|
monkeypatch.setattr("sys.prefix", "/usr")
|
||||||
|
monkeypatch.setattr("sys.base_prefix", "/usr")
|
||||||
|
monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
|
||||||
|
|
||||||
|
dot_venv = tmp_path / ".venv"
|
||||||
|
dot_venv.mkdir()
|
||||||
|
|
||||||
|
result = gateway_cli._detect_venv_dir()
|
||||||
|
assert result == dot_venv
|
||||||
|
|
||||||
|
def test_falls_back_to_venv_directory(self, tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setattr("sys.prefix", "/usr")
|
||||||
|
monkeypatch.setattr("sys.base_prefix", "/usr")
|
||||||
|
monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
|
||||||
|
|
||||||
|
venv = tmp_path / "venv"
|
||||||
|
venv.mkdir()
|
||||||
|
|
||||||
|
result = gateway_cli._detect_venv_dir()
|
||||||
|
assert result == venv
|
||||||
|
|
||||||
|
def test_prefers_dot_venv_over_venv(self, tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setattr("sys.prefix", "/usr")
|
||||||
|
monkeypatch.setattr("sys.base_prefix", "/usr")
|
||||||
|
monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
|
||||||
|
|
||||||
|
(tmp_path / ".venv").mkdir()
|
||||||
|
(tmp_path / "venv").mkdir()
|
||||||
|
|
||||||
|
result = gateway_cli._detect_venv_dir()
|
||||||
|
assert result == tmp_path / ".venv"
|
||||||
|
|
||||||
|
def test_returns_none_when_no_virtualenv(self, tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setattr("sys.prefix", "/usr")
|
||||||
|
monkeypatch.setattr("sys.base_prefix", "/usr")
|
||||||
|
monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", tmp_path)
|
||||||
|
|
||||||
|
result = gateway_cli._detect_venv_dir()
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestGeneratedUnitUsesDetectedVenv:
|
||||||
|
def test_systemd_unit_uses_dot_venv_when_detected(self, tmp_path, monkeypatch):
|
||||||
|
dot_venv = tmp_path / ".venv"
|
||||||
|
dot_venv.mkdir()
|
||||||
|
(dot_venv / "bin").mkdir()
|
||||||
|
|
||||||
|
monkeypatch.setattr(gateway_cli, "_detect_venv_dir", lambda: dot_venv)
|
||||||
|
monkeypatch.setattr(gateway_cli, "get_python_path", lambda: str(dot_venv / "bin" / "python"))
|
||||||
|
|
||||||
|
unit = gateway_cli.generate_systemd_unit(system=False)
|
||||||
|
|
||||||
|
assert f"VIRTUAL_ENV={dot_venv}" in unit
|
||||||
|
assert f"{dot_venv}/bin" in unit
|
||||||
|
# Must NOT contain a hardcoded /venv/ path
|
||||||
|
assert "/venv/" not in unit or "/.venv/" in unit
|
||||||
|
|
||||||
|
|
||||||
class TestEnsureUserSystemdEnv:
|
class TestEnsureUserSystemdEnv:
|
||||||
"""Tests for _ensure_user_systemd_env() D-Bus session bus auto-detection."""
|
"""Tests for _ensure_user_systemd_env() D-Bus session bus auto-detection."""
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue