# ========= Base ========= FROM node:22-slim # ========= System deps ========= RUN apt-get update && apt-get install -y --no-install-recommends \ git \ openssh-client \ build-essential \ python3 \ python3-pip \ ca-certificates \ curl \ jq \ && rm -rf /var/lib/apt/lists/* # ========= HF CLI & Python deps ========= RUN pip3 install --no-cache-dir huggingface_hub requests slack-sdk --break-system-packages # ========= Git HTTPS fallback ========= RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/ # ========= OpenClaw ========= RUN npm install -g openclaw@latest --unsafe-perm # ========= Copy scripts ========= COPY auto-approve.py /usr/local/bin/auto-approve.py RUN chmod +x /usr/local/bin/auto-approve.py # ========= ENV ========= ENV PORT=7860 \ OPENCLAW_GATEWAY_MODE=local \ HOME=/root # ========= sync.py ========= RUN cat <<'PYEOF' > /usr/local/bin/sync.py import os, sys, tarfile 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") def restore(): try: print(f"--- [SYNC] Restore from {repo_id} ---") if not repo_id or not token: print("--- [SYNC] Skipped (no config) ---") return 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: path = hf_hub_download( repo_id=repo_id, filename=name, repo_type="dataset", token=token ) with tarfile.open(path, "r:gz") as tar: tar.extractall("/root/.openclaw/") print("--- [SYNC] Restore OK ---") return print("--- [SYNC] No backup found ---") except Exception as e: print("Restore error:", e) def backup(): try: day = datetime.now().strftime("%Y-%m-%d") name = f"backup_{day}.tar.gz" with tarfile.open(name, "w:gz") as tar: for t in ["sessions","workspace","agents","memory","openclaw.json"]: p=f"/root/.openclaw/{t}" if os.path.exists(p): tar.add(p, arcname=t) api.upload_file( path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token ) print("--- [SYNC] Backup OK ---") except Exception as e: print("Backup error:", e) if __name__ == "__main__": if len(sys.argv)>1 and sys.argv[1]=="backup": backup() else: restore() PYEOF # ========= start-openclaw ========= RUN cat <<'SHEOF' > /usr/local/bin/start-openclaw #!/bin/bash set -e mkdir -p /root/.openclaw/{sessions,workspace,credentials} chmod 700 /root/.openclaw # Restore state python3 /usr/local/bin/sync.py # Clean base url safely if [ -n "$OPENAI_API_BASE" ]; then CLEAN_BASE=$(echo "$OPENAI_API_BASE" \ | sed "s|/chat/completions||g" \ | sed "s|/v1/|/v1|g" \ | sed "s|/v1$|/v1|g") else CLEAN_BASE="https://api.siliconflow.cn/v1" fi # Slack credentials if [ -n "$SLACK_BOT_TOKEN" ]; then cat > /root/.openclaw/credentials/slack.json < /root/.openclaw/openclaw.json </dev/null 2>&1; then echo "--- Gateway ready ---" break fi sleep 1 done # Auto approve Slack pairing echo "--- Starting auto-approve.py ---" python3 -u /usr/local/bin/auto-approve.py & # Wait for frontend pairing echo "--- Waiting for frontend pairing ---" paired=0 for i in {1..60}; do status=$(curl -s -H "Authorization: Bearer $OPENCLAW_GATEWAY_PASSWORD" \ http://127.0.0.1:$PORT/api/status || echo "") if echo "$status" | grep -q '"connectedClients":[1-9]'; then echo "✅ Gateway paired with frontend" paired=1 break fi sleep 1 done if [ "$paired" -ne 1 ]; then echo "⚠️ Warning: no frontend connected after 60s" fi # Keep foreground wait SHEOF RUN chmod +x /usr/local/bin/start-openclaw # ========= Ports & CMD ========= EXPOSE 7860 CMD ["/usr/local/bin/start-openclaw"]