# 核心镜像:使用 node-slim 保持轻量 FROM node:22-slim # 1. 整合系统依赖安装 RUN apt-get update && apt-get install -y --no-install-recommends \ git python3 python3-pip ca-certificates procps tzdata \ && rm -rf /var/lib/apt/lists/* # 2. 安装 Python 同步依赖 (保持 --break-system-packages 以适配新版镜像) RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages # 3. 安装核心程序:合并清理指令 ARG OPENCLAW_VERSION=2026.2.26 RUN npm install -g openclaw@${OPENCLAW_VERSION} --registry=https://registry.npmjs.org/ \ --unsafe-perm=true --foreground-scripts && npm cache clean --force # 4. 环境变量预设 ENV TZ=Asia/Shanghai \ PORT=7860 \ HOME=/root \ OPENCLAW_TRUST_LOCAL_WS=1 \ OPENCLAW_SECURITY_STRICT=false \ NODE_TLS_REJECT_UNAUTHORIZED=0 \ OPENCLAW_TRUST_PROXY=true \ NODE_ENV=production # 5. 同步引擎 (修复了 Python 嵌套转义导致的 SyntaxError) RUN echo 'import os, sys, tarfile, time\n\ from huggingface_hub import HfApi, hf_hub_download\n\ from datetime import datetime, timedelta\n\ api = HfApi()\n\ repo_id = os.getenv("HF_DATASET")\n\ token = os.getenv("HF_TOKEN")\n\ base_dir = "/root"\n\ \n\ def restore():\n\ if not repo_id or not token: return\n\ try:\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 = "backup_" + day + ".tar.gz"\n\ if name in files:\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: tar.extractall(path=base_dir)\n\ print("--- [Sync] ✅ 恢复成功: " + day + " ---")\n\ return True\n\ except Exception as e: print("--- [Sync] ❌ 恢复失败: " + str(e))\n\ \n\ def backup():\n\ if not repo_id or not token: return\n\ try:\n\ target_dir = "/root/.openclaw"\n\ if not os.path.exists(target_dir): return\n\ day_str = datetime.now().strftime("%Y-%m-%d")\n\ name = "backup_" + day_str + ".tar.gz"\n\ with tarfile.open(name, "w:gz") as tar: tar.add(target_dir, arcname=".openclaw")\n\ api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)\n\ if os.path.exists(name): os.remove(name)\n\ print("--- [Sync] ✨ 备份完成: " + name + " ---")\n\ except Exception as e: print("--- [Sync] ❌ 备份失败: " + str(e))\n\ \n\ if __name__ == "__main__":\n\ if len(sys.argv) > 1 and sys.argv[1] == "backup": backup()\n\ else: restore()' > /usr/local/bin/sync.py # 6. 最终启动脚本 (优化配置合并策略:严格深度合并 + 数组按ID智能合并) RUN echo "#!/bin/bash\n\ mkdir -p /root/.openclaw\n\ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\n\ \n\ echo \"--- [System] 📦 1. 尝试从云端恢复配置... ---\"\n\ python3 /usr/local/bin/sync.py restore || true\n\ find /root/.openclaw -name \"*.lock\" -delete\n\ chmod 700 /root/.openclaw\n\ \n\ echo \"--- [System] 📝 2. 开始合并默认配置与已恢复的配置... ---\"\n\ export CLEAN_BASE=\$(echo \"\$OPENAI_API_BASE\" | sed \"s|/chat/completions||g\" | sed \"s|/v1/|/v1|g\")\n\ \n\ # 使用 Python 深度合并 JSON (包含数组按ID合并逻辑)\n\ python3 -c \"\n\ import json, os\n\ \n\ def deep_update(d, u):\n\ for k, v in u.items():\n\ if isinstance(v, dict) and k in d and isinstance(d[k], dict):\n\ deep_update(d[k], v)\n\ elif isinstance(v, list) and k in d and isinstance(d[k], list):\n\ # 处理数组合并逻辑\n\ d_dict = {}\n\ non_id_d = []\n\ # 解析原配置(恢复的)数组\n\ for item in d[k]:\n\ if isinstance(item, dict) and 'id' in item:\n\ d_dict[item['id']] = item\n\ else:\n\ non_id_d.append(item)\n\ \n\ non_id_u = []\n\ # 解析新配置(环境变量)数组并进行合并\n\ for item in v:\n\ if isinstance(item, dict) and 'id' in item:\n\ if item['id'] in d_dict:\n\ # id相同,对内部字段继续深度合并(以新配置为准覆盖)\n\ d_dict[item['id']] = deep_update(d_dict[item['id']], item)\n\ else:\n\ d_dict[item['id']] = item\n\ else:\n\ # 对于普通元素(如字符串IP),去重合并\n\ if item not in non_id_d:\n\ non_id_u.append(item)\n\ \n\ # 组合合并后的数组\n\ d[k] = list(d_dict.values()) + non_id_d + non_id_u\n\ else:\n\ d[k] = v\n\ return d\n\ \n\ config_path = '/root/.openclaw/openclaw.json'\n\ current_config = {}\n\ \n\ if os.path.exists(config_path):\n\ try:\n\ with open(config_path, 'r', encoding='utf-8') as f:\n\ current_config = json.load(f)\n\ except Exception:\n\ pass\n\ \n\ clean_base = os.environ.get('CLEAN_BASE', '')\n\ api_key = os.environ.get('OPENAI_API_KEY', '')\n\ model = os.environ.get('MODEL', '')\n\ gateway_pw = os.environ.get('OPENCLAW_GATEWAY_PASSWORD', '')\n\ \n\ default_config = {\n\ 'models': {\n\ 'providers': {\n\ 'siliconflow': {\n\ 'baseUrl': clean_base,\n\ 'apiKey': api_key,\n\ 'api': 'openai-completions',\n\ 'authHeader': True,\n\ 'models': [{'id': model, 'name': 'DeepSeek', 'contextWindow': 128000}]\n\ }\n\ }\n\ },\n\ 'agents': {\n\ 'defaults': {\n\ 'model': {'primary': 'siliconflow/' + model}\n\ }\n\ },\n\ 'gateway': {\n\ 'mode': 'local', 'port': 7860, 'bind': 'custom', 'customBindHost': '0.0.0.0',\n\ 'trustedProxies': ['10.0.0.0/8'],\n\ 'auth': {'mode': 'token', 'token': gateway_pw},\n\ 'controlUi': {\n\ 'enabled': True,\n\ 'allowInsecureAuth': True,\n\ 'dangerouslyDisableDeviceAuth': True,\n\ 'dangerouslyAllowHostHeaderOriginFallback': True\n\ },\n\ 'tools': {'deny': ['gateway']}\n\ }\n\ }\n\ \n\ merged_config = deep_update(current_config, default_config)\n\ \n\ with open(config_path, 'w', encoding='utf-8') as f:\n\ json.dump(merged_config, f, indent=2, ensure_ascii=False)\n\ \"\n\ echo \"--- [System] ✅ 配置合并完成。 ---\"\n\ \n\ # 启动后台备份任务\n\ (while true; do sleep 1800; python3 /usr/local/bin/sync.py backup; done) &\n\ \n\ export NODE_ENV=production\n\ export OPENCLAW_TRUST_PROXY=true\n\ \n\ echo \"--- [System] 🚀 3. 正在启动 OpenClaw Gateway... ---\"\n\ set +e\n\ trap 'kill -TERM \$PID 2>/dev/null' TERM INT\n\ \n\ while true; do\n\ find /root/.openclaw -name \"*.lock\" -delete\n\ \n\ openclaw gateway run --port 7860 &\n\ PID=\$!\n\ wait \$PID\n\ \n\ echo \"--- [System] ⚠️ OpenClaw 主进程退出,正在清理环境准备重启... ---\"\n\ openclaw gateway stop || true\n\ pkill -f openclaw || true\n\ pkill -f node || true\n\ \n\ python3 /usr/local/bin/sync.py backup || true\n\ sleep 3\n\ done\n\ " > /usr/local/bin/start-openclaw && chmod +x /usr/local/bin/start-openclaw EXPOSE 7860 CMD ["/usr/local/bin/start-openclaw"]