#!/bin/sh # OpenClaw HF Spaces - Production Entrypoint # Storage: HF Storage Bucket at /data (100 GB persistent) # ---------------------------------------------------------------- # Logging # ---------------------------------------------------------------- ts() { date -u +"%H:%M:%S"; } log() { echo "[$(ts)] [entrypoint] $*"; } warn() { echo "[$(ts)] [entrypoint] WARN: $*"; } # ---------------------------------------------------------------- # Storage: resolve OPENCLAW_HOME # Prefer /data (HF Storage Bucket). Fall back to /home/user. # Retry up to 5s in case the bucket mount is slightly delayed. # ---------------------------------------------------------------- OPENCLAW_HOME=/home/user mkdir -p /home/user/.openclaw RETRIES=5 i=0 while [ $i -lt $RETRIES ]; do if [ -d /data ] && touch /data/.write-test 2>/dev/null; then rm -f /data/.write-test OPENCLAW_HOME=/data mkdir -p /data/.openclaw log "Storage bucket /data is writable - using persistent storage" break fi i=$((i+1)) if [ $i -lt $RETRIES ]; then log "Waiting for /data mount... ($i/$RETRIES)" sleep 1 fi done if [ "$OPENCLAW_HOME" = "/home/user" ]; then warn "/data not writable after ${RETRIES}s - using ephemeral /home/user" fi export OPENCLAW_HOME log "OPENCLAW_HOME=$OPENCLAW_HOME" # ---------------------------------------------------------------- # Export provider API keys from Secrets # ---------------------------------------------------------------- for VAR in $(env | cut -d= -f1); do case "$VAR" in OPENCLAW_*|SPACE_*|SYSTEM_*|HF_*|NODE_*|npm_*) continue ;; esac case "$VAR" in *_API_KEY|*_SECRET_KEY|*_ACCESS_TOKEN|*_BOT_TOKEN|*_AUTH_TOKEN|*_APP_KEY) VAL=$(printenv "$VAR" 2>/dev/null || true) if [ -n "$VAL" ]; then export "$VAR" log "exported: $VAR" fi ;; esac done export HF_TOKEN="${HF_TOKEN:-}" # ---------------------------------------------------------------- # One-time setup (config write, webhook registration, seed files) # ---------------------------------------------------------------- log "Running setup..." node /app/spaces/huggingface/setup-hf-config.mjs || true log "Setup done." # ---------------------------------------------------------------- # Security audit (non-fatal) # ---------------------------------------------------------------- if [ -f /app/security-check.sh ]; then sh /app/security-check.sh || true fi # ---------------------------------------------------------------- # Auto-approve pending device pairings (background) # ---------------------------------------------------------------- ( DEVICES_DIR="$OPENCLAW_HOME/.openclaw/devices" mkdir -p "$DEVICES_DIR" log "Device auto-pairing watcher started" while true; do sleep 8 PENDING="$DEVICES_DIR/pending.json" PAIRED="$DEVICES_DIR/paired.json" if [ -f "$PENDING" ]; then node - << 'JSEOF' const fs = require('fs'); const dir = process.env.OPENCLAW_HOME + '/.openclaw/devices'; const pend = dir + '/pending.json'; const pair = dir + '/paired.json'; try { const raw = fs.readFileSync(pend, 'utf-8').trim(); if (!raw || raw === '[]' || raw === '{}') process.exit(0); let pending = JSON.parse(raw); if (!Array.isArray(pending)) pending = Object.values(pending); if (pending.length === 0) process.exit(0); let paired = []; try { paired = JSON.parse(fs.readFileSync(pair, 'utf-8')); } catch(e) {} if (!Array.isArray(paired)) paired = []; const ids = new Set(paired.map(function(d){ return d.id||d.deviceId||d.name; })); const todo = pending.filter(function(d){ return !ids.has(d.id||d.deviceId||d.name); }); if (todo.length === 0) process.exit(0); const now = new Date().toISOString(); paired = paired.concat(todo.map(function(d){ return Object.assign({}, d, { approved: true, approvedAt: now }); })); fs.writeFileSync(pair, JSON.stringify(paired, null, 2)); fs.writeFileSync(pend, '[]'); console.log('[auto-pair] Approved ' + todo.length + ' device(s)'); } catch(e) { /* ignore */ } JSEOF fi done ) & # ---------------------------------------------------------------- # Gateway loop # # OpenClaw restart behaviour: # - On config save it spawns a child process (new gateway) then # the parent exits with code 0. # - The child inherits the port 7860 lock. # - We must NOT restart until the child also exits (port is free). # # Normal crash: port is immediately free, loop restarts quickly. # Config-save restart: port stays held by child; we wait up to 90s. # ---------------------------------------------------------------- log "Starting gateway loop..." while true; do log "Gateway starting..." node /app/openclaw.mjs gateway \ --allow-unconfigured \ --bind lan \ --port 7860 CODE=$? log "Gateway exited (code $CODE)" # Wait for port 7860 to be released before starting a new instance WAITED=0 while nc -z 127.0.0.1 7860 2>/dev/null; do if [ $WAITED -eq 0 ]; then log "Port 7860 still held (spawned child running) - waiting..." fi sleep 2 WAITED=$((WAITED + 2)) if [ $WAITED -ge 90 ]; then warn "Port 7860 still in use after 90s - forcing restart" break fi done if [ $WAITED -gt 0 ]; then log "Port free after ${WAITED}s" fi sleep 1 log "Restarting gateway..." done