# 核心镜像:Node 22 slim FROM node:22-slim RUN apt-get update && apt-get install -y --no-install-recommends tini \ && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y --no-install-recommends \ git openssh-client build-essential python3 python3-pip g++ make ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages RUN update-ca-certificates RUN npm install -g openclaw@latest --unsafe-perm RUN mkdir -p /home/node/.openclaw && chown -R node:node /home/node/.openclaw ENV PORT=7860 \ OPENCLAW_GATEWAY_MODE=local \ HOME=/home/node # sync.py(备份排除 openclaw.json,防止旧配置污染) RUN echo 'import os, sys, tarfile\n\ from huggingface_hub import HfApi, hf_hub_download\n\ from datetime import datetime, timedelta\n\ \n\ api = HfApi()\n\ repo_id = os.getenv("HF_DATASET")\n\ token = os.getenv("HF_TOKEN")\n\ DATA_DIR = os.path.expanduser("~/.openclaw")\n\ \n\ def restore():\n\ try:\n\ print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---")\n\ if not repo_id or not token:\n\ print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---")\n\ return False\n\ files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)\n\ now = datetime.now()\n\ for i in range(5):\n\ day = (now - timedelta(days=i)).strftime("%Y-%m-%d")\n\ name = f"backup_{day}.tar.gz"\n\ if name in files:\n\ print(f"--- [SYNC] 发现备份文件: {name}, 正在下载... ---")\n\ path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token)\n\ with tarfile.open(path, "r:gz") as tar:\n\ tar.extractall(path=DATA_DIR)\n\ print(f"--- [SYNC] 恢复成功! 数据已覆盖至 {DATA_DIR} ---")\n\ return True\n\ print("--- [SYNC] 未找到最近 5 天的备份包 ---")\n\ except Exception as e:\n\ print(f"--- [SYNC] 恢复异常: {e} ---")\n\ \n\ def backup():\n\ try:\n\ targets = ["sessions", "workspace", "agents", "memory"]\n\ existing = [t for t in targets if os.path.exists(os.path.join(DATA_DIR, t))]\n\ if not existing:\n\ print("--- [SYNC] 没有需要备份的数据,跳过备份 ---")\n\ return\n\ day = datetime.now().strftime("%Y-%m-%d")\n\ name = f"backup_{day}.tar.gz"\n\ print(f"--- [SYNC] 正在执行全量备份: {name} ---")\n\ with tarfile.open(name, "w:gz") as tar:\n\ for target in existing:\n\ tar.add(os.path.join(DATA_DIR, target), arcname=target)\n\ api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)\n\ print(f"--- [SYNC] 备份上传成功! ---")\n\ os.remove(name)\n\ except Exception as e:\n\ print(f"--- [SYNC] 备份失败: {e} ---")\n\ \n\ if __name__ == "__main__":\n\ if len(sys.argv) > 1 and sys.argv[1] == "backup":\n\ backup()\n\ else:\n\ restore()\n\ ' > /usr/local/bin/sync.py # 入口脚本 RUN printf '#!/bin/bash\nset -e\n\n\ : "${OPENAI_API_BASE:?OPENAI_API_BASE not set}"\n\ : "${OPENAI_API_KEY:?OPENAI_API_KEY not set}"\n\ : "${MODEL:?MODEL not set}"\n\ : "${OPENCLAW_GATEWAY_PASSWORD:?OPENCLAW_GATEWAY_PASSWORD not set}"\n\n\ DATA_DIR="$HOME/.openclaw"\n\ mkdir -p "$DATA_DIR"/{sessions,workspace,agents/main/sessions}\n\n\ python3 /usr/local/bin/sync.py restore\n\n\ # 恢复后强制覆写 openclaw.json,防止旧配置污染\n\ cat > "$DATA_DIR/openclaw.json" < /usr/local/bin/start-openclaw && chmod +x /usr/local/bin/start-openclaw USER node EXPOSE 7860 ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/usr/local/bin/start-openclaw"]