From 4efb40c3254b69974e7a53506d6cd59333efc5d6 Mon Sep 17 00:00:00 2001 From: Wesley Simplicio Date: Sat, 9 May 2026 07:47:28 -0300 Subject: [PATCH] fix(install): set world-readable uv python dirs for root FHS layout When installing as root on Linux with the default FHS layout (/usr/local/lib/hermes-agent), `uv python install` placed the managed Python under /root/.local/share/uv/python/, which non-root users cannot traverse. The shared /usr/local/bin/hermes wrapper then failed for them with "bad interpreter: Permission denied" when execing the venv python. Export UV_PYTHON_INSTALL_DIR and UV_PYTHON_BIN_DIR to /usr/local/share/uv/ in the root-FHS branch of resolve_install_layout so the managed Python is world-readable and the shared wrapper works for any user. Closes #21457 --- scripts/install.sh | 8 +++++ ...test_install_sh_root_fhs_uv_python_path.py | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/test_install_sh_root_fhs_uv_python_path.py diff --git a/scripts/install.sh b/scripts/install.sh index 71902f55866..7d1df04124e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -268,10 +268,18 @@ resolve_install_layout() { fi INSTALL_DIR="/usr/local/lib/hermes-agent" ROOT_FHS_LAYOUT=true + # Place uv-managed Python under /usr/local/share so the venv interpreter + # is world-readable. Default uv paths land in /root/.local/share/uv, + # which non-root users can't traverse — leaving the shared + # /usr/local/bin/hermes wrapper unable to exec the bad-interpreter venv + # python. See #21457. + export UV_PYTHON_INSTALL_DIR="${UV_PYTHON_INSTALL_DIR:-/usr/local/share/uv/python}" + export UV_PYTHON_BIN_DIR="${UV_PYTHON_BIN_DIR:-/usr/local/share/uv/bin}" log_info "Root install on Linux — using FHS layout" log_info " Code: $INSTALL_DIR" log_info " Command: /usr/local/bin/hermes" log_info " Data: $HERMES_HOME (unchanged)" + log_info " uv Python: $UV_PYTHON_INSTALL_DIR (world-readable)" return 0 fi diff --git a/tests/test_install_sh_root_fhs_uv_python_path.py b/tests/test_install_sh_root_fhs_uv_python_path.py new file mode 100644 index 00000000000..de7d337a952 --- /dev/null +++ b/tests/test_install_sh_root_fhs_uv_python_path.py @@ -0,0 +1,35 @@ +"""Regression test for install.sh root-mode uv Python install path. + +When installing as root with the FHS layout (INSTALL_DIR=/usr/local/lib/...), +``uv python install`` must place the managed Python under a world-readable +location, otherwise the venv interpreter ends up at ``/root/.local/share/uv/...`` +and the shared ``/usr/local/bin/hermes`` wrapper fails for non-root users with +"bad interpreter: Permission denied". See #21457. +""" + +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parent.parent +INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" + + +def test_root_fhs_layout_exports_world_readable_uv_python_dirs() -> None: + text = INSTALL_SH.read_text() + + assert 'export UV_PYTHON_INSTALL_DIR="${UV_PYTHON_INSTALL_DIR:-/usr/local/share/uv/python}"' in text + assert 'export UV_PYTHON_BIN_DIR="${UV_PYTHON_BIN_DIR:-/usr/local/share/uv/bin}"' in text + + +def test_root_fhs_uv_python_export_is_inside_root_branch() -> None: + """The export must live in the root-FHS branch of resolve_install_layout, + above its `return 0`, so non-root and Termux installs are unaffected.""" + text = INSTALL_SH.read_text() + + marker = 'ROOT_FHS_LAYOUT=true' + assert marker in text + after_marker = text.split(marker, 1)[1] + return_idx = after_marker.find('return 0') + export_idx = after_marker.find('UV_PYTHON_INSTALL_DIR') + assert export_idx != -1 + assert export_idx < return_idx