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
This commit is contained in:
Wesley Simplicio 2026-05-09 07:47:28 -03:00 committed by kshitij
parent 0537e2600d
commit 4efb40c325
2 changed files with 43 additions and 0 deletions

View file

@ -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

View file

@ -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