Spaces:
Runtime error
Runtime error
| # 核心镜像选择 | |
| FROM node:22-slim | |
| # 1. 基础依赖补全 | |
| 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/* | |
| # 2. 安装 HF 数据交互工具 | |
| RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages | |
| # 3. 构建环境与 Git 协议优化 | |
| RUN update-ca-certificates && \ | |
| git config --global http.sslVerify false && \ | |
| git config --global url."https://github.com/".insteadOf ssh://git@github.com/ | |
| # 4. OpenClaw 核心安装 | |
| RUN npm install -g openclaw@latest --unsafe-perm | |
| # 5. 安装飞书频道插件(预装) | |
| RUN npm install -g @openclaw/feishu --unsafe-perm | |
| # 6. 环境变量预设 | |
| ENV PORT=7860 \ | |
| OPENCLAW_GATEWAY_MODE=local \ | |
| HOME=/root | |
| # 7. Python 同步引擎 (sync.py) - 增强版(json配置+压缩数据,直接覆盖配置) | |
| RUN echo 'import os, sys, tarfile\n\ | |
| from huggingface_hub import HfApi, hf_hub_download\n\ | |
| from datetime import datetime, timedelta\n\ | |
| import json\n\ | |
| import shutil\n\ | |
| \n\ | |
| api = HfApi()\n\ | |
| repo_id = os.getenv("HF_DATASET")\n\ | |
| token = os.getenv("HF_TOKEN")\n\ | |
| OPENCLAW_DIR = "/root/.openclaw"\n\ | |
| \n\ | |
| BACKUP_FILES = [\n\ | |
| "agents",\n\ | |
| "credentials",\n\ | |
| "openclaw.json",\n\ | |
| "workspace/IDENTITY.md",\n\ | |
| "workspace/USER.md",\n\ | |
| "workspace/SOUL.md",\n\ | |
| "workspace/AGENTS.md",\n\ | |
| "workspace/TOOLS.md",\n\ | |
| "workspace/MEMORY.md",\n\ | |
| "workspace/skills",\n\ | |
| ]\n\ | |
| \n\ | |
| def restore():\n\ | |
| try:\n\ | |
| files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)\n\ | |
| now = datetime.now()\n\ | |
| restored = False\n\ | |
| for i in range(5):\n\ | |
| day = (now - timedelta(days=i)).strftime("%Y-%m-%d")\n\ | |
| config_name = f"backup_{day}_config.json"\n\ | |
| data_name = f"backup_{day}_data.tar.gz"\n\ | |
| \n\ | |
| # 1. 恢复数据包(如果存在)\n\ | |
| if data_name in files:\n\ | |
| data_path = hf_hub_download(repo_id=repo_id, filename=data_name, repo_type="dataset", token=token)\n\ | |
| with tarfile.open(data_path, "r:gz") as tar:\n\ | |
| tar.extractall(path=OPENCLAW_DIR)\n\ | |
| print(f"Restored data from {data_name}")\n\ | |
| restored = True\n\ | |
| \n\ | |
| # 2. 恢复配置文件(如果存在),直接覆盖现有配置\n\ | |
| if config_name in files:\n\ | |
| config_path = hf_hub_download(repo_id=repo_id, filename=config_name, repo_type="dataset", token=token)\n\ | |
| target_config_path = os.path.join(OPENCLAW_DIR, "openclaw.json")\n\ | |
| # 确保目标目录存在\n\ | |
| os.makedirs(os.path.dirname(target_config_path), exist_ok=True)\n\ | |
| shutil.copy2(config_path, target_config_path)\n\ | |
| print(f"Restored config from {config_name} (overwritten)")\n\ | |
| restored = True\n\ | |
| \n\ | |
| if restored:\n\ | |
| print("Restore completed.")\n\ | |
| return True\n\ | |
| \n\ | |
| print("No backup found in last 5 days")\n\ | |
| return False\n\ | |
| except Exception as e:\n\ | |
| print(f"Restore Error: {e}")\n\ | |
| return False\n\ | |
| \n\ | |
| def backup():\n\ | |
| try:\n\ | |
| day = datetime.now().strftime("%Y-%m-%d")\n\ | |
| config_name = f"backup_{day}_config.json"\n\ | |
| data_name = f"backup_{day}_data.tar.gz"\n\ | |
| \n\ | |
| # 1. 备份配置文件(直接上传 JSON,不压缩)\n\ | |
| config_path = os.path.join(OPENCLAW_DIR, "openclaw.json")\n\ | |
| if os.path.exists(config_path):\n\ | |
| with open(config_path, "rb") as f:\n\ | |
| api.upload_file(\n\ | |
| path_or_fileobj=f,\n\ | |
| path_in_repo=config_name,\n\ | |
| repo_id=repo_id,\n\ | |
| repo_type="dataset",\n\ | |
| token=token\n\ | |
| )\n\ | |
| print(f"Config backup {config_name} uploaded.")\n\ | |
| \n\ | |
| # 2. 备份其他文件(打包为 tar.gz)\n\ | |
| with tarfile.open(data_name, "w:gz") as tar:\n\ | |
| for f in BACKUP_FILES:\n\ | |
| if f == "openclaw.json":\n\ | |
| continue # 已单独处理\n\ | |
| path = os.path.join(OPENCLAW_DIR, f)\n\ | |
| if os.path.exists(path):\n\ | |
| tar.add(path, arcname=f)\n\ | |
| print(f"Backed up: {f}")\n\ | |
| if os.path.exists(data_name):\n\ | |
| api.upload_file(\n\ | |
| path_or_fileobj=data_name,\n\ | |
| path_in_repo=data_name,\n\ | |
| repo_id=repo_id,\n\ | |
| repo_type="dataset",\n\ | |
| token=token\n\ | |
| )\n\ | |
| print(f"Data backup {data_name} uploaded.")\n\ | |
| \n\ | |
| print(f"Backup {day} completed.")\n\ | |
| except Exception as e:\n\ | |
| print(f"Backup Error: {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 | |
| #RK|# 8. 启动控制逻辑 | |
| RUN cat > /usr/local/bin/start-openclaw << 'SCRIPT' | |
| #!/bin/bash | |
| set -e | |
| mkdir -p /root/.openclaw/sessions | |
| # 阶段 1: 清理可能存在的重复插件(使用全局 npm 安装的版本) | |
| rm -rf /root/.openclaw/extensions/feishu | |
| # 阶段 2: 执行启动前恢复(会清理旧的 feishu 配置) | |
| python3 /usr/local/bin/sync.py restore | |
| # 处理地址逻辑 | |
| CLEAN_BASE=$(echo "$OPENAI_API_BASE" | sed "s|/chat/completions||g" | sed "s|/v1/|/v1|g" | sed "s|/v1$|/v1|g") | |
| # 阶段 3: 仅当配置文件不存在时才生成默认配置 | |
| if [ ! -f /root/.openclaw/openclaw.json ]; then | |
| echo "No existing openclaw.json found, generating default configuration..." | |
| cat > /root/.openclaw/openclaw.json <<EOF | |
| { | |
| "models": { | |
| "providers": { | |
| "$MODEL_PROVIDER": { | |
| "baseUrl": "$OPENAI_API_BASE", | |
| "apiKey": "$OPENAI_API_KEY", | |
| "api": "openai-completions", | |
| "models": [{ | |
| "id": "$MODEL", | |
| "name": "$MODEL_NAME", | |
| "contextWindow": $MODEL_CONTEXT | |
| }] | |
| } | |
| } | |
| }, | |
| "agents": { | |
| "defaults": { | |
| "model": { | |
| "primary": "$MODEL_PROVIDER/$MODEL" | |
| } | |
| } | |
| }, | |
| "gateway": { | |
| "mode": "local", | |
| "bind": "lan", | |
| "port": $PORT, | |
| "trustedProxies": ["0.0.0.0/0", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"], | |
| "auth": { | |
| "mode": "token", | |
| "token": "$OPENCLAW_GATEWAY_TOKEN" | |
| }, | |
| "remote": { | |
| "token": "$OPENCLAW_GATEWAY_TOKEN" | |
| }, | |
| "controlUi": { | |
| "allowInsecureAuth": true, | |
| "dangerouslyAllowHostHeaderOriginFallback": true, | |
| "dangerouslyDisableDeviceAuth": true | |
| } | |
| } | |
| } | |
| EOF | |
| else | |
| echo "Existing openclaw.json found, skipping generation." | |
| fi | |
| # 阶段 4: doctor 自动检测并配置 feishu 插件 | |
| openclaw doctor --fix | |
| # 阶段 5: 配置 feishu 频道(doctor 之后) | |
| if [ -n "$FEISHU_APP_ID" ] && [ -n "$FEISHU_APP_SECRET" ]; then | |
| openclaw config set channels.feishu.enabled true | |
| openclaw config set channels.feishu.accounts.main.appId "$FEISHU_APP_ID" | |
| openclaw config set channels.feishu.accounts.main.appSecret "$FEISHU_APP_SECRET" | |
| fi | |
| # 增量备份循环 (每 3小时) | |
| (while true; do sleep 10800; python3 /usr/local/bin/sync.py backup; done) & | |
| exec openclaw gateway run --port $PORT | |
| SCRIPT | |
| RUN chmod +x /usr/local/bin/start-openclaw | |
| EXPOSE 7860 | |
| CMD ["/usr/local/bin/start-openclaw"] | |