Spaces:
Sleeping
Sleeping
Somrat Sorkar
Add OpenRouter support + 15+ popular models (Qwen, Grok, MiMo, Seed, Nemotron, GLM, Mercury, etc.)
b19cd4a | set -e | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HuggingClaw β OpenClaw Gateway for HF Spaces | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ββ Startup Banner ββ | |
| OPENCLAW_VERSION="${OPENCLAW_VERSION:-latest}" | |
| echo "" | |
| echo " ββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo " β π¦ HuggingClaw Gateway β" | |
| echo " ββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo "" | |
| # ββ Validate required secrets ββ | |
| ERRORS="" | |
| if [ -z "$LLM_API_KEY" ]; then | |
| ERRORS="${ERRORS} β LLM_API_KEY is not set\n" | |
| fi | |
| if [ -z "$LLM_MODEL" ]; then | |
| ERRORS="${ERRORS} β LLM_MODEL is not set (e.g. google/gemini-2.5-flash, anthropic/claude-sonnet-4-5, openai/gpt-4)\n" | |
| fi | |
| if [ -z "$GATEWAY_TOKEN" ]; then | |
| ERRORS="${ERRORS} β GATEWAY_TOKEN is not set (generate: openssl rand -hex 32)\n" | |
| fi | |
| if [ -n "$ERRORS" ]; then | |
| echo "Missing required secrets:" | |
| echo -e "$ERRORS" | |
| echo "Add them in HF Spaces β Settings β Secrets" | |
| exit 1 | |
| fi | |
| # ββ Set LLM env based on model name ββ | |
| # Auto-correct Gemini models to use google/ prefix if anthropic/ was mistakenly used | |
| if [[ "$LLM_MODEL" == "anthropic/gemini"* ]]; then | |
| LLM_MODEL=$(echo "$LLM_MODEL" | sed 's/^anthropic\//google\//') | |
| echo "β οΈ Corrected model from anthropic/gemini* to google/gemini*" | |
| fi | |
| # Auto-detect and set provider-specific API key from model name | |
| if [[ "$LLM_MODEL" == "openrouter/"* ]]; then | |
| export OPENROUTER_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "google/"* ]]; then | |
| export GOOGLE_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "openai/"* ]]; then | |
| export OPENAI_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "zhipu/"* ]] || [[ "$LLM_MODEL" == "zai/"* ]]; then | |
| export ZHIPU_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "moonshot/"* ]]; then | |
| export MOONSHOT_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "minimax/"* ]]; then | |
| export MINIMAX_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "mistral/"* ]] || [[ "$LLM_MODEL" == "mistralai/"* ]]; then | |
| export MISTRAL_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "cohere/"* ]]; then | |
| export COHERE_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "groq/"* ]]; then | |
| export GROQ_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "qwen/"* ]]; then | |
| export QWEN_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "x-ai/"* ]] || [[ "$LLM_MODEL" == "xai/"* ]]; then | |
| export XAI_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "nvidia/"* ]]; then | |
| export NVIDIA_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "reka/"* ]]; then | |
| export REKA_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "bytedance/"* ]] || [[ "$LLM_MODEL" == "seed/"* ]]; then | |
| export BYTEDANCE_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "kwaipilot/"* ]]; then | |
| export KWAIPILOT_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "z-ai/"* ]]; then | |
| export ZAI_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "inception/"* ]]; then | |
| export INCEPTION_API_KEY="$LLM_API_KEY" | |
| elif [[ "$LLM_MODEL" == "xiaomi/"* ]]; then | |
| export XIAOMI_API_KEY="$LLM_API_KEY" | |
| else | |
| # Default to Anthropic for claude/* or anthropic/* models | |
| export ANTHROPIC_API_KEY="$LLM_API_KEY" | |
| fi | |
| # ββ Setup directories ββ | |
| mkdir -p /home/node/.openclaw/agents/main/sessions | |
| mkdir -p /home/node/.openclaw/credentials | |
| mkdir -p /home/node/.openclaw/workspace | |
| chmod 700 /home/node/.openclaw | |
| # ββ Validate HF token (if provided) ββ | |
| if [ -n "$HF_TOKEN" ]; then | |
| echo "π Validating HF token..." | |
| HF_AUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $HF_TOKEN" https://huggingface.co/api/repos/create --max-time 10 2>/dev/null || echo "000") | |
| if [ "$HF_AUTH_STATUS" = "401" ]; then | |
| echo " β οΈ HF token is invalid or expired! Workspace backup will not work." | |
| echo " Get a new token: https://huggingface.co/settings/tokens" | |
| else | |
| echo " β HF token is valid" | |
| fi | |
| fi | |
| # ββ Auto-create + Restore workspace from HF Dataset ββ | |
| if [ -n "$HF_USERNAME" ] && [ -n "$HF_TOKEN" ]; then | |
| BACKUP_DATASET="${BACKUP_DATASET_NAME:-huggingclaw-backup}" | |
| BACKUP_URL="https://${HF_USERNAME}:${HF_TOKEN}@huggingface.co/datasets/${HF_USERNAME}/${BACKUP_DATASET}" | |
| # Auto-create the dataset if it doesn't exist | |
| echo "π¦ Checking HF Dataset: ${HF_USERNAME}/${BACKUP_DATASET}..." | |
| DATASET_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| -H "Authorization: Bearer $HF_TOKEN" \ | |
| "https://huggingface.co/api/datasets/${HF_USERNAME}/${BACKUP_DATASET}" \ | |
| --max-time 10 2>/dev/null || echo "000") | |
| if [ "$DATASET_CHECK" = "404" ]; then | |
| echo " π Dataset not found, creating ${HF_USERNAME}/${BACKUP_DATASET}..." | |
| CREATE_RESULT=$(curl -s -w "\n%{http_code}" \ | |
| -X POST "https://huggingface.co/api/repos/create" \ | |
| -H "Authorization: Bearer $HF_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{\"type\":\"dataset\",\"name\":\"${BACKUP_DATASET}\",\"private\":true}" \ | |
| --max-time 15 2>/dev/null || echo "error") | |
| CREATE_STATUS=$(echo "$CREATE_RESULT" | tail -1) | |
| if [ "$CREATE_STATUS" = "200" ] || [ "$CREATE_STATUS" = "201" ]; then | |
| echo " β Dataset created: ${HF_USERNAME}/${BACKUP_DATASET} (private)" | |
| else | |
| echo " β οΈ Could not create dataset (HTTP $CREATE_STATUS). Create it manually:" | |
| echo " https://huggingface.co/datasets/create" | |
| fi | |
| elif [ "$DATASET_CHECK" = "200" ]; then | |
| echo " β Dataset exists" | |
| else | |
| echo " β οΈ Could not check dataset (HTTP $DATASET_CHECK)" | |
| fi | |
| # Restore workspace | |
| echo "π¦ Restoring workspace..." | |
| WORKSPACE="/home/node/.openclaw/workspace" | |
| GIT_USER_EMAIL="${WORKSPACE_GIT_USER:-openclaw@example.com}" | |
| GIT_USER_NAME="${WORKSPACE_GIT_NAME:-OpenClaw Bot}" | |
| cd "$WORKSPACE" | |
| if [ ! -d ".git" ]; then | |
| git init -q | |
| git remote add origin "$BACKUP_URL" | |
| else | |
| git remote set-url origin "$BACKUP_URL" | |
| fi | |
| git config user.email "$GIT_USER_EMAIL" | |
| git config user.name "$GIT_USER_NAME" | |
| if git fetch origin main 2>/dev/null; then | |
| git reset --hard origin/main 2>/dev/null && echo " β Workspace restored!" | |
| else | |
| echo " β οΈ No remote data yet, starting fresh." | |
| fi | |
| cd / | |
| fi | |
| # ββ Build config ββ | |
| CONFIG_JSON=$(cat <<'CONFIGEOF' | |
| { | |
| "gateway": { | |
| "mode": "local", | |
| "port": 7860, | |
| "bind": "lan", | |
| "auth": { | |
| "token": "" | |
| }, | |
| "controlUi": { | |
| "allowInsecureAuth": true | |
| }, | |
| "trustedProxies": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] | |
| }, | |
| "channels": {}, | |
| "plugins": { | |
| "entries": {} | |
| } | |
| } | |
| CONFIGEOF | |
| ) | |
| # Gateway token | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".gateway.auth.token = \"$GATEWAY_TOKEN\"") | |
| # Model configuration at top level | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".agents.defaults.model = \"$LLM_MODEL\"") | |
| # Control UI origin (allow HF Space URL for web UI access) | |
| if [ -n "$SPACE_HOST" ]; then | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".gateway.controlUi.allowedOrigins = [\"https://${SPACE_HOST}\"]") | |
| fi | |
| # Disable device auth (pairing) for headless Docker β token-only auth | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".gateway.controlUi.dangerouslyDisableDeviceAuth = true") | |
| # Telegram (supports multiple user IDs, comma-separated) | |
| if [ -n "$TELEGRAM_BOT_TOKEN" ]; then | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq '.plugins.entries.telegram = {"enabled": true}') | |
| export TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" | |
| if [ -n "$TELEGRAM_USER_IDS" ]; then | |
| # Convert comma-separated IDs to JSON array | |
| IDS_JSON=$(echo "$TELEGRAM_USER_IDS" | tr ',' '\n' | sed 's/^ *//;s/ *$//' | jq -R . | jq -s .) | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".channels.telegram = {\"dmPolicy\": \"allowlist\", \"allowFrom\": $IDS_JSON}") | |
| elif [ -n "$TELEGRAM_USER_ID" ]; then | |
| # Single user (backward compatible) | |
| CONFIG_JSON=$(echo "$CONFIG_JSON" | jq ".channels.telegram = {\"dmPolicy\": \"allowlist\", \"allowFrom\": [\"$TELEGRAM_USER_ID\"]}") | |
| fi | |
| fi | |
| # Write config | |
| echo "$CONFIG_JSON" > "/home/node/.openclaw/openclaw.json" | |
| # ββ Startup Summary ββ | |
| echo "" | |
| echo " ββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo " β π Configuration Summary β" | |
| echo " ββββββββββββββββββββββββββββββββββββββββββββ€" | |
| printf " β %-40s β\n" "Model: $LLM_MODEL" | |
| if [ -n "$TELEGRAM_BOT_TOKEN" ]; then | |
| printf " β %-40s β\n" "Telegram: β enabled" | |
| else | |
| printf " β %-40s β\n" "Telegram: β not configured" | |
| fi | |
| if [ -n "$HF_USERNAME" ] && [ -n "$HF_TOKEN" ]; then | |
| printf " β %-40s β\n" "Backup: β ${HF_USERNAME}/${BACKUP_DATASET:-huggingclaw-backup}" | |
| else | |
| printf " β %-40s β\n" "Backup: β not configured" | |
| fi | |
| if [ -n "$SPACE_HOST" ]; then | |
| printf " β %-40s β\n" "Keep-alive: β every ${KEEP_ALIVE_INTERVAL:-300}s" | |
| printf " β %-40s β\n" "Control UI: https://${SPACE_HOST}" | |
| else | |
| printf " β %-40s β\n" "Keep-alive: βΈοΈ local mode" | |
| fi | |
| SYNC_STATUS="β disabled" | |
| if [ -n "$HF_USERNAME" ] && [ -n "$HF_TOKEN" ]; then | |
| SYNC_STATUS="β every ${SYNC_INTERVAL:-600}s" | |
| fi | |
| printf " β %-40s β\n" "Auto-sync: $SYNC_STATUS" | |
| echo " ββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo "" | |
| # ββ Trap SIGTERM for graceful shutdown ββ | |
| graceful_shutdown() { | |
| echo "" | |
| echo "π Shutting down gracefully..." | |
| # Commit any unsaved workspace changes | |
| if [ -d "/home/node/.openclaw/workspace/.git" ]; then | |
| echo "πΎ Saving workspace before exit..." | |
| cd /home/node/.openclaw/workspace | |
| git add -A 2>/dev/null | |
| if ! git diff --cached --quiet 2>/dev/null; then | |
| TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| git commit -m "Shutdown sync ${TIMESTAMP}" 2>/dev/null | |
| git push origin main 2>/dev/null && echo " β Workspace saved!" || echo " β οΈ Push failed" | |
| else | |
| echo " β No unsaved changes" | |
| fi | |
| fi | |
| # Kill background processes | |
| kill $(jobs -p) 2>/dev/null | |
| echo "π Goodbye!" | |
| exit 0 | |
| } | |
| trap graceful_shutdown SIGTERM SIGINT | |
| # ββ Start background services ββ | |
| node /home/node/app/health-server.js & | |
| /home/node/app/keep-alive.sh & | |
| /home/node/app/workspace-sync.sh & | |
| # ββ Launch gateway ββ | |
| echo "π Launching OpenClaw gateway on port 7860..." | |
| echo "" | |
| # Set model via environment for the gateway | |
| export LLM_MODEL="$LLM_MODEL" | |
| openclaw gateway run --port 7860 --bind lan --verbose 2>&1 | tee -a /home/node/.openclaw/gateway.log & | |
| GATEWAY_PID=$! | |
| # Wait a moment for startup errors | |
| sleep 3 | |
| if ! kill -0 $GATEWAY_PID 2>/dev/null; then | |
| echo "" | |
| echo "β Gateway failed to start. Last 30 lines of log:" | |
| echo "ββββββββββββββββββββββββββββββββββββββββββββ" | |
| tail -30 /home/node/.openclaw/gateway.log | |
| exit 1 | |
| fi | |
| # Wait for gateway (allows trap to fire) | |
| wait $GATEWAY_PID | |