#!/bin/bash # Hugging Face Spaces startup script for OpenClaw echo "๐Ÿฆž Starting OpenClaw on Hugging Face Spaces..." # Create config directory mkdir -p /root/.openclaw/workspace # Check required env vars if [ -z "$API_KEY" ] || [ -z "$BASE_URL" ]; then echo "โš ๏ธ Warning: API_KEY or BASE_URL not set. AI features won't work." fi # ========================================== # GITHUB STATE SYNC (STARTUP PULL) # ========================================== OPENCLAW_DIR="/root/.openclaw" if [ -n "$MEMORY_REPO_URL" ] && [ -n "$MEMORY_REPO_TOKEN" ]; then echo "๐Ÿ”„ Memory Sync Enabled! Attempting to restore state from GitHub..." # Extract domain and path from repo URL to format it with the token # Supports formats like: https://github.com/user/repo or github.com/user/repo REPO_CLEAN=$(echo "$MEMORY_REPO_URL" | sed -e 's|^[^/]*//||' -e 's|^.*@||') AUTH_REPO_URL="https://${MEMORY_REPO_TOKEN}@${REPO_CLEAN}" # Use a temporary directory for cloning to avoid conflict with existing config SYNC_TMP="/tmp/openclaw_sync" rm -rf "$SYNC_TMP" # Try cloning the repository if git clone "$AUTH_REPO_URL" "$SYNC_TMP" 2>/dev/null; then echo "โœ… Successfully downloaded state from GitHub repository." # Copy over the state, ensuring the destination directory exists mkdir -p "$OPENCLAW_DIR" # We use rsync or standard cp to move contents (excluding .git from remote) # Note: copying everything EXCEPT the remote's .git to our local working dir. if [ "$(ls -A $SYNC_TMP)" ]; then cp -r "$SYNC_TMP/." "$OPENCLAW_DIR/" rm -rf "$OPENCLAW_DIR/.git" fi # Initialize Git in the actual OpenClaw directory so we can push back later cd "$OPENCLAW_DIR" git init git config user.email "openclaw-bot@huggingface.co" git config user.name "OpenClaw Auto-Sync" git remote add origin "$AUTH_REPO_URL" # Force the branch name if needed (assumes main) git checkout -B main # Reset local to match remote exactly git fetch origin main 2>/dev/null || true git reset --soft origin/main 2>/dev/null || true echo "โœ… State restoration complete!" else echo "โŒ Failed to clone repository. Is the token valid and URL format correct? Starting fresh..." fi else echo "โ„น๏ธ MEMORY_REPO_URL or MEMORY_REPO_TOKEN not provided. State will NOT be persisted across restarts." fi # ========================================== # CLEAN STALE SESSION LOCKS # ========================================== # When Hugging Face spaces restart unexpectedly, lock files might be left behind. # Because Docker PIDs start at 1 again, OpenClaw might think the old lock is still active # and time out trying to access the session. We wipe them cleanly on boot. if [ -d "/root/.openclaw/agents" ]; then echo "๐Ÿงน Cleaning up any stale session lock files on boot..." find /root/.openclaw/agents -type f -name "*.jsonl.lock" -exec rm -f {} + fi # Set defaults BASE_URL=${BASE_URL:-"http://127.0.0.1:20128/v1"} MODEL_ID=${MODEL_ID:-"Multiple-models"} PROVIDER_NAME=${PROVIDER_NAME:-"custom"} # Create necessary OpenClaw memory files to prevent ENOENT errors mkdir -p /root/.openclaw/workspace/memory touch /root/.openclaw/workspace/MEMORY.md touch "/root/.openclaw/workspace/memory/$(date -u +%Y-%m-%d).md" touch "/root/.openclaw/workspace/memory/$(date -u -d "yesterday" +%Y-%m-%d 2>/dev/null || date -u -v -1d +%Y-%m-%d 2>/dev/null || echo "").md" 2>/dev/null || true # Create OpenClaw config cat > /root/.openclaw/openclaw.json << EOF { "meta": { "lastTouchedVersion": "2026.2.19-2" }, "models": { "providers": { "${PROVIDER_NAME}": { "baseUrl": "${BASE_URL}", "apiKey": "${API_KEY}", "api": "openai-completions", "models": [ { "id": "${MODEL_ID}", "name": "${MODEL_ID}" } ] } } }, "agents": { "defaults": { "model": { "primary": "${PROVIDER_NAME}/${MODEL_ID}" }, "workspace": "/root/.openclaw/workspace", "timeoutSeconds": 300, "lockTimeoutMs": 120000 } }, "commands": { "native": "auto", "nativeSkills": "auto", "restart": false }, "hooks": { "internal": { "enabled": true, "entries": { "boot-md": { "enabled": true }, "command-logger": { "enabled": true }, "session-memory": { "enabled": true } } } }, "session": { "dmScope": "per-channel-peer" }, "gateway": { "port": 7860, "mode": "local", "bind": "lan", "controlUi": { "dangerouslyDisableDeviceAuth": true }, "trustedProxies": ["0.0.0.0/0"], "tailscale": { "mode": "off" } }, "skills": { "install": { "nodeManager": "npm" } }, "plugins": { "entries": {} } } EOF echo "โœ… Config created" echo "๐Ÿ“ก API: ${BASE_URL}" echo "๐Ÿค– Model: ${MODEL_ID}" echo "" echo "๐ŸŒ Starting OpenClaw Gateway..." echo "๐Ÿ“ฑ Open your Space URL to access the dashboard" echo "๐Ÿ”‘ Note: OpenClaw will natively generate a Gateway Token on its first run. Check these initialization logs below to find it!" echo "" # Start OpenClaw gateway directly in the background openclaw gateway & GATEWAY_PID=$! sleep 4 # Run a background task to display the token right after initialization ( echo "" echo "==========================================================================" # Try to grab the token from the updated config file TOKEN=$(grep -E '"token"\s*:' /root/.openclaw/openclaw.json | head -1 | awk -F '"' '{print $4}') if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then # Fallback to CLI command TOKEN=$(openclaw config get gateway.auth.token 2>/dev/null) fi echo "๐Ÿ”‘ GENERATED GATEWAY TOKEN: $TOKEN" echo "๐Ÿ‘‰ Use this token in the OpenClaw pairing screen!" echo "==========================================================================" echo "" ) # ========================================== # GITHUB STATE SYNC (CONTINUOUS PUSH) # ========================================== if [ -n "$MEMORY_REPO_URL" ] && [ -n "$MEMORY_REPO_TOKEN" ]; then echo "๐Ÿ”„ Starting background memory synchronization..." # Extract domain and path again just in case for the fallback initialization REPO_CLEAN=$(echo "$MEMORY_REPO_URL" | sed -e 's|^[^/]*//||' -e 's|^.*@||') AUTH_REPO_URL="https://${MEMORY_REPO_TOKEN}@${REPO_CLEAN}" ( # Ensure working directory exists before jumping in mkdir -p "$OPENCLAW_DIR" cd "$OPENCLAW_DIR" || exit # If for some reason git wasn't initialized during the startup step (e.g., clone failed because repo was empty), # we initialize it here as a fallback before entering the infinite loop. if [ ! -d ".git" ]; then echo "โš ๏ธ Git not initialized in $OPENCLAW_DIR. Initializing fallback..." git init git config user.email "openclaw-bot@huggingface.co" git config user.name "OpenClaw Auto-Sync" git remote add origin "$AUTH_REPO_URL" git checkout -B main fi # Ensure .gitignore prevents pushing bad/heavy files cat > .gitignore << 'IGNORE' node_modules/ .env *.jsonl.lock IGNORE # Untrack any lock files that may have accidentally been added previously find "$OPENCLAW_DIR" -type f -name "*.jsonl.lock" -exec git rm --cached {} + 2>/dev/null || true while true; do # Sync every 30 seconds sleep 30 # Remove any nested .git directories that confuse our top-level git repo # This prevents "error: 'workspace/' does not have a commit checked out" find "$OPENCLAW_DIR" -mindepth 2 -type d -name ".git" -exec rm -rf {} + # Add all changes git add . # Commit will fail cleanly if there are no changes, acting as our checker if git commit -m "Auto-sync OpenClaw state [$(date -u)]" >/dev/null 2>&1; then echo "[$(date -u)] ๐Ÿ”„ Syncing OpenClaw memory state to GitHub..." # Try to pull in case of external changes to avoid conflicts, then push git pull --rebase origin main || echo "โš ๏ธ Git pull failed" git push -u origin main || echo "โŒ Git push failed! Please check token permissions." # echo "[$(date -u)] โœ… Memory state saved." fi done ) & fi # Wait for the main gateway process wait $GATEWAY_PID