| |
| FROM node:22-slim |
|
|
| |
| 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 && \ |
| git config --global http.sslVerify false && \ |
| git config --global url."https://github.com/".insteadOf ssh://git@github.com/ |
|
|
| |
| RUN npm install -g openclaw@latest --unsafe-perm |
|
|
| |
|
|
|
|
| |
| ENV PORT=7860 \ |
| OPENCLAW_GATEWAY_MODE=local \ |
| HOME=/root |
|
|
| |
| 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\ |
| |
| 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\ |
| |
| 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\ |
| |
| 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 之后) |
| npx -y @larksuite/openclaw-lark install |
| |
| # 增量备份循环 (每 12小时) |
| (while true; do sleep 43200; 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"] |
| |