zngw commited on
Commit
8bf79eb
·
1 Parent(s): 35d8776

拆分配置

Browse files
Dockerfile CHANGED
@@ -1,187 +1,58 @@
1
- # 核心镜像:Node 22 slim 保证了环境的现代性与轻量化
2
  FROM node:22-slim
3
 
 
 
 
4
  # 1. 安装系统依赖
5
- # 包含:git (拉取依赖), openssh-client (解决构建报错), build-essential/g++/make (编译原生模块), python3 (运行同步脚本)
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
- git openssh-client build-essential python3 python3-pip \
8
- g++ make ca-certificates jq \
9
- && rm -rf /var/lib/apt/lists/*
10
-
11
- # 2. 安装 Hugging Face 命令行工具
12
- RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages
13
-
14
- # 3. 构建环境优化
15
- # 修复 Git 证书问题并将所有 SSH 协议重定向为 HTTPS
16
- RUN update-ca-certificates && \
17
- git config --global http.sslVerify false && \
18
- git config --global url."https://github.com/".insteadOf ssh://git@github.com/
 
 
 
 
 
 
 
19
 
20
  # 4. 全局安装 OpenClaw
21
  RUN npm install -g openclaw@latest --unsafe-perm
22
 
23
- # 5. 设置环境变量
 
 
 
 
 
 
 
 
 
24
  ENV PORT=7860 \
25
  OPENCLAW_GATEWAY_MODE=local \
26
- HOME=/root
 
 
 
 
 
27
 
28
- # 6. 核心同步引擎 (sync.py)
29
- # 针对 OpenClaw 新版 MEMORY.md 机制进行了全路径覆盖
30
- RUN echo 'import os, sys, tarfile\n\
31
- from huggingface_hub import HfApi, hf_hub_download\n\
32
- from datetime import datetime, timedelta\n\
33
- api = HfApi()\n\
34
- repo_id = os.getenv("HF_DATASET")\n\
35
- token = os.getenv("HF_TOKEN")\n\
36
- \n\
37
- def restore():\n\
38
- try:\n\
39
- print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---")\n\
40
- if not repo_id or not token: \n\
41
- print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---")\n\
42
- return False\n\
43
- files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)\n\
44
- now = datetime.now()\n\
45
- for i in range(5):\n\
46
- day = (now - timedelta(days=i)).strftime("%Y-%m-%d")\n\
47
- name = f"backup_{day}.tar.gz"\n\
48
- if name in files:\n\
49
- print(f"--- [SYNC] 发现备份文件: {name}, 正在下载... ---")\n\
50
- path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token)\n\
51
- with tarfile.open(path, "r:gz") as tar: tar.extractall(path="/root/.openclaw/")\n\
52
- print(f"--- [SYNC] 恢复成功! 数据已覆盖至 /root/.openclaw/ ---")\n\
53
- return True\n\
54
- print("--- [SYNC] 未找到最近 5 天的备份包 ---")\n\
55
- except Exception as e: print(f"--- [SYNC] 恢复异常: {e} ---")\n\
56
- \n\
57
- def backup():\n\
58
- try:\n\
59
- day = datetime.now().strftime("%Y-%m-%d")\n\
60
- name = f"backup_{day}.tar.gz"\n\
61
- print(f"--- [SYNC] 正在执行全量备份: {name} ---")\n\
62
- with tarfile.open(name, "w:gz") as tar:\n\
63
- # 路径说明:sessions(网关历史), workspace(记忆文件), agents(配置), memory(旧版目录),devices(设备)\n\
64
- for target in ["sessions", "workspace", "agents", "memory", "devices", "openclaw.json"]:\n\
65
- full_path = f"/root/.openclaw/{target}"\n\
66
- if os.path.exists(full_path):\n\
67
- tar.add(full_path, arcname=target)\n\
68
- api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)\n\
69
- print(f"--- [SYNC] 备份上传成功! ---")\n\
70
- except Exception as e: print(f"--- [SYNC] 备份失败: {e} ---")\n\
71
- \n\
72
- if __name__ == "__main__":\n\
73
- if len(sys.argv) > 1 and sys.argv[1] == "backup": backup()\n\
74
- else: restore()' > /usr/local/bin/sync.py
75
-
76
- # 6.1 自动批准
77
- RUN echo 'import subprocess, json, sys\n\
78
- def run_command(cmd):\n\
79
- """执行 shell 命令并返回输出"""\n\
80
- try:\n\
81
- result = subprocess.run(\n\
82
- cmd, \n\
83
- shell=True, \n\
84
- capture_output=True, \n\
85
- text=True, \n\
86
- check=True\n\
87
- )\n\
88
- return result.stdout.strip()\n\
89
- except subprocess.CalledProcessError as e:\n\
90
- # 静默失败或打印错误,视需求而定\n\
91
- return None\n\
92
- \n\
93
- def main():\n\
94
- print("--- [Pending]: 检查设备情况...")\n\
95
- # 获取 JSON 数据\n\
96
- json_output = run_command("openclaw devices list --json")\n\
97
- #print(f"--- {json_output}")\n\
98
- \n\
99
- if not json_output:\n\
100
- print("如果没有输出或命令失败,直接退出(可能是还没初始化或无网络)")\n\
101
- sys.exit(0)\n\
102
- \n\
103
- try:\n\
104
- data = json.loads(json_output)\n\
105
- except json.JSONDecodeError:\n\
106
- sys.exit(0)\n\
107
- \n\
108
- # 解析 pending 列表\n\
109
- # 结构: [true, ["browser"], "warn", {obj1}, {obj2}, ...]\n\
110
- pending_list = data.get("pending", [])\n\
111
- \n\
112
- if not isinstance(pending_list, list):\n\
113
- sys.exit(0)\n\
114
- \n\
115
- request_ids = []\n\
116
- \n\
117
- # 检查每个元素是否为包含 requestId 的字典\n\
118
- for item in pending_list:\n\
119
- if isinstance(item, dict) and "requestId" in item:\n\
120
- request_ids.append(item["requestId"])\n\
121
- # 从索引 3 开始遍历\n\
122
- for item in pending_list[3:]:\n\
123
- if isinstance(item, dict) and "requestId" in item:\n\
124
- request_ids.append(item["requestId"])\n\
125
- \n\
126
- if not request_ids:\n\
127
- sys.exit(0)\n\
128
- \n\
129
- # 循环批准\n\
130
- for req_id in request_ids:\n\
131
- cmd = f"openclaw devices approve {req_id}"\n\
132
- subprocess.run(cmd, shell=True, capture_output=True, text=True)\n\
133
- \n\
134
- if __name__ == "__main__":\n\
135
- main()' > /usr/local/bin/pending.py
136
 
137
- # 7. 容器入口脚本 (start-openclaw)
138
- # 负责恢复数据 -> 生成配置 -> 启动网关 -> 定时备份
139
- RUN echo "#!/bin/bash\n\
140
- set -e\n\
141
- mkdir -p /root/.openclaw/sessions\n\
142
- mkdir -p /root/.openclaw/workspace\n\
143
- mkdir -p /root/.openclaw/devices\n\
144
- \n\
145
- # 启动前执行数据恢复\n\
146
- python3 /usr/local/bin/sync.py restore\n\
147
- \n\
148
- # 清理 API Base 地址\n\
149
- CLEAN_BASE=\$(echo \"\$OPENAI_API_BASE\" | sed \"s|/chat/completions||g\" | sed \"s|/v1/|/v1|g\" | sed \"s|/v1\$|/v1|g\")\n\
150
- \n\
151
- # 生成 openclaw.json 配置文件\n\
152
- cat > /root/.openclaw/openclaw.json <<EOF\n\
153
- {\n\
154
- \"models\": {\n\
155
- \"providers\": {\n\
156
- \"siliconflow\": {\n\
157
- \"baseUrl\": \"\$CLEAN_BASE\",\n\
158
- \"apiKey\": \"\$OPENAI_API_KEY\",\n\
159
- \"api\": \"openai-completions\",\n\
160
- \"models\": [{ \"id\": \"\$MODEL\", \"name\": \"DeepSeek\", \"contextWindow\": 128000 }]\n\
161
- }\n\
162
- }\n\
163
- },\n\
164
- \"agents\": { \"defaults\": { \"model\": { \"primary\": \"siliconflow/\$MODEL\" } } },\n\
165
- \"gateway\": {\n\
166
- \"mode\": \"local\", \"bind\": \"lan\", \"port\": \$PORT,\n\
167
- \"trustedProxies\": [\"0.0.0.0/0\", \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\"],\n\
168
- \"auth\": { \"mode\": \"token\", \"token\": \"\$OPENCLAW_GATEWAY_PASSWORD\" },\n\
169
- \"controlUi\": { \n\
170
- \"allowedOrigins\":[ \n\
171
- \"https://zngw-openclaw.hf.space\" \n\
172
- ] \n\
173
- }\n\
174
- }\n\
175
- }\n\
176
- EOF\n\
177
- \n\
178
- # 启动定时备份进程 (每 1 小时执行一次,增强安全性)\n\
179
- (while true; do sleep 3600; python3 /usr/local/bin/pending.py; python3 /usr/local/bin/sync.py backup; done) &\n\
180
- \n\
181
- # 启动 OpenClaw 网关\n\
182
- openclaw doctor --fix\n\
183
- exec openclaw gateway run --port \$PORT\n\
184
- " > /usr/local/bin/start-openclaw && chmod +x /usr/local/bin/start-openclaw
185
 
186
- EXPOSE 7860
187
- CMD ["/usr/local/bin/start-openclaw"]
 
 
1
  FROM node:22-slim
2
 
3
+ ARG APP_HOME=/root/.openclaw
4
+ ARG SCRIPTS_DIR=/usr/local/bin
5
+
6
  # 1. 安装系统依赖
 
7
  RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ git \
9
+ openssh-client \
10
+ build-essential \
11
+ g++ \
12
+ make \
13
+ python3 \
14
+ python3-pip \
15
+ ca-certificates \
16
+ jq \
17
+ curl \
18
+ && rm -rf /var/lib/apt/lists/* \
19
+ && update-ca-certificates
20
+
21
+ # 2. 配置 Git
22
+ RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/ && \
23
+ git config --global --add safe.directory "*"
24
+
25
+ # 3. 安装 Python 依赖
26
+ RUN pip3 install --no-cache-dir huggingface_hub
27
 
28
  # 4. 全局安装 OpenClaw
29
  RUN npm install -g openclaw@latest --unsafe-perm
30
 
31
+ # 5. 复制所有脚本和模板
32
+ COPY scripts/sync.py ${SCRIPTS_DIR}/sync.py
33
+ COPY scripts/pending.py ${SCRIPTS_DIR}/pending.py
34
+ COPY scripts/start-openclaw.sh ${SCRIPTS_DIR}/start-openclaw.sh
35
+ COPY config/openclaw.json ${APP_HOME}/openclaw.json
36
+
37
+ # 赋予执行权限
38
+ RUN chmod +x ${SCRIPTS_DIR}/sync.py ${SCRIPTS_DIR}/pending.py ${SCRIPTS_DIR}/start-openclaw.sh
39
+
40
+ # 6. 设置环境变量
41
  ENV PORT=7860 \
42
  OPENCLAW_GATEWAY_MODE=local \
43
+ HOME=/root \
44
+ PYTHONUNBUFFERED=1 \
45
+ PYTHONDONTWRITEBYTECODE=1
46
+
47
+ # 7. 创建基础目录
48
+ RUN mkdir -p ${APP_HOME}/{sessions,workspace,agents,memory,devices}
49
 
50
+ # 8. 暴露端口
51
+ EXPOSE ${PORT}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ # 9. 健康检查
54
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
55
+ CMD curl -f http://localhost:${PORT}/health || exit 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # 10. 入口点
58
+ ENTRYPOINT ["/usr/local/bin/start-openclaw.sh"]
config/openclaw.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "models": {
3
+ "providers": {
4
+ "siliconflow": {
5
+ "baseUrl": "https://integrate.api.nvidia.com/v1",
6
+ "apiKey": "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",
7
+ "api": "openai-completions",
8
+ "models": [{ "id": "z-ai/glm5", "name": "GLM-5", "contextWindow": 128000 }]
9
+ }
10
+ }
11
+ },
12
+ "agents": { "defaults": { "model": { "primary": "siliconflow/z-ai/glm5" } } },
13
+ "gateway": {
14
+ "mode": "local",
15
+ "bind": "lan",
16
+ "port": 7860,
17
+ "trustedProxies": ["0.0.0.0/0", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
18
+ "auth": { "mode": "token", "token": "Zw123455." },
19
+ "controlUi": {
20
+ "allowedOrigins": [
21
+ "https://zngw-openclaw.hf.space"
22
+ ]
23
+ }
24
+ }
25
+ }
scripts/pending.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import subprocess
3
+ import json
4
+ import sys
5
+ import time
6
+
7
+ def run_command(cmd):
8
+ try:
9
+ result = subprocess.run(
10
+ cmd,
11
+ shell=True,
12
+ capture_output=True,
13
+ text=True,
14
+ check=True,
15
+ timeout=10 # 增加超时防止卡死
16
+ )
17
+ return result.stdout.strip()
18
+ except subprocess.CalledProcessError as e:
19
+ # 如果是非零退出码,可能是没有 pending 请求,静默处理
20
+ return None
21
+ except subprocess.TimeoutExpired:
22
+ print("--- [Pending] 命令执行超时 ---")
23
+ return None
24
+
25
+ def main():
26
+ print("--- [Pending]: 检查设备待批准列表...")
27
+
28
+ json_output = run_command("openclaw devices list --json")
29
+
30
+ if not json_output:
31
+ # 可能是还没初始化,或者命令本身不需要输出
32
+ sys.exit(0)
33
+
34
+ try:
35
+ data = json.loads(json_output)
36
+ except json.JSONDecodeError:
37
+ # 输出不是有效的 JSON,忽略
38
+ sys.exit(0)
39
+
40
+ pending_list = data.get("pending", [])
41
+
42
+ if not isinstance(pending_list, list):
43
+ sys.exit(0)
44
+
45
+ request_ids = []
46
+
47
+ # 逻辑优化:统一遍历逻辑,不再硬编码索引切片,除非数据结构非常特殊
48
+ # 根据原逻辑,似乎是从索引 3 开始或者是字典类型
49
+ for item in pending_list:
50
+ if isinstance(item, dict) and "requestId" in item:
51
+ request_ids.append(item["requestId"])
52
+
53
+ if not request_ids:
54
+ sys.exit(0)
55
+
56
+ print(f"--- [Pending] 发现 {len(request_ids)} 个待批准请求 ---")
57
+
58
+ for req_id in request_ids:
59
+ cmd = f"openclaw devices approve {req_id}"
60
+ print(f"--- [Pending] 正在批准: {req_id} ---")
61
+ subprocess.run(cmd, shell=True, capture_output=True, text=True)
62
+
63
+ print("--- [Pending] 所有请求处理完毕 ---")
64
+
65
+ if __name__ == "__main__":
66
+ main()
scripts/start-openclaw.sh ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # 捕获信号以优雅关闭
5
+ trap "echo '收到终止信号,正在关闭后台任务...'; kill -TERM '$CHILD_PID' 2>/dev/null; exit 0" SIGTERM SIGINT
6
+
7
+ echo "--- [Start] 初始化 OpenClaw 环境 ---"
8
+
9
+ # 确保目录存在
10
+ mkdir -p /root/.openclaw/{sessions,workspace,devices}
11
+
12
+ # 1. 数据恢复
13
+ echo "--- [Start] 执行数据恢复 ---"
14
+ python3 /usr/local/bin/sync.py restore
15
+
16
+ # 2. 验证配置json
17
+ echo "--- [Start] 验证配置openclaw.json ---"
18
+ if command -v jq &> /dev/null; then
19
+ if ! jq empty "/root/.openclaw/openclaw.json" 2>/dev/null; then
20
+ echo "警告: 配置的 openclaw.json 可能格式有误,请检查配置。"
21
+ cat "/root/.openclaw/openclaw.json"
22
+ else
23
+ echo "配置文件格式有效。"
24
+ fi
25
+ fi
26
+
27
+ # 3. 更新
28
+ echo "--- [Start] 检查更新 ---"
29
+ openclaw update
30
+
31
+ # 4. 启动后台任务
32
+ echo "--- [Start] 启动后台守护进程 ---"
33
+
34
+ (
35
+ sleep 60
36
+ # 任务 A: 自动批准 (每 10 分钟 = 600 秒)
37
+ while true; do
38
+ echo "[Cron] 执行设备自动批准..."
39
+ python3 /usr/local/bin/pending.py
40
+ sleep 600
41
+ done
42
+ ) &
43
+ PID_APPROVE=$!
44
+
45
+ (
46
+ # 任务 B: 数据备份 (每 1 小时 = 3600 秒)
47
+ # 先等待一小会儿再开始第一次备份,避免启动瞬间占用资源
48
+ sleep 60
49
+ while true; do
50
+ echo "[Cron] 执行数据备份..."
51
+ python3 /usr/local/bin/sync.py backup
52
+ sleep 3600
53
+ done
54
+ ) &
55
+ PID_BACKUP=$!
56
+
57
+ # 记录子进程 ID 用于信号处理
58
+ CHILD_PID="$PID_APPROVE $PID_BACKUP"
59
+
60
+ # 5. 运行诊断修复
61
+ echo "--- [Start] 运行 Doctor 修复 ---"
62
+ openclaw doctor --fix || echo "--- [Warning] Doctor 修复完成但有警告 ---"
63
+
64
+ # 6. 启动主服务
65
+ echo "--- [Start] 启动 OpenClaw Gateway (端口: ${PORT}) ---"
66
+ # exec 替换当前 shell,使 openclaw 成为 PID 1,便于接收信号
67
+ exec openclaw gateway run --port ${PORT}
scripts/sync.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import tarfile
5
+ from huggingface_hub import HfApi, hf_hub_download
6
+ from datetime import datetime, timedelta
7
+
8
+ def restore():
9
+ repo_id = os.getenv("HF_DATASET")
10
+ token = os.getenv("HF_TOKEN")
11
+
12
+ if not repo_id or not token:
13
+ print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---")
14
+ return True # 返回 True 表示流程继续,只是没恢复数据
15
+
16
+ try:
17
+ print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---")
18
+ api = HfApi()
19
+ files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)
20
+
21
+ now = datetime.now()
22
+ # 查找最近 5 天的备份
23
+ for i in range(5):
24
+ day = (now - timedelta(days=i)).strftime("%Y-%m-%d")
25
+ name = f"backup_{day}.tar.gz"
26
+
27
+ if name in files:
28
+ print(f"--- [SYNC] 发现备份文件: {name}, 正在下载... ---")
29
+ path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token)
30
+
31
+ extract_path = "/root/.openclaw/"
32
+ with tarfile.open(path, "r:gz") as tar:
33
+ # 安全解压,防止路径遍历攻击 (虽然来源可信,但作为好习惯)
34
+ tar.extractall(path=extract_path)
35
+
36
+ print(f"--- [SYNC] 恢复成功! 数据已覆盖至 {extract_path} ---")
37
+ return True
38
+
39
+ print("--- [SYNC] 未找到最近 5 天的备份包,将使用空状态启动 ---")
40
+ return True
41
+ except Exception as e:
42
+ print(f"--- [SYNC] 恢复异常: {e} ---")
43
+ # 如果恢复失败,是否应该阻止启动?这里选择打印错误但继续,以免配置错误导致容器无法启动
44
+ return True
45
+
46
+ def backup():
47
+ repo_id = os.getenv("HF_DATASET")
48
+ token = os.getenv("HF_TOKEN")
49
+
50
+ if not repo_id or not token:
51
+ return
52
+
53
+ try:
54
+ day = datetime.now().strftime("%Y-%m-%d")
55
+ name = f"backup_{day}.tar.gz"
56
+ print(f"--- [SYNC] 正在执行全量备份: {name} ---")
57
+
58
+ base_path = "/root/.openclaw"
59
+ targets = ["sessions", "workspace", "agents", "memory", "devices", "openclaw.json"]
60
+
61
+ with tarfile.open(name, "w:gz") as tar:
62
+ for target in targets:
63
+ full_path = os.path.join(base_path, target)
64
+ if os.path.exists(full_path):
65
+ tar.add(full_path, arcname=target)
66
+
67
+ api = HfApi()
68
+ api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)
69
+ print(f"--- [SYNC] 备份上传成功! ---")
70
+
71
+ # 清理本地临时备份文件以节省空间
72
+ if os.path.exists(name):
73
+ os.remove(name)
74
+
75
+ except Exception as e:
76
+ print(f"--- [SYNC] 备份失败: {e} ---")
77
+
78
+ if __name__ == "__main__":
79
+ if len(sys.argv) > 1 and sys.argv[1] == "backup":
80
+ backup()
81
+ else:
82
+ restore()