#!/usr/bin/env bash set -eo pipefail export HOME="${HOME:-/root}" DATA="/data" # --- 0. Persistent data dir: ~/.hermes -> /data --------------------------------- # Hermes (baked into the image) defaults to $HOME/.hermes. We swap that for the # mounted /data bucket so config, skills, memory, sessions and cron survive # restarts. /root is ephemeral, so the symlink itself is recreated every boot. mkdir -p "${DATA}" rm -rf "${HOME}/.hermes" ln -sfn "${DATA}" "${HOME}/.hermes" # Heal mount permissions/ownership (bucket mounts can reset them between boots). if [ ! -w "${DATA}" ] && [ "$(id -u)" = "0" ]; then echo "[boot] repairing ownership on ${DATA}..." >&2 chown root:root "${DATA}" 2>/dev/null || true chmod u+rwx "${DATA}" 2>/dev/null || true fi # --- 1. Seed the volume from the image on first boot --------------------------------------- if [ ! -f "${DATA}/config.yaml" ]; then echo "[boot] first boot — seeding Hermes data (node, uv, bundled skills) into ${DATA}..." >&2 if [ -d /hermes-seed ]; then cp -a /hermes-seed/. "${DATA}/" 2>/dev/null || true fi fi # Storage Buckets are S3-like and may drop the executable bit on remount. Re-assert +x # on every boot for the binaries Hermes relies on (node, uv, skills scripts) so the # volume stays self-healing across restarts. if [ -d "${DATA}/node/bin" ]; then chmod +x "${DATA}/node/bin/"* 2>/dev/null || true fi find "${DATA}" -maxdepth 3 -type f \( -name "uv" -o -name "node" -o -name "npm" -o -name "npx" \) -exec chmod +x {} + 2>/dev/null || true # node/npm (managed by the installer under ~/.hermes/node) + uv on PATH. export PATH="${DATA}/node/bin:${HOME}/.local/bin:${PATH}" # Make sure `hermes` is reachable (it's baked at /usr/local/bin, fallback to venv). if ! command -v hermes >/dev/null 2>&1; then ln -sfn /usr/local/lib/hermes-agent/venv/bin/hermes /usr/local/bin/hermes 2>/dev/null || true fi # --- 2. Persist secrets (Space env secrets) into ~/.hermes/.env every boot ---- # (config.yaml lives in the .env directory too, so this file IS the volume) env_file="${DATA}/.env" touch "${env_file}" set_key() { local key="$1" value="${2:-}" if [ -n "${value}" ]; then if grep -qE "^${key}=" "${env_file}" 2>/dev/null; then sed -i "/^${key}=/d" "${env_file}" fi printf '%s=%s\n' "${key}" "${value}" >> "${env_file}" fi } set_key OPENROUTER_API_KEY "${OPENROUTER_API_KEY:-}" set_key HERMES_OPENROUTER_CACHE 1 # Dashboard basic auth (a non-loopback bind requires an auth provider). set_key HERMES_DASHBOARD_BASIC_AUTH_USERNAME "${HERMES_DASHBOARD_BASIC_AUTH_USERNAME:-admin}" set_key HERMES_DASHBOARD_BASIC_AUTH_PASSWORD "${HERMES_DASHBOARD_BASIC_AUTH_PASSWORD:-}" set_key HERMES_DASHBOARD_BASIC_AUTH_SECRET "${HERMES_DASHBOARD_BASIC_AUTH_SECRET:-}" # OpenAI-compatible API server (loopback only). set_key API_SERVER_ENABLED "${API_SERVER_ENABLED:-true}" set_key API_SERVER_PORT "${API_SERVER_PORT:-8642}" set_key API_SERVER_KEY "${API_SERVER_KEY:-}" set_key API_SERVER_HOST "127.0.0.1" # --- 3. Default config.yaml (OpenRouter + delegation/spawn), only if missing ---- if [ ! -f "${DATA}/config.yaml" ]; then cat > "${DATA}/config.yaml" <<'YAML' # Hermes Agent — persistent setup via OpenRouter with subagent spawning. model: provider: openrouter default: "~anthropic/claude-sonnet-latest" # Spawn subagents through OpenRouter (parallel + nested orchestration). delegation: provider: openrouter max_concurrent_children: 3 max_spawn_depth: 2 orchestrator_enabled: true max_iterations: 50 terminal: backend: local openrouter: response_cache: true YAML fi # --- 4. Start the Hermes web dashboard on the Hugging Face PORT ---------------- PORT="${PORT:-7860}" echo "[boot] Hermes dashboard → http://0.0.0.0:${PORT}/" >&2 # Use prebuilt web UI if present (baked at Docker build); otherwise build at runtime. DASH_FLAGS="--host 0.0.0.0 --port ${PORT}" if [ -d /usr/local/lib/hermes-agent/hermes_cli/web_dist ]; then DASH_FLAGS="${DASH_FLAGS} --skip-build" fi exec hermes dashboard ${DASH_FLAGS}