hermes / entrypoint.sh
amangs's picture
Update entrypoint.sh
288acd7 verified
Raw History Blame Contribute Delete
8.3 kB
#!/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