File size: 8,297 Bytes
5dfb831
 
 
c5ea2d1
5dfb831
c5ea2d1
 
 
5dfb831
64de9c5
 
5dfb831
 
 
c5ea2d1
 
5dfb831
 
 
 
c5ea2d1
5dfb831
 
 
c5ea2d1
5dfb831
c5ea2d1
5dfb831
 
 
0f30050
5dfb831
0f30050
 
 
 
 
 
 
 
 
 
 
 
 
c53fbbc
0f30050
5dfb831
 
c5ea2d1
5dfb831
 
 
 
fec87cb
5dfb831
fec87cb
 
5dfb831
fec87cb
 
5dfb831
 
874a702
5dfb831
fec87cb
5dfb831
 
b7b9ac7
 
288acd7
 
 
 
 
 
 
 
 
874a702
fec87cb
5dfb831
 
 
 
fec87cb
5dfb831
fec87cb
 
5dfb831
874a702
fec87cb
5dfb831
 
 
 
 
48edc0f
0f30050
5dfb831
874a702
5dfb831
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
874a702
5dfb831
c5ea2d1
5dfb831
 
 
874a702
5dfb831
874a702
c5ea2d1
5dfb831
 
874a702
 
5dfb831
874a702
 
5dfb831
c5ea2d1
5dfb831
 
 
 
 
 
c5ea2d1
5dfb831
 
874a702
5dfb831
 
 
 
 
a6d6c8c
874a702
 
5dfb831
076f6b1
604d176
 
 
 
 
9854653
076f6b1
 
3218b82
 
0b9fa1e
c5ea2d1
8f5d4cc
 
5dfb831
 
 
874a702
5dfb831
64de9c5
c5ea2d1
5dfb831
c5ea2d1
5dfb831
a6d6c8c
 
 
 
64de9c5
 
 
 
5dfb831
 
 
8f5d4cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5dfb831
 
8f5d4cc
5dfb831
 
 
 
 
8f5d4cc
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#!/bin/bash
# Entrypoint script for Hermes Agent on Hugging Face Spaces
#
# Startup Architecture:
#   entrypoint.sh
#     β”œβ”€β”€ data_sync daemon (Background, Data persistence)
#     β”œβ”€β”€ hermes gateway run (Background, API Server :8642 + Gateway)
#     └── node /opt/hermes-web-ui/dist/server/index.js (Foreground, BFF :7860)

set -e

echo "πŸš€ Hermes Agent v0.10.0 - Hugging Face Spaces"
echo "=============================================="

# ==================== Initialize Directories ====================
echo "πŸ“ Initializing directory structure..."
mkdir -p /data/.hermes/{cron,sessions,logs,memories,skills,pairing,hooks,image_cache,audio_cache,whatsapp/session,script}
mkdir -p /data/.hermes-web-ui
mkdir -p /app/logs

# ==================== Data Restoration ====================
export SKIP_CONFIG_RESTORE=true

if [ -n "$HF_DATASET_REPO" ]; then
    echo "πŸ“₯ Restoring data from Dataset..."
    python -m src.data_sync restore || {
        echo "⚠️ Data restore failed, starting with clean configuration"
    }
fi

# ==================== Setup config.yaml with Env Expansion ====================
CONFIG_FILE="/data/.hermes/config.yaml"
# Expand ${TELEGRAM_BOT_APIBASE} and other environment variables from template into runtime config
python3 -c "
import os, re
src = '/app/config/config.yaml'
dst = '/data/.hermes/config.yaml'
content = open(src).read()
# Expand \${VAR} and \$VAR patterns from current environment
def expand(match):
    var = match.group(1) or match.group(2)
    return os.environ.get(var, '')
expanded = re.sub(r'\\\$\\{([A-Za-z0-9_]+)\\}|\\\$(\\w+)', expand, content)
open(dst, 'w').write(expanded)
" 2>/dev/null || cp /app/config/config.yaml "$CONFIG_FILE"

# Merge restored user config if present
RESTORED_CONFIG="/data/.hermes/config.yaml.restored"
if [ -f "$RESTORED_CONFIG" ]; then
    echo "πŸ”„ Merging user configuration..."
    python3 << 'MERGE_SCRIPT'
import yaml
GENERATED = '/data/.hermes/config.yaml'
RESTORED = '/data/.hermes/config.yaml.restored'
ENTRYPOINT_PRIORITY = {'api_server'}
try:
    with open(GENERATED) as f: generated = yaml.safe_load(f) or {}
    with open(RESTORED) as f: restored = yaml.safe_load(f) or {}
    merged = {}
    for k in set(list(generated.keys()) + list(restored.keys())):
        merged[k] = generated.get(k, restored.get(k)) if k in ENTRYPOINT_PRIORITY else restored.get(k, generated.get(k))
    with open(GENERATED, 'w') as f:
        yaml.dump(merged, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
    print("   βœ… Configuration successfully merged")
except Exception as e:
    print(f"   ⚠️ Merge failed: {e}")
MERGE_SCRIPT
    rm -f "$RESTORED_CONFIG"
fi


# ==================== REA MCP Registration ====================
# Idempotent; after config generation so the entry survives, before gateway loads config
echo "πŸ”Œ Registering REA MCP with Hermes..."
rea setup --client hermes --yes || echo "   ⚠️ REA setup skipped (non-fatal)"
rea setup --client opencode --yes || true  
# optional


# ==================== Base URLs & Runtime Environment ====================
export NVIDIA_BASE_URL="${NVIDIA_BASE_URL:-https://integrate.api.nvidia.com/v1}"
export API_SERVER_ENABLED=true
export API_SERVER_PORT=8642
export API_SERVER_HOST=127.0.0.1
export GATEWAY_ALLOW_ALL_USERS="${GATEWAY_ALLOW_ALL_USERS:-true}"
export HERMES_MODEL="${MODEL_NAME:-deepseek-ai/deepseek-v4-flash}"

# API Server Secret Key
export API_SERVER_KEY="${API_SERVER_KEY:-$(python3 -c "import secrets; print(secrets.token_hex(32))")}"

# ==================== Write Environment Variables (.env) ====================
echo "βš™οΈ  Syncing environment variables to .env..."
ENV_FILE="/data/.hermes/.env"

PERSISTENT_VARS=(
    "MODEL_PROVIDER" "MODEL_NAME" "HERMES_MODEL"
    "NVIDIA_API_KEY" "NVIDIA_BASE_URL"
    "API_SERVER_KEY" "API_SERVER_ENABLED" "API_SERVER_PORT" "API_SERVER_HOST"
    "TELEGRAM_BOT_TOKEN" "TELEGRAM_ALLOWED_USERS" "TELEGRAM_BOT_APIBASE" "TELEGRAM_PROXY"
    "GATEWAY_ALLOW_ALL_USERS"
    "AUTH_TOKEN" "TZ"
)

declare -A env_entries=()
if [ -f "$ENV_FILE" ]; then
    while IFS= read -r line; do
        [[ "$line" =~ ^[[:space:]]*# ]] && continue
        [[ -z "${line// }" ]] && continue
        eq_idx="${line%%=*}"
        if [ -n "$eq_idx" ] && [ "$eq_idx" != "$line" ]; then
            env_entries["$eq_idx"]="$line"
        fi
    done < "$ENV_FILE"
fi

for var in "${PERSISTENT_VARS[@]}"; do
    if [ -n "${!var}" ]; then
        env_entries["$var"]="${var}=${!var}"
    fi
done

{
    for key in "${!env_entries[@]}"; do
        echo "${env_entries[$key]}"
    done
} | sort > "$ENV_FILE"
echo "   βœ… .env synchronized"

# ==================== Start Data Sync Daemon ====================
SYNC_INTERVAL=${SYNC_INTERVAL:-60}
python -m src.data_sync daemon &
SYNC_PID=$!
echo "πŸ”„ Data sync daemon started (PID: $SYNC_PID, Interval: ${SYNC_INTERVAL}s)"

# ==================== Start Gateway & API Server ====================
echo "πŸ“‘ Starting Hermes Gateway + API Server..."
(
    while true; do
        hermes gateway run --replace 2>&1
        EXIT_CODE=$?
        if [ "$EXIT_CODE" -ne 0 ]; then
            echo "   ⚠️ Gateway exited (code=$EXIT_CODE), restarting in 15s..."
            sleep 15
        else
            echo "   πŸ›‘ Gateway exited normally"
            break
        fi
    done
) &
GATEWAY_PID=$!

echo "   ⏳ Waiting for API Server (:8642)..."
for i in $(seq 1 30); do
    if curl -sf http://127.0.0.1:8642/health > /dev/null 2>&1; then
        echo "   βœ… API Server is ready (http://127.0.0.1:8642)"
        break
    fi
    sleep 1
done

# ==================== Web UI Auth ====================
export AUTH_TOKEN="${AUTH_TOKEN:-123456}"
export PASSWORD="$AUTH_TOKEN"

# ==================== Start Zen Proxy (Go) ====================
if [ -d "/data/.hermes/script/opencode-router" ]; then
    echo "πŸš€ Initializing & Starting OpenCode Router (:8787)..."
    chmod +x /data/.hermes/script/opencode-router/opencode-router /data/.hermes/script/opencode-router/*.sh 2>/dev/null || true
    if [ -f "/data/.hermes/script/opencode-router/start.sh" ]; then
        bash /data/.hermes/script/opencode-router/start.sh 8787
    fi
fi

# ==================== Start Persistent Services (script/init.d) ====================
[ -f /data/.hermes/script/init.d.sh ] && bash /data/.hermes/script/init.d.sh

# ==================== Start Web UI (BFF Server) ====================
echo "🌐 Starting Hermes Web UI on internal port 7861..."
export PORT=7861
export UPSTREAM=http://127.0.0.1:8642
export HERMES_BIN=/usr/local/bin/hermes
export HERMES_HOME=/data/.hermes
export HERMES_WEB_UI_HOME=/data/.hermes-web-ui

cleanup() {
    echo "πŸ›‘ Cleaning up..."
    if [ -n "$HF_DATASET_REPO" ]; then
        python -m src.data_sync backup --force 2>/dev/null || true
    fi
    kill $CADDY_PID $BFF_PID $GATEWAY_PID $SYNC_PID 2>/dev/null || true
    pkill -f "dufs -c /data/.hermes/script/dufs/dufs.yaml" 2>/dev/null || true
    pkill -f "code-server --port 8443" 2>/dev/null || true
    pkill -f "opencode-router 8787" 2>/dev/null || true
    exit 0
}
trap cleanup SIGTERM SIGINT

node /opt/hermes-web-ui/dist/server/index.js &
BFF_PID=$!

echo "   ⏳ Waiting for Web UI (:7861)..."
for i in $(seq 1 20); do
    if curl -sf http://localhost:7861/health > /dev/null 2>&1; then
        echo "   βœ… Web UI is ready β†’ http://localhost:7861"
        break
    fi
    sleep 1
done

# ==================== Start Caddy Reverse Proxy (:7860) ====================
CADDYFILE="/data/.hermes/script/Caddyfile"
if [ ! -f "$CADDYFILE" ]; then
    mkdir -p /data/.hermes/script
    cat << 'EOF' > "$CADDYFILE"
:7860 {
    handle_path /code* {
        reverse_proxy 127.0.0.1:8443
    }
    handle_path /opencode* {
        reverse_proxy 127.0.0.1:8787
    }
    handle /v1/* {
        reverse_proxy 127.0.0.1:8642
    }
    handle {
        reverse_proxy 127.0.0.1:7861
    }
}
EOF
fi

echo "πŸ”€ Starting Caddy on public port 7860..."
caddy run --config "$CADDYFILE" --adapter caddyfile &
CADDY_PID=$!

echo "   ⏳ Waiting for Caddy (:7860)..."
for i in $(seq 1 20); do
    if curl -sf http://localhost:7860/health > /dev/null 2>&1; then
        echo "   βœ… Caddy is ready β†’ http://localhost:7860"
        break
    fi
    sleep 1
done

wait $CADDY_PID