| 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"] |