File size: 4,119 Bytes
4136906
287d491
4136906
 
9cb5fe7
4136906
9cb5fe7
 
 
 
 
 
 
287d491
9cb5fe7
 
 
 
 
 
287d491
9cb5fe7
 
 
 
 
287d491
4136906
 
6fd41e0
 
 
 
 
 
 
 
9cb5fe7
 
287d491
9cb5fe7
 
 
 
4136906
9cb5fe7
 
 
4136906
 
 
 
 
 
 
 
 
 
287d491
 
4136906
287d491
4136906
 
 
 
287d491
4136906
 
 
 
 
287d491
9cb5fe7
 
4136906
 
 
 
 
287d491
4136906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9cb5fe7
4136906
287d491
3cb49fd
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/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}