fix(install): support non-sudo service-user installs on apt distros (#25814)

The Debian/Ubuntu branch of install_node_deps() ran 'npx playwright install
--with-deps chromium' unconditionally. Playwright invokes sudo interactively
to apt-install Chromium's system libraries, which blocks the installer for
non-sudo users (systemd service accounts, unprivileged operator users) on
an unsatisfiable password prompt.

Changes:
- install.sh: gate --with-deps behind a sudo capability check on the apt
  branch (matches the existing Arch/pacman branch pattern). Non-sudo users
  fall back to 'npx playwright install chromium' alone and the installer
  prints the exact 'sudo npx playwright install-deps chromium' command an
  administrator can run separately.
- install.sh: add --skip-browser (alias --no-playwright) to skip the
  Playwright step entirely for headless installs that don't need browser
  automation. Mirrors the existing --no-venv / --skip-setup shape.
- installation.md: add a 'Non-Sudo / System Service User Installs' section
  covering the admin/service-user split, the --skip-browser flag, and the
  ~/.local/bin PATH gotcha (the root cause of the 'No module named dotenv'
  error users hit when running the repo source 'hermes' script with system
  Python instead of the venv launcher).
- test_install_sh_browser_install.py: regression coverage for the
  --skip-browser flag and the sudo-gate on the apt branch.

Reported by @ssilver in Discord.
This commit is contained in:
Teknium 2026-05-14 09:05:31 -07:00 committed by GitHub
parent 26933c2f59
commit 78b842c995
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 100 additions and 6 deletions

View file

@ -69,6 +69,7 @@ DETECTED_BROWSER_EXECUTABLE=""
# Options # Options
USE_VENV=true USE_VENV=true
RUN_SETUP=true RUN_SETUP=true
SKIP_BROWSER=false
BRANCH="main" BRANCH="main"
# Detect non-interactive mode (e.g. curl | bash) # Detect non-interactive mode (e.g. curl | bash)
@ -91,6 +92,10 @@ while [[ $# -gt 0 ]]; do
RUN_SETUP=false RUN_SETUP=false
shift shift
;; ;;
--skip-browser|--no-playwright)
SKIP_BROWSER=true
shift
;;
--branch) --branch)
BRANCH="$2" BRANCH="$2"
shift 2 shift 2
@ -112,6 +117,7 @@ while [[ $# -gt 0 ]]; do
echo "Options:" echo "Options:"
echo " --no-venv Don't create virtual environment" echo " --no-venv Don't create virtual environment"
echo " --skip-setup Skip interactive setup wizard" echo " --skip-setup Skip interactive setup wizard"
echo " --skip-browser Skip Playwright/Chromium install (browser tools won't work)"
echo " --branch NAME Git branch to install (default: main)" echo " --branch NAME Git branch to install (default: main)"
echo " --dir PATH Installation directory" echo " --dir PATH Installation directory"
echo " default (non-root): ~/.hermes/hermes-agent" echo " default (non-root): ~/.hermes/hermes-agent"
@ -1566,6 +1572,13 @@ install_node_deps() {
# Playwright's --with-deps only supports apt-based systems natively. # Playwright's --with-deps only supports apt-based systems natively.
# For Arch/Manjaro we install the system libs via pacman first. # For Arch/Manjaro we install the system libs via pacman first.
# Other systems must install Chromium dependencies manually. # Other systems must install Chromium dependencies manually.
if [ "$SKIP_BROWSER" = true ]; then
log_info "Skipping Playwright/Chromium install (--skip-browser)"
log_info "Browser tools will be unavailable until you run manually:"
log_info " cd $INSTALL_DIR && npx playwright install chromium"
log_info "On apt-based systems, an admin also needs to run:"
log_info " sudo npx playwright install-deps chromium"
else
log_info "Installing browser engine (Playwright Chromium)..." log_info "Installing browser engine (Playwright Chromium)..."
DETECTED_BROWSER_EXECUTABLE="$(find_system_browser 2>/dev/null || true)" DETECTED_BROWSER_EXECUTABLE="$(find_system_browser 2>/dev/null || true)"
if [ -n "$DETECTED_BROWSER_EXECUTABLE" ]; then if [ -n "$DETECTED_BROWSER_EXECUTABLE" ]; then
@ -1574,12 +1587,30 @@ install_node_deps() {
else else
case "$DISTRO" in case "$DISTRO" in
ubuntu|debian|raspbian|pop|linuxmint|elementary|zorin|kali|parrot) ubuntu|debian|raspbian|pop|linuxmint|elementary|zorin|kali|parrot)
log_info "Playwright may request sudo to install browser system dependencies (shared libraries)." # Use --with-deps only when sudo is available non-interactively
log_info "This is standard Playwright setup — Hermes itself does not require root access." # (root, or a user with passwordless sudo). Non-sudo users
cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install --with-deps chromium 2>/dev/null || { # — typical for systemd service accounts and unprivileged
log_warn "Playwright browser installation failed — browser tools will not work." # operator users — would otherwise get blocked on an
log_warn "Try running manually: cd $INSTALL_DIR && npx playwright install --with-deps chromium" # interactive sudo prompt that they can't satisfy. Fall back
} # to the browser-only install in that case, and print the
# exact command the admin needs to run separately.
if [ "$(id -u)" -eq 0 ] || (command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null); then
log_info "Installing Playwright Chromium with system dependencies..."
cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install --with-deps chromium 2>/dev/null || {
log_warn "Playwright browser installation failed — browser tools will not work."
log_warn "Try running manually: cd $INSTALL_DIR && npx playwright install --with-deps chromium"
}
else
log_warn "No sudo available — skipping system-library install (--with-deps)."
log_info "Ask an administrator to run, one time, as root:"
log_info " sudo npx playwright install-deps chromium"
log_info " (from $INSTALL_DIR, after Node.js deps are installed)"
log_info "Installing Chromium binary into this user's Playwright cache..."
cd "$INSTALL_DIR" && run_browser_install_with_timeout 600 npx playwright install chromium 2>/dev/null || {
log_warn "Playwright browser installation failed — browser tools will not work."
log_warn "Try running manually: cd $INSTALL_DIR && npx playwright install chromium"
}
fi
;; ;;
arch|manjaro|cachyos|endeavouros|garuda) arch|manjaro|cachyos|endeavouros|garuda)
if command -v pacman &> /dev/null; then if command -v pacman &> /dev/null; then
@ -1624,6 +1655,7 @@ install_node_deps() {
;; ;;
esac esac
fi fi
fi
log_success "Browser engine setup complete" log_success "Browser engine setup complete"
fi fi

View file

@ -32,4 +32,29 @@ def test_playwright_installs_are_timeout_guarded() -> None:
assert "run_browser_install_with_timeout()" in text assert "run_browser_install_with_timeout()" in text
assert "run_browser_install_with_timeout 600 npx playwright install chromium" in text assert "run_browser_install_with_timeout 600 npx playwright install chromium" in text
# --with-deps is still invoked on apt-based systems, but only when sudo
# is available non-interactively (root or passwordless sudo). Non-sudo
# service users fall back to the browser-only install — see
# install_node_deps() in install.sh.
assert "run_browser_install_with_timeout 600 npx playwright install --with-deps chromium" in text assert "run_browser_install_with_timeout 600 npx playwright install --with-deps chromium" in text
def test_install_script_supports_skip_browser_flag() -> None:
"""--skip-browser (and --no-playwright alias) skips the Playwright install."""
text = INSTALL_SH.read_text()
assert "--skip-browser|--no-playwright)" in text
assert "SKIP_BROWSER=true" in text
assert 'if [ "$SKIP_BROWSER" = true ]; then' in text
assert "--skip-browser Skip Playwright/Chromium install" in text
def test_install_script_skips_with_deps_when_no_sudo() -> None:
"""Non-sudo users on apt distros must not block on an interactive sudo prompt."""
text = INSTALL_SH.read_text()
# The apt branch must gate --with-deps behind a sudo capability check
# (root or non-interactive sudo), otherwise the installer hangs for
# service-user installs (systemd accounts, operator users, etc.).
assert 'if [ "$(id -u)" -eq 0 ] || (command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null); then' in text
assert "sudo npx playwright install-deps chromium" in text

View file

@ -132,6 +132,43 @@ If you want to clone the repo and install from source — for contributing, runn
--- ---
## Non-Sudo / System Service User Installs
Running Hermes as a dedicated unprivileged user (e.g. a `hermes` systemd service account, or any user without `sudo` access) is supported. The only thing on the install path that genuinely needs root is Playwright's `--with-deps` step, which `apt`-installs shared libraries (`libnss3`, `libxkbcommon`, etc.) used by Chromium. The installer detects whether sudo is available and gracefully degrades when it isn't — it will install the Chromium binary into the service user's own Playwright cache and print the exact command an administrator needs to run separately.
**Recommended split (Debian/Ubuntu):**
1. **One time, as an admin user with sudo**, install the system libraries Chromium needs:
```bash
sudo npx playwright install-deps chromium
```
(You can run this from anywhere — `npx` will fetch Playwright on the fly.)
2. **As the unprivileged service user**, run the regular installer. It will detect the missing sudo, skip `--with-deps`, and install Chromium into the user's local Playwright cache:
```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```
If you want to skip the Playwright step entirely — for example because you're running headless and don't need browser automation — pass `--skip-browser`:
```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-browser
```
3. **Make `hermes` available to the service user's shells.** The installer writes the launcher to `~/.local/bin/hermes`. System service accounts often have a minimal PATH that doesn't include `~/.local/bin`. Either add it to the user's environment, or symlink the launcher into a system location:
```bash
# Option A — add to the service user's profile
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# Option B — symlink system-wide (run as an admin)
sudo ln -s /home/hermes/.hermes/hermes-agent/venv/bin/hermes /usr/local/bin/hermes
```
4. **Verify:** `hermes doctor` should now run cleanly. If you get `ModuleNotFoundError: No module named 'dotenv'`, you're invoking the repo source `hermes` file (`~/.hermes/hermes-agent/hermes`) with system Python instead of the venv launcher (`~/.hermes/hermes-agent/venv/bin/hermes`) — fix step 3.
The same pattern works on Arch (the installer uses pacman with the same sudo-detection logic), Fedora/RHEL, and openSUSE — those distros don't support `--with-deps` at all, so an administrator always installs the system libraries separately. The relevant `dnf`/`zypper` commands are printed by the installer.
---
## Troubleshooting ## Troubleshooting
| Problem | Solution | | Problem | Solution |