diff --git a/nix/nixosModules.nix b/nix/nixosModules.nix index 178305a28..e511228e9 100644 --- a/nix/nixosModules.nix +++ b/nix/nixosModules.nix @@ -10,6 +10,12 @@ # container recreation. Environment variables are written to $HERMES_HOME/.env # and read by hermes at startup — no container recreation needed for env changes. # +# Tool resolution: the hermes wrapper uses --suffix PATH for nix store tools, +# so apt/uv-installed versions take priority. The container entrypoint provisions +# extensible tools on first boot: nodejs/npm via apt, uv via curl, and a Python +# 3.11 venv (bootstrapped entirely by uv) at ~/.venv with pip seeded. Agents get +# writable tool prefixes for npm i -g, pip install, uv tool install, etc. +# # Usage: # services.hermes-agent = { # enable = true; @@ -111,16 +117,45 @@ chown -R "$HERMES_UID:$HERMES_GID" "$HERMES_HOME" fi - # Install sudo on Debian/Ubuntu if missing (first boot only, cached in writable layer) - if command -v apt-get >/dev/null 2>&1 && ! command -v sudo >/dev/null 2>&1; then - apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq sudo >/dev/null 2>&1 || true + # ── Provision apt packages (first boot only, cached in writable layer) ── + # sudo: agent self-modification + # nodejs/npm: writable node so npm i -g works (nix store copies are read-only) + # curl: needed for uv installer + if [ ! -f /var/lib/hermes-tools-provisioned ] && command -v apt-get >/dev/null 2>&1; then + echo "First boot: provisioning agent tools..." + apt-get update -qq + apt-get install -y -qq sudo nodejs npm curl + touch /var/lib/hermes-tools-provisioned fi + if command -v sudo >/dev/null 2>&1 && [ ! -f /etc/sudoers.d/hermes ]; then mkdir -p /etc/sudoers.d echo "$TARGET_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/hermes chmod 0440 /etc/sudoers.d/hermes fi + # uv (Python manager) — not in Ubuntu repos, retry-safe outside the sentinel + if ! command -v uv >/dev/null 2>&1 && [ ! -x "$TARGET_HOME/.local/bin/uv" ] && command -v curl >/dev/null 2>&1; then + su -s /bin/sh "$TARGET_USER" -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' || true + fi + + # Python 3.11 venv — gives the agent a writable Python with pip. + # Uses uv to install Python 3.11 (Ubuntu 24.04 ships 3.12). + # --seed includes pip/setuptools so bare `pip install` works. + _UV_BIN="$TARGET_HOME/.local/bin/uv" + if [ ! -d "$TARGET_HOME/.venv" ] && [ -x "$_UV_BIN" ]; then + su -s /bin/sh "$TARGET_USER" -c " + export PATH=\"\$HOME/.local/bin:\$PATH\" + uv python install 3.11 + uv venv --python 3.11 --seed \"\$HOME/.venv\" + " || true + fi + + # Put the agent venv first on PATH so python/pip resolve to writable copies + if [ -d "$TARGET_HOME/.venv/bin" ]; then + export PATH="$TARGET_HOME/.venv/bin:$PATH" + fi + if command -v setpriv >/dev/null 2>&1; then exec setpriv --reuid="$HERMES_UID" --regid="$HERMES_GID" --init-groups "$@" elif command -v su >/dev/null 2>&1; then diff --git a/nix/packages.nix b/nix/packages.nix index 8c2b7cbd9..805f76605 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -35,7 +35,7 @@ ${pkgs.lib.concatMapStringsSep "\n" (name: '' makeWrapper ${hermesVenv}/bin/${name} $out/bin/${name} \ - --prefix PATH : "${runtimePath}" \ + --suffix PATH : "${runtimePath}" \ --set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills '') [ "hermes" "hermes-agent" "hermes-acp" ]}