aether / entrypoint.sh
keithproject's picture
fix: Rsync Permission issue
17426b7
Raw
History Blame Contribute Delete
10.3 kB
#!/bin/bash
set -e
BOOT_START=$(date +%s)
echo "[entrypoint] Hermes Agent on HuggingFace Spaces (Safe Sync Mode)"
echo "==============================================================================="
# ── Configuration ───────────────────────────────────────────────────────────
HERMES_WORK="/opt/data" # Fast local filesystem (working dir)
HERMES_BACKUP="/data/hermes" # HF mounted bucket (backup only)
INSTALL_DIR="/opt/hermes"
HERMES_UID=1000
HERMES_GID=1000
SYNC_INTERVAL="${SYNC_INTERVAL:-60}" # Default: sync every 1 minute
# ── Singleton guard ──────────────────────────────────────────────────────────
# Hanya satu instance yang menjalankan steps. Instance duplikat (dari HF rapid-
# restart) cukup sleep infinity β€” tidak exit (exit = HF restart lagi).
# HERMES_ENTRYPOINT_OWNER di-export sebelum exec gosu sehingga instance setelah
# privilege-drop tahu dia adalah kelanjutan sah, bukan duplikat.
ENTRYPOINT_PID_FILE="/tmp/hermes-entrypoint.pid"
ENTRYPOINT_LOG="/tmp/hermes-entrypoint.log"
if [ -z "${HERMES_ENTRYPOINT_OWNER:-}" ]; then
# Instance baru tanpa token β†’ cek apakah ada instance lain yang sudah running
if [ -f "$ENTRYPOINT_PID_FILE" ]; then
OLD_PID=$(cat "$ENTRYPOINT_PID_FILE" 2>/dev/null || echo "")
if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
echo "[entrypoint] Instance $OLD_PID already running β€” standing by silently."
sleep infinity
exit 0
else
echo "[entrypoint] Stale PID $OLD_PID (dead) β€” taking over..."
rm -f "$ENTRYPOINT_PID_FILE" "$ENTRYPOINT_LOG" 2>/dev/null || true
fi
fi
echo $$ > "$ENTRYPOINT_PID_FILE"
export HERMES_ENTRYPOINT_OWNER=$$
# Buat log world-writable sekarang (sebagai root) agar hermes bisa write setelah gosu
touch "$ENTRYPOINT_LOG" && chmod 666 "$ENTRYPOINT_LOG"
fi
exec > >(tee -a "$ENTRYPOINT_LOG") 2>&1
# ── Helper: Safe Sync (EXCLUDE large/temp files) ───────────────────────────
safe_sync() {
local SRC="$1"
local DST="$2"
if [ ! -d "$SRC" ]; then
echo " Source $SRC does not exist, skipping sync"
return 0
fi
mkdir -p "$DST"
echo " Syncing: $SRC -> $DST"
echo " Excluding: node_modules, .cache, logs, *.tmp, *.lock, *.pid, .local/share/uv"
rsync -a --checksum --delete \
--no-owner --no-group \
--exclude='.local/share/uv' \
--exclude='node_modules' \
--exclude='.cache' \
--exclude='.playwright' \
--exclude='logs' \
--exclude='*.log' \
--exclude='*.log.*' \
--exclude='*.tmp' \
--exclude='*.lock' \
--exclude='*.pid' \
--exclude='__pycache__' \
--exclude='state.db-wal' \
--exclude='state.db-shm' \
"$SRC/" "$DST/"
echo " βœ“ Sync completed"
}
# ── Stage 1: Restore from backup (as root, before privilege drop) ──────────
if [ "$(id -u)" = "0" ]; then
echo ""
echo "=== 1. Restoring Hermes data from persistent storage ==="
# Create working directory on fast local filesystem
mkdir -p "$HERMES_WORK"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}
mkdir -p "$HERMES_BACKUP"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}
chown -R ${HERMES_UID}:${HERMES_GID} "$HERMES_BACKUP"
# Restore from backup if exists
if [ -d "$HERMES_BACKUP" ] && [ "$(ls -A $HERMES_BACKUP 2>/dev/null | wc -l)" -gt 0 ]; then
echo "Found existing data in $HERMES_BACKUP"
RESTORE_START=$(date +%s)
safe_sync "$HERMES_BACKUP" "$HERMES_WORK"
RESTORE_END=$(date +%s)
echo "[TIMER] Restore from backup: $((RESTORE_END - RESTORE_START))s"
echo "Restore completed (sessions, memories, workspace restored)"
else
echo "No existing backup found, starting fresh"
fi
# Fix ownership on working directory (fast - only local files)
chown -R ${HERMES_UID}:${HERMES_GID} "$HERMES_WORK"
echo "βœ“ Working directory ready: $HERMES_WORK (owned by hermes)"
# Ensure /tmp is world-writable
chmod 1777 /tmp 2>/dev/null || true
echo ""
echo "=== 2. Starting Scheduled Backup (Every ${SYNC_INTERVAL}s) ==="
# Background backup loop β€” uses safe_sync which already excludes .local/share/uv
(while true; do
sleep ${SYNC_INTERVAL}
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backing up $HERMES_WORK -> $HERMES_BACKUP"
safe_sync "$HERMES_WORK" "$HERMES_BACKUP"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup completed"
done) &
BACKUP_PID=$!
echo "Backup loop started with PID $BACKUP_PID"
echo ""
echo "=== 3. Starting nginx reverse proxy (port 7860 β†’ 7861) ==="
nginx
echo " βœ“ nginx started (PID $(cat /run/nginx.pid 2>/dev/null || echo unknown))"
echo ""
echo "=== 4. Dropping privileges to hermes (uid ${HERMES_UID}) ==="
exec gosu ${HERMES_UID} "$0" "$@"
fi
# ── Stage 2: Run as hermes user ─────────────────────────────────────────────
echo ""
echo "=== 4. Activating Python virtual environment ==="
if [ -f "${INSTALL_DIR}/.venv/bin/activate" ]; then
source "${INSTALL_DIR}/.venv/bin/activate"
echo "βœ“ Activated venv: $(which python3)"
fi
# ── Bootstrap config files (in working dir, not mounted bucket) ─────────────
echo ""
echo "=== 5. Bootstrapping configuration files ==="
if [ ! -f "$HERMES_WORK/.env" ] && [ -f "$INSTALL_DIR/.env.example" ]; then
cp "$INSTALL_DIR/.env.example" "$HERMES_WORK/.env"
echo "βœ“ Created .env from example"
fi
if [ ! -f "$HERMES_WORK/config.yaml" ] && [ -f "$INSTALL_DIR/cli-config.yaml.example" ]; then
cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_WORK/config.yaml"
echo "βœ“ Created config.yaml from example"
fi
if [ ! -f "$HERMES_WORK/SOUL.md" ] && [ -f "$INSTALL_DIR/docker/SOUL.md" ]; then
cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_WORK/SOUL.md"
echo "βœ“ Created SOUL.md from template"
fi
# ── Dashboard basic auth ────────────────────────────────────────────────────
DASH_USER="${HERMES_DASHBOARD_USERNAME:-admin}"
DASH_PASS="${HERMES_DASHBOARD_PASSWORD:-}"
if [ -n "$DASH_PASS" ] && [ -f "$HERMES_WORK/config.yaml" ]; then
if ! grep -q 'basic_auth' "$HERMES_WORK/config.yaml"; then
cat >> "$HERMES_WORK/config.yaml" <<EOF
dashboard:
basic_auth:
username: ${DASH_USER}
password: ${DASH_PASS}
EOF
echo "βœ“ Injected dashboard.basic_auth into config.yaml"
fi
fi
if [ -n "$DASH_PASS" ]; then
export HERMES_DASHBOARD_BASIC_AUTH_USERNAME="$DASH_USER"
export HERMES_DASHBOARD_BASIC_AUTH_PASSWORD="$DASH_PASS"
fi
# ── Sync bundled skills ─────────────────────────────────────────────────────
if [ -d "$INSTALL_DIR/skills" ] && [ -f "$INSTALL_DIR/tools/skills_sync.py" ]; then
python3 "$INSTALL_DIR/tools/skills_sync.py" 2>&1 || echo "[entrypoint] Skills sync skipped"
fi
# ── Build artifacts check ───────────────────────────────────────────────────
echo ""
echo "=== 6. Build artifacts check ==="
test -f "$INSTALL_DIR/run_agent.py" && echo " βœ“ run_agent.py" || echo " ⚠ run_agent.py not found"
test -f "$INSTALL_DIR/gateway/run.py" && echo " βœ“ gateway/run.py" || echo " ⚠ gateway/run.py not found"
test -d "$INSTALL_DIR/web" && echo " βœ“ web/ dashboard" || echo " ⚠ web/ not found"
command -v hermes >/dev/null 2>&1 && echo " βœ“ hermes CLI: $(which hermes)" || echo " ⚠ hermes CLI not in PATH"
# ── Register Buzz profile ───────────────────────────────────────────────────
if [ -n "${BUZZ_RELAY_URL:-}" ] && [ -n "${BUZZ_PRIVATE_KEY:-}" ]; then
_BUZZ_NAME="${BUZZ_DISPLAY_NAME:-${AGENT_NAME:-$(hostname)}}"
echo ""
echo "=== 7. Setting Buzz profile name: ${_BUZZ_NAME} ==="
buzz users set-profile --name "${_BUZZ_NAME}" 2>&1 || \
echo "⚠ buzz users set-profile failed (non-fatal)"
fi
# ── Export working directory as HERMES_HOME ─────────────────────────────────
export HERMES_HOME="$HERMES_WORK"
echo ""
echo "=== 8. Environment Setup ==="
echo " Working directory (FAST): $HERMES_WORK"
echo " Backup directory (HF Bucket): $HERMES_BACKUP"
echo " Sync interval: ${SYNC_INTERVAL}s"
echo " HERMES_HOME: $HERMES_HOME"
ENTRYPOINT_END=$(date +%s)
echo ""
echo "[TIMER] Entrypoint setup: $((ENTRYPOINT_END - BOOT_START))s"
# ── Start Hermes Gateway ────────────────────────────────────────────────────
echo ""
echo "=== 9. Starting Hermes Agent ==="
echo " Hermes running on local filesystem: $HERMES_WORK (FAST)"
echo " Persistent storage: $HERMES_BACKUP (synced every ${SYNC_INTERVAL}s)"
echo ""
cd "$HERMES_WORK"
# Trap SIGTERM untuk final backup sebelum shutdown (dipasang di Stage 2 karena exec gosu menggantikan Stage 1)
trap "echo ''; echo 'Shutting down... performing final backup'; safe_sync '$HERMES_WORK' '$HERMES_BACKUP'; echo 'Final backup completed'; exit 0" SIGTERM SIGINT
# # Initial backup segera setelah start (jangan tunggu interval pertama)
# (sleep 30 && echo "[$(date '+%Y-%m-%d %H:%M:%S')] Initial post-start backup" && safe_sync "$HERMES_WORK" "$HERMES_BACKUP" && echo "[$(date '+%Y-%m-%d %H:%M:%S')] Initial backup done") &
# Start dashboard di background (port 7861 untuk HF Spaces)
"${INSTALL_DIR}/.venv/bin/hermes" dashboard --host 0.0.0.0 --port 7861 --no-open --insecure &
DASHBOARD_PID=$!
echo " Dashboard started (PID $DASHBOARD_PID) on :7860"
sleep 2
exec "${INSTALL_DIR}/.venv/bin/python3" -u "${INSTALL_DIR}/gateway/run.py"