| # 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 | |
| actual_hermes_uid=$(id -u hermes) | |
| if [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then | |
| echo "$HERMES_HOME is not owned by $actual_hermes_uid, fixing" | |
| # 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 | |
| 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} | |
| # .env | |
| if [ ! -f "$HERMES_HOME/.env" ]; then | |
| cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env" | |
| fi | |
| # config.yaml | |
| if [ ! -f "$HERMES_HOME/config.yaml" ]; then | |
| cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml" | |
| fi | |
| # Ensure the main config file remains accessible to the hermes runtime user | |
| # even if it was edited on the host after initial ownership setup. | |
| if [ -f "$HERMES_HOME/config.yaml" ]; then | |
| chown hermes:hermes "$HERMES_HOME/config.yaml" | |
| chmod 640 "$HERMES_HOME/config.yaml" | |
| fi | |
| # SOUL.md | |
| if [ ! -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 "$@" | |
| fi | |
| exec hermes "$@" | |