FROM node:22-slim RUN apt-get update && apt-get install -y --no-install-recommends \ git openssh-client build-essential python3 python3-pip g++ make ca-certificates sqlite3 \ && rm -rf /var/lib/apt/lists/* RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages RUN update-ca-certificates && \ git config --global http.sslVerify false && \ git config --global url."https://github.com/".insteadOf ssh://git@github.com/ RUN npm install -g openclaw@2026.6.9 --unsafe-perm ENV PORT=7860 OPENCLAW_GATEWAY_MODE=local HOME=/root # ==================== 同步引擎 - 最终修复版 ==================== RUN cat > /usr/local/bin/sync.py << 'EOF' import os, sys, tarfile, shutil from huggingface_hub import HfApi, hf_hub_download from datetime import datetime, timedelta api = HfApi() repo_id = os.getenv("HF_DATASET") token = os.getenv("HF_TOKEN") BASE = "/root/.openclaw" def restore(): print("🔍 [Restore] Searching latest backup...") try: files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token) now = datetime.now() for i in range(5): day = (now - timedelta(days=i)).strftime("%Y-%m-%d") name = f"backup_{day}.tar.gz" if name in files: print(f"📦 Downloading {name}...") path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token) print("📂 Tar contents preview:") with tarfile.open(path, "r:gz") as tar: for member in tar.getmembers()[:20]: # 只打印前20个 print(" ", member.name) for member in tar.getmembers(): # 关键修复:统一放到 .openclaw 下 if member.name.startswith('agents/') or member.name.startswith('skills/') or \ member.name.startswith('workspace/') or member.name.startswith('cron/') or \ member.name.startswith('state/'): member.name = os.path.join('.openclaw', member.name) elif member.name == 'openclaw.json' or member.name == 'backup_state.sqlite': member.name = os.path.join('.openclaw', member.name) tar.extract(member, "/root") print("✅ Extraction completed") # 处理 DB if os.path.exists(f"{BASE}/backup_state.sqlite"): os.makedirs(f"{BASE}/state", exist_ok=True) shutil.move(f"{BASE}/backup_state.sqlite", f"{BASE}/state/openclaw.sqlite") print("✅ SQLite (cron) restored") return True print("⚠️ No backup found") except Exception as e: print(f"❌ Restore error: {e}") def backup(): print("📦 [Backup] Creating...") try: day = datetime.now().strftime("%Y-%m-%d") name = f"backup_{day}.tar.gz" with tarfile.open(name, "w:gz") as tar: if os.path.exists(BASE): tar.add(BASE, arcname=".openclaw") api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token) print(f"✅ Backup uploaded: {name}") except Exception as e: print(f"❌ Backup error: {e}") if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "backup": backup() else: restore() EOF # 模板 RUN mkdir -p /root/.openclaw RUN cat > /root/.openclaw/openclaw.json.template << 'EOT' {"models":{"providers":{"thirdparty":{"baseUrl":"PLACEHOLDER_BASE_URL","apiKey":"PLACEHOLDER_API_KEY","api":"openai-completions","headers":{"User-Agent":"Mozilla/5.0"},"models":[{"id":"PLACEHOLDER_MODEL_ID","name":"GLM-4.7-Flash","contextWindow":131072}]}}},"agents":{"defaults":{"model":{"primary":"thirdparty/PLACEHOLDER_MODEL_ID"}}},"gateway":{"mode":"local","bind":"lan","port":7860,"trustedProxies":["0.0.0.0/0","10.0.0.0/8","10.16.0.0/12","172.16.0.0/12","192.168.0.0/16"],"auth":{"mode":"token","token":"PLACEHOLDER_GATEWAY_PASSWORD"},"controlUi":{"enabled":true,"allowInsecureAuth":true,"dangerouslyAllowHostHeaderOriginFallback":true,"dangerouslyDisableDeviceAuth":true}}} EOT # 启动脚本 RUN cat > /usr/local/bin/start-openclaw << 'EOF' #!/bin/bash set -e mkdir -p /root/.openclaw/{sessions,state} echo "🚀 Starting OpenClaw..." python3 /usr/local/bin/sync.py restore sleep 8 if [ ! -d "/root/.openclaw/agents" ]; then echo "🔄 Retrying restore..." python3 /usr/local/bin/sync.py restore sleep 6 fi echo "=== Final Status ===" echo "Agents dir: $([ -d "/root/.openclaw/agents" ] && echo Yes || echo No)" echo "Sessions: $(ls /root/.openclaw/agents/main/sessions 2>/dev/null | wc -l || echo 0)" echo "Skills: $(ls /root/.openclaw/skills 2>/dev/null | wc -l || echo 0)" [ -f "/root/.openclaw/state/openclaw.sqlite" ] && echo "Cron jobs: $(sqlite3 /root/.openclaw/state/openclaw.sqlite "SELECT COUNT(*) FROM cron_jobs WHERE enabled=1;" 2>/dev/null || echo 0)" #python3 /usr/local/bin/sync.py backup CLEAN_BASE=$(echo "$OPENAI_API_BASE" | sed 's|/chat/completions||g' | sed 's|/v1/|/v1|g' | sed 's|/v1$|/v1|g') sed -e "s|PLACEHOLDER_BASE_URL|$CLEAN_BASE|g" \ -e "s|PLACEHOLDER_API_KEY|$OPENAI_API_KEY|g" \ -e "s|PLACEHOLDER_MODEL_ID|$MODEL|g" \ -e "s|PLACEHOLDER_GATEWAY_PASSWORD|$OPENCLAW_GATEWAY_PASSWORD|g" \ /root/.openclaw/openclaw.json.template > /root/.openclaw/openclaw.json echo "✅ OpenClaw started" (while true; do sleep 12000; python3 /usr/local/bin/sync.py backup; done) & openclaw doctor --fix exec openclaw gateway run --port $PORT EOF RUN chmod +x /usr/local/bin/start-openclaw EXPOSE 7860 CMD ["/usr/local/bin/start-openclaw"]