Spaces:
Paused
Paused
| # 核心镜像:使用 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"] |