#!/bin/bash # Docker entrypoint: bootstrap config files into the mounted volume, then run hermes. set -e INSTALL_DIR="/opt/hermes" RUNTIME_HELPER="$INSTALL_DIR/docker/space_runtime.py" export HERMES_HOME="$(python3 "$RUNTIME_HELPER" home)" # --- Privilege dropping via gosu --- # When started as root (the default), optionally remap the hermes user/group # to match host-side ownership, fix volume permissions, then re-exec as hermes. if [ "$(id -u)" = "0" ]; then # HF Spaces run inside containers, but they don't always expose /.dockerenv. # hermes-web-ui uses that marker to choose detached "gateway run" mode # instead of system-service "gateway start", which times out in Spaces. if [ -n "${SPACE_ID:-}${SPACE_HOST:-}" ] && [ ! -e "/.dockerenv" ]; then touch "/.dockerenv" 2>/dev/null || true fi 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" groupmod -g "$HERMES_GID" hermes fi mkdir -p "$HERMES_HOME" 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" chown -R hermes:hermes "$HERMES_HOME" fi echo "Dropping root privileges" exec gosu hermes env HERMES_HOME="$HERMES_HOME" "$0" "$@" fi # --- Running as hermes from here --- export HERMES_HOME="$(python3 "$RUNTIME_HELPER" home)" 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} # hermes-web-ui currently resolves runtime files via ~/.hermes. # Mirror the active Hermes home into the hermes user's legacy home path # so the external UI sees the same config/.env/session files. LEGACY_HERMES_HOME="/opt/data/.hermes" if [ ! -e "$LEGACY_HERMES_HOME" ]; then ln -s "$HERMES_HOME" "$LEGACY_HERMES_HOME" fi # .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 # 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 if [ $# -eq 0 ] && [ -n "${SPACE_ID:-}${SPACE_HOST:-}" ]; then SHELL_EXEC="/bin/bash" if [ ! -x "$SHELL_EXEC" ]; then SHELL_EXEC="/bin/sh" fi UI_NODE="$(command -v node || command -v nodejs || true)" if [ -z "$UI_NODE" ]; then echo "Node.js runtime not found; hermes-web-ui cannot start" exit 1 fi HERMES_WEB_UI_ROOT="$(npm root -g)/hermes-web-ui" HERMES_WEB_UI_ENTRY="$HERMES_WEB_UI_ROOT/dist/server/index.js" if [ ! -f "$HERMES_WEB_UI_ENTRY" ]; then echo "hermes-web-ui entrypoint missing at $HERMES_WEB_UI_ENTRY" exit 1 fi HERMES_BIN="$INSTALL_DIR/.venv/bin/hermes" export HERMES_BIN export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}" export PORT="${PORT:-7860}" # Let hermes-web-ui's GatewayManager own port selection via config.yaml. # Exporting a default API_SERVER_PORT here forces child gateway processes # back onto 8642, which breaks health checks when the UI assigns 8643+. export UPSTREAM="${UPSTREAM:-$(python3 "$RUNTIME_HELPER" api-server-upstream)}" export AUTH_DISABLED="${AUTH_DISABLED:-false}" export AUTH_TOKEN="${AUTH_TOKEN:-wangjx0515}" WEB_UI_CMD="$UI_NODE \"$HERMES_WEB_UI_ENTRY\"" exec $SHELL_EXEC -lc "$WEB_UI_CMD" fi exec python3 "$RUNTIME_HELPER" exec "$@"