Merge pull request #2873 from Charanis/codex/webui-launcher-env-pr

[1/7] Preserve WebUI launcher environment
This commit is contained in:
nesquena-hermes 2026-05-24 23:09:28 +00:00
commit d42253bca3
4 changed files with 32 additions and 3 deletions

View file

@ -28,8 +28,8 @@ def _load_repo_dotenv() -> None:
``python3 bootstrap.py`` directly behaves identically to ``./start.sh``. ``python3 bootstrap.py`` directly behaves identically to ``./start.sh``.
Variables are set unconditionally (matching shell source semantics), so a Variables are set unconditionally (matching shell source semantics), so a
value in .env overrides one already present in the shell environment. value in .env overrides one already present in the shell environment.
To keep a CLI-supplied value, unset it from .env or launch via start.sh ``ctl.sh`` sets HERMES_WEBUI_PRESERVE_ENV=1 when it has already resolved
and override there. launcher-specific values such as HERMES_HOME or HERMES_WEBUI_STATE_DIR.
Only loads the webui repo .env — not ~/.hermes/.env, which the server Only loads the webui repo .env — not ~/.hermes/.env, which the server
loads independently at startup for provider credentials. loads independently at startup for provider credentials.
@ -41,6 +41,12 @@ def _load_repo_dotenv() -> None:
if not env_path.exists(): if not env_path.exists():
return return
try: try:
preserve_existing = os.getenv("HERMES_WEBUI_PRESERVE_ENV", "").strip().lower() in {
"1",
"true",
"yes",
"on",
}
for raw_line in env_path.read_text(encoding="utf-8").splitlines(): for raw_line in env_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip() line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line: if not line or line.startswith("#") or "=" not in line:
@ -52,6 +58,8 @@ def _load_repo_dotenv() -> None:
k = k[7:].strip() k = k[7:].strip()
v = v.strip().strip('"').strip("'") v = v.strip().strip('"').strip("'")
if k: if k:
if preserve_existing and k in os.environ:
continue
os.environ[k] = v os.environ[k] = v
except Exception as exc: except Exception as exc:
import sys as _sys import sys as _sys

4
ctl.sh
View file

@ -219,7 +219,9 @@ start_cmd() {
: >> "${LOG_FILE}" : >> "${LOG_FILE}"
( (
cd "${REPO_ROOT}" cd "${REPO_ROOT}"
exec "${python_exe}" "${REPO_ROOT}/bootstrap.py" --no-browser --foreground --host "${CTL_HOST}" "${CTL_PORT}" ${CTL_BOOTSTRAP_ARGS[@]+"${CTL_BOOTSTRAP_ARGS[@]}"} trap '' HUP
export HERMES_WEBUI_PRESERVE_ENV=1
exec nohup "${python_exe}" "${REPO_ROOT}/bootstrap.py" --no-browser --foreground --host "${CTL_HOST}" "${CTL_PORT}" ${CTL_BOOTSTRAP_ARGS[@]+"${CTL_BOOTSTRAP_ARGS[@]}"}
) >> "${LOG_FILE}" 2>&1 & ) >> "${LOG_FILE}" 2>&1 &
pid=$! pid=$!

View file

@ -112,6 +112,18 @@ class TestLoadRepoDotenv:
self._run(tmp_path, "HERMES_WEBUI_HOST=0.0.0.0\n") self._run(tmp_path, "HERMES_WEBUI_HOST=0.0.0.0\n")
assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0" assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0"
def test_preserve_existing_env_keeps_ctl_overrides(self, tmp_path):
"""ctl.sh can ask bootstrap.py to keep wrapper-provided env values."""
os.environ["HERMES_WEBUI_PRESERVE_ENV"] = "1"
os.environ["HERMES_HOME"] = "/runtime/hermesOne"
os.environ["HERMES_WEBUI_PASSWORD"] = ""
self._run(
tmp_path,
"HERMES_HOME=/repo/default\nHERMES_WEBUI_PASSWORD=repo-password\n",
)
assert os.environ.get("HERMES_HOME") == "/runtime/hermesOne"
assert os.environ.get("HERMES_WEBUI_PASSWORD") == ""
def test_does_not_set_empty_values(self, tmp_path): def test_does_not_set_empty_values(self, tmp_path):
"""A key whose value is empty after stripping is not set to a non-empty string.""" """A key whose value is empty after stripping is not set to a non-empty string."""
os.environ.pop("HERMES_EMPTY_KEY", None) os.environ.pop("HERMES_EMPTY_KEY", None)

View file

@ -138,6 +138,13 @@ def test_start_writes_pid_under_hermes_home_runs_foreground_no_browser_and_logs(
assert not pid_file.exists() assert not pid_file.exists()
def test_start_uses_nohup_so_daemon_survives_launcher_exit():
ctl_text = CTL.read_text(encoding="utf-8")
assert "trap '' HUP" in ctl_text
assert 'exec nohup "${python_exe}"' in ctl_text
def test_start_loads_dotenv_but_inline_overrides_win(tmp_path): def test_start_loads_dotenv_but_inline_overrides_win(tmp_path):
repo_root = tmp_path / "repo" repo_root = tmp_path / "repo"
repo_root.mkdir() repo_root.mkdir()