Something went wrong. Try again.
The agent that grows with you
Something went wrong. Try again.
4.5 kB · 104 lines
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105#!/bin/bash# Docker/Podman entrypoint: bootstrap config files into the mounted volume, then run hermes.set -e
HERMES_HOME="${HERMES_HOME:-/opt/data}"INSTALL_DIR="/opt/hermes"
# --- Privilege dropping via gosu ---# When started as root (the default for Docker, or fakeroot in rootless Podman),# optionally remap the hermes user/group to match host-side ownership, fix volume# permissions, then re-exec as hermes.if [ "$(id -u)" = "0" ]; then if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "$(id -u hermes)" ]; then echo "Changing hermes UID to $HERMES_UID" usermod -u "$HERMES_UID" hermes fi
if [ -n "$HERMES_GID" ] && [ "$HERMES_GID" != "$(id -g hermes)" ]; then echo "Changing hermes GID to $HERMES_GID" # -o allows non-unique GID (e.g. macOS GID 20 "staff" may already exist # as "dialout" in the Debian-based container image) groupmod -o -g "$HERMES_GID" hermes 2>/dev/null || true fi
# Fix ownership of the data volume. When HERMES_UID remaps the hermes user, # files created by previous runs (under the old UID) become inaccessible. # Always chown -R when UID was remapped; otherwise only if top-level is wrong. actual_hermes_uid=$(id -u hermes) needs_chown=false if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "10000" ]; then needs_chown=true elif [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then needs_chown=true fi if [ "$needs_chown" = true ]; then echo "Fixing ownership of $HERMES_HOME to hermes ($actual_hermes_uid)" # In rootless Podman the container's "root" is mapped to an unprivileged # host UID — chown will fail. That's fine: the volume is already owned # by the mapped user on the host side. chown -R hermes:hermes "$HERMES_HOME" 2>/dev/null || \ echo "Warning: chown failed (rootless container?) — continuing anyway" fi
# Ensure config.yaml is readable by the hermes runtime user even if it was # edited on the host after initial ownership setup. Must run here (as root) # rather than after the gosu drop, otherwise a non-root caller like # `docker run -u $(id -u):$(id -g)` hits "Operation not permitted" (#15865). if [ -f "$HERMES_HOME/config.yaml" ]; then chown hermes:hermes "$HERMES_HOME/config.yaml" 2>/dev/null || true chmod 640 "$HERMES_HOME/config.yaml" 2>/dev/null || true fi
echo "Dropping root privileges" exec gosu hermes "$0" "$@"fi
# --- Running as hermes from here ---source "${INSTALL_DIR}/.venv/bin/activate"
# Create essential directory structure. Cache and platform directories# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on# demand by the application — don't pre-create them here so new installs# get the consolidated layout from get_hermes_dir().# The "home/" subdirectory is a per-profile HOME for subprocesses (git,# ssh, gh, npm …). Without it those tools write to /root which is# ephemeral and shared across profiles. See issue #4426.mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}
# .envif [ ! -f "$HERMES_HOME/.env" ]; then cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env"fi
# config.yamlif [ ! -f "$HERMES_HOME/config.yaml" ]; then cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"fi
# SOUL.mdif [ ! -f "$HERMES_HOME/SOUL.md" ]; then cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md"fi
# Sync bundled skills (manifest-based so user edits are preserved)if [ -d "$INSTALL_DIR/skills" ]; then python3 "$INSTALL_DIR/tools/skills_sync.py"fi
# Final exec: two supported invocation patterns.## docker run <image> -> exec `hermes` with no args (legacy default)# docker run <image> chat -q "..." -> exec `hermes chat -q "..."` (legacy wrap)# docker run <image> sleep infinity -> exec `sleep infinity` directly# docker run <image> bash -> exec `bash` directly## If the first positional arg resolves to an executable on PATH, we assume the# caller wants to run it directly (needed by the launcher which runs long-lived# `sleep infinity` sandbox containers — see tools/environments/docker.py).# Otherwise we treat the args as a hermes subcommand and wrap with `hermes`,# preserving the documented `docker run <image> <subcommand>` behavior.if [ $# -gt 0 ] && command -v "$1" >/dev/null 2>&1; then exec "$@"fiexec hermes "$@"