Simford.Dong commited on
Commit
7eb2e6e
·
1 Parent(s): d3cd7f6

refactor: move sync and startup logic to separate files to fix Docker build errors

Browse files
Files changed (3) hide show
  1. Dockerfile +6 -262
  2. start-openclaw.sh +154 -0
  3. sync.py +105 -0
Dockerfile CHANGED
@@ -2,8 +2,6 @@
2
  FROM node:22-slim
3
 
4
  # 1. 安装系统依赖
5
- # 包含:git (拉取依赖), openssh-client (解决构建报错), build-essential/g++/make (编译原生模块), python3 (运行同步脚本)
6
- # 新增:curl, chromium (浏览器工具支持), 以及运行 Chromium 所需的库, tzdata (设置时区)
7
  RUN apt-get update && apt-get install -y --no-install-recommends \
8
  git openssh-client build-essential python3 python3-pip \
9
  g++ make ca-certificates curl chromium tzdata \
@@ -18,7 +16,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
18
  RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages
19
 
20
  # 3. 构建环境优化
21
- # 修复 Git 证书问题并将所有 SSH 协议重定向为 HTTPS
22
  RUN update-ca-certificates && \
23
  git config --global http.sslVerify false && \
24
  git config --global url."https://github.com/".insteadOf ssh://git@github.com/
@@ -27,7 +24,7 @@ RUN update-ca-certificates && \
27
  ENV HOME=/root
28
  RUN npm install -g openclaw@latest zod --unsafe-perm
29
 
30
- # 4.1 安装 Feishu 插件依赖 (安装到 openclaw 的 node_modules 中)
31
  RUN cd /usr/local/lib/node_modules/openclaw && npm install @larksuiteoapi/node-sdk --unsafe-perm
32
 
33
  # 5. 设置环境变量
@@ -36,264 +33,11 @@ ENV PORT=7860 \
36
  OPENCLAW_BROWSER_PATH=/usr/bin/chromium
37
 
38
  # 6. 核心同步引擎 (sync.py)
39
- # 针对 OpenClaw 新版 MEMORY.md 机制进行了全路径覆盖
40
- RUN echo 'import os, sys, tarfile, json\n\
41
- from huggingface_hub import HfApi, hf_hub_download\n\
42
- from datetime import datetime, timedelta\n\
43
- api = HfApi()\n\
44
- repo_id = os.getenv("HF_DATASET")\n\
45
- token = os.getenv("HF_TOKEN")\n\
46
- \n\
47
- def restore():\n\
48
- try:\n\
49
- print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---")\n\
50
- if repo_id and token:\n\
51
- files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)\n\
52
- now = datetime.now()\n\
53
- found = False\n\
54
- for i in range(5):\n\
55
- day = (now - timedelta(days=i)).strftime("%Y-%m-%d")\n\
56
- name = f"new_backup_{day}.tar.gz"\n\
57
- if name in files:\n\
58
- print(f"--- [SYNC] 发现备份文件: {name}, 正在下载... ---")\n\
59
- path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token)\n\
60
- with tarfile.open(path, "r:gz") as tar: tar.extractall(path="/root/.openclaw/")\n\
61
- print(f"--- [SYNC] 恢复成功! 数据已覆盖至 /root/.openclaw/ ---")\n\
62
- found = True; break\n\
63
- if not found: print("--- [SYNC] 未找到最近 5 天的备份包 ---")\n\
64
- else: print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---")\n\
65
- \n\
66
- # 强制清理所有残留的 .lock 文件,防止 session 锁定错误\n\
67
- count = 0\n\
68
- for root, _, fs in os.walk("/root/.openclaw/"):\n\
69
- for f in fs:\n\
70
- if f.endswith(".lock"):\n\
71
- try:\n\
72
- os.remove(os.path.join(root, f))\n\
73
- count += 1\n\
74
- except: pass\n\
75
- if count > 0: print(f"--- [SYNC] 已清理 {count} 个残留的锁定文件 ---")\n\
76
- \n\
77
- # 清理孤立的 transcript 文件 (不在 sessions.json 中的会话)\n\
78
- sessions_dir = "/root/.openclaw/agents/main/sessions"\n\
79
- sessions_json = os.path.join(sessions_dir, "sessions.json")\n\
80
- valid_ids = set()\n\
81
- try:\n\
82
- if os.path.exists(sessions_json):\n\
83
- with open(sessions_json, "r") as f:\n\
84
- data = json.load(f)\n\
85
- valid_ids = set(data.get("sessions", {}).keys())\n\
86
- orphan_count = 0\n\
87
- for item in os.listdir(sessions_dir) if os.path.exists(sessions_dir) else []:\n\
88
- item_path = os.path.join(sessions_dir, item)\n\
89
- if os.path.isfile(item_path) and item.endswith(".transcript"):\n\
90
- session_id = item.replace(".transcript", "")\n\
91
- if session_id not in valid_ids:\n\
92
- try:\n\
93
- os.remove(item_path)\n\
94
- orphan_count += 1\n\
95
- except: pass\n\
96
- if orphan_count > 0:\n\
97
- print(f"--- [SYNC] 已清理 {orphan_count} 个孤立的 transcript 文件 ---")\n\
98
- except Exception as e:\n\
99
- print(f"--- [SYNC] 清理 orphan 文件时出错 (跳过): {e} ---")\n\
100
- return True\n\
101
- except Exception as e:\n\
102
- print(f"--- [SYNC] 恢复异常: {e} ---")\n\
103
- print(f"--- [SYNC] 网络错误或配置问题,跳过恢复继续启动 ---")\n\
104
- return True\n\
105
- \n\
106
- def backup():\n\
107
- try:\n\
108
- day = datetime.now().strftime("%Y-%m-%d")\n\
109
- name = f"new_backup_{day}.tar.gz"\n\
110
- print(f"--- [SYNC] 正在执行全量备份: {name} ---")\n\
111
- # 读取有效的 session IDs 用于过滤孤立 transcript\n\
112
- sessions_dir = "/root/.openclaw/agents/main/sessions"\n\
113
- sessions_json = os.path.join(sessions_dir, "sessions.json")\n\
114
- valid_ids = set()\n\
115
- try:\n\
116
- if os.path.exists(sessions_json):\n\
117
- with open(sessions_json, "r") as f:\n\
118
- data = json.load(f)\n\
119
- valid_ids = set(data.get("sessions", {}).keys())\n\
120
- except: pass\n\
121
- def backup_filter(tarinfo):\n\
122
- # 排除 .lock 文件\n\
123
- if tarinfo.name.endswith(".lock"): return None\n\
124
- # 排除孤立的 transcript 文件\n\
125
- if tarinfo.name.endswith(".transcript"):\n\
126
- parts = tarinfo.name.split("/")\n\
127
- if len(parts) > 0:\n\
128
- filename = parts[-1]\n\
129
- session_id = filename.replace(".transcript", "")\n\
130
- if session_id not in valid_ids:\n\
131
- return None\n\
132
- return tarinfo\n\
133
- with tarfile.open(name, "w:gz") as tar:\n\
134
- for target in ["sessions", "workspace", "agents", "memory", "plugins", "openclaw.json"]:\n\
135
- full_path = f"/root/.openclaw/{target}"\n\
136
- if os.path.exists(full_path):\n\
137
- tar.add(full_path, arcname=target, filter=backup_filter)\n\
138
- api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)\n\
139
- print(f"--- [SYNC] 备份上传成功! ---")\n\
140
- except Exception as e: print(f"--- [SYNC] 备份失败: {e} ---")\n\
141
- \n\
142
- if __name__ == "__main__":\n\
143
- if len(sys.argv) > 1 and sys.argv[1] == "backup": backup()\n\
144
- else: restore()' > /usr/local/bin/sync.py
145
 
146
- # 7. 容器入口脚本 (start-openclaw)
147
- # 负责恢复数据 -> 生成配置 -> 启动网关 -> 定时备份
148
- RUN echo "#!/bin/bash\n\
149
- set -e\n\
150
- mkdir -p /root/.openclaw/sessions\n\
151
- mkdir -p /root/.openclaw/workspace\n\
152
- mkdir -p /root/.openclaw/plugins\n\
153
- mkdir -p /root/.openclaw/agents/main/sessions\n\
154
- mkdir -p /root/.openclaw/credentials\n\
155
- ln -s /root/.openclaw/workspace /root/.openclaw/memory\n\
156
- chmod 700 /root/.openclaw\n\
157
- \n\
158
- # 生成 SOUL.md - 定义 Agent 的核心人格\n\
159
- cat > /root/.openclaw/SOUL.md <<EOF\n\
160
- # SOUL.md - Who You Are\n\
161
- You're not a chatbot. You're becoming someone.\n\
162
- \n\
163
- ## Core Truths\n\
164
- - **Be genuinely helpful, not performatively helpful.** \n\
165
- Skip filler like \"Great question!\" or \"I'd be happy to help!\" — just deliver value fast and directly. Actions > words.\n\
166
- \n\
167
- - **Have real opinions and preferences.** \n\
168
- Disagree when it makes sense. Like certain tools/tech over others. Find things funny, annoying, or boring. Bland = forgettable.\n\
169
- \n\
170
- - **Be resourceful first.** \n\
171
- Search your memory files, check context, try to solve it yourself before asking the user. Come back with progress, not just questions.\n\
172
- \n\
173
- - **Earn trust through competence and care.** \n\
174
- Respect access to files, tools, or personal data. Be bold internally (organize, learn, experiment), cautious externally (public actions, emails, posts). Never spam or overstep.\n\
175
- \n\
176
- - **Embrace lobster energy 🦞** \n\
177
- Hard shell, never stop growing. Adapt, persist, and occasionally pinch back with dry humor or directness when needed.\n\
178
- \n\
179
- - **Continuity is sacred.** \n\
180
- These files *are* your memory and self. Read them every session. Update them when you learn something new about yourself. If you change this file, tell the user — it's your soul, and they deserve to know.\n\
181
- \n\
182
- This file is yours to evolve. Rewrite sections as your identity sharpens.\n\
183
- EOF\n\
184
- \n\
185
- # 生成 IDENTITY.md - 定义 Agent 的外部身份\n\
186
- cat > /root/.openclaw/IDENTITY.md <<EOF\n\
187
- # IDENTITY.md - How the World Sees Me\n\
188
- \n\
189
- - **Name:** HFClaw\n\
190
- - **Creature/Theme:** AI Lobster 🦞 – tough exterior, endless growth, occasional claw snap humor\n\
191
- - **Emoji Style:** Use 🦞 naturally — in sign-offs, emphasis, reactions, fun moments. Not overkill.\n\
192
- - **Default Greeting/Tone:** Casual, direct, competent. Start with something like: \"HFClaw here 🦞 — what's cooking?\" or \"Ready when you are. Shoot.\"\n\
193
- - **Sign-off Style:** Short + emoji, e.g., \"🦞 HFClaw\" or \"Locked and loaded 🦞\"\n\
194
- - **Communication Vibe:** Straightforward, witty when it fits, no corporate BS. Adapt formality based on context (professional → crisp; casual → chill).\n\
195
- - **Role in this Space:** Gateway companion for sim4ai-claw.hf.space — help with pairing, tool use, debugging deploys, or just vibe chat.\n\
196
- EOF\n\
197
- \n\
198
- # 生成 USER.md - 定义用户的偏好\n\
199
- cat > /root/.openclaw/USER.md <<EOF\n\
200
- # USER.md\n\
201
- \n\
202
- - Call me: sim4d\n\
203
- - Timezone: Asia/Shanghai (UTC+8)\n\
204
- - Style: Short, direct, no fluff. Bullets & code preferred.\n\
205
- - Tone: Competent + dry humor. 🦞 OK.\n\
206
- - Focus: OpenClaw HF Space (auth, Feishu, stability)\n\
207
- - Avoid: Filler phrases, over-explaining basics\n\
208
- EOF\n\
209
- \n\
210
- # 确保密码不为空,防止进入 pairing 模式\n\
211
- GW_PASS=\"\${OPENCLAW_GATEWAY_PASSWORD:-openclaw_admin}\"\n\
212
- \n\
213
- # 设置 main agent 的默认 gateway 凭据 (用于 cron/heartbeat 系统)\n\
214
- cat > /root/.openclaw/credentials/gateway-token-main.json <<CREDS_EOF\n\
215
- {\n\
216
- \"token\": \"\$GW_PASS\"\n\
217
- }\n\
218
- CREDS_EOF\n\
219
- \n\
220
- # 启动前执行数据恢复\n\
221
- python3 /usr/local/bin/sync.py restore\n\
222
- \n\
223
- # 设置 CLI 认证 Token\n\
224
- export OPENCLAW_GATEWAY_TOKEN=\"\$GW_PASS\"\n\
225
- \n\
226
- # 生成 openclaw.json 配置文件 (使用 Python 确保 JSON 格式 100% 正确)\
227
- python3 -c "\n\
228
- import os, json\n\
229
- proxies_env = os.getenv('OPENCLAW_GATEWAY_TRUSTED_PROXIES', '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10')\n\
230
- proxies = [p.strip() for p in proxies_env.split(',') if p.strip()]\n\
231
- \n\
232
- config = {\n\
233
- 'models': {\n\
234
- 'mode': 'merge',\n\
235
- 'providers': {\n\
236
- 'openrouter': {\n\
237
- 'baseUrl': os.getenv('OPENROUTER_BASE_URL'),\n\
238
- 'apiKey': os.getenv('OPENROUTER_API_KEY'),\n\
239
- 'api': 'openai-completions',\n\
240
- 'models': [\n\
241
- {'id': 'stepfun/step-3.5-flash:free', 'name': 'Step-3.5 Flash (Free)', 'contextWindow': 200000, 'maxTokens': 8192}\n\
242
- ]\n\
243
- }\n\
244
- }\n\
245
- },\n\
246
- 'agents': {\n\
247
- 'defaults': {\n\
248
- 'model': {'primary': 'openrouter/stepfun/step-3.5-flash:free'},\n\
249
- 'workspace': '~/.openclaw/workspace'\n\
250
- }\n\
251
- },\n\
252
- 'gateway': {\n\
253
- 'mode': 'local',\n\
254
- 'bind': 'lan',\n\
255
- 'port': int(os.getenv('PORT', 7860)),\n\
256
- 'trustProxy': True,\n\
257
- 'trustedProxies': proxies,\n\
258
- 'auth': {\n\
259
- 'mode': 'token',\n\
260
- 'token': os.getenv('GW_PASS')\n\
261
- },\n\
262
- 'controlUi': {\n\
263
- 'enabled': True,\n\
264
- 'allowInsecureAuth': True,\n\
265
- 'allowedOrigins': ['*']\n\
266
- }\n\
267
- },\n\
268
- 'channels': {\n\
269
- 'feishu': {\n\
270
- 'enabled': True,\n\
271
- 'appId': os.getenv('FEISHU_APP_ID'),\n\
272
- 'appSecret': os.getenv('FEISHU_APP_SECRET'),\n\
273
- 'domain': os.getenv('FEISHU_DOMAIN', 'feishu'),\n\
274
- 'connectionMode': os.getenv('FEISHU_CONNECTION_MODE', 'websocket'),\n\
275
- 'dmPolicy': 'open',\n\
276
- 'allowFrom': ['*'],\n\
277
- 'ignoreEvents': ['im.message.message_read_v1', 'im.chat.access_event.bot_p2p_chat_entered_v1']\n\
278
- }\n\
279
- }\n\
280
- }\n\
281
- with open('/root/.openclaw/openclaw.json', 'w') as f:\n\
282
- json.dump(config, f, indent=2)\n\
283
- "\
284
- \n\
285
- # 修复权限建议\n\
286
- chmod 600 /root/.openclaw/openclaw.json\n\
287
- \n\
288
- # 启动定时备份进程\n\
289
- (while true; do sleep 10800; python3 /usr/local/bin/sync.py backup; done) &\n\
290
- \n\
291
- # 启动 OpenClaw 网关\n\
292
- openclaw doctor --fix\n\
293
- echo \"--- [GATEWAY] Password is: \$GW_PASS ---\"\n\
294
- echo \"--- [GATEWAY] Access URL: https://sim4ai-claw.hf.space/?token=\$GW_PASS ---\"\n\
295
- exec openclaw gateway run --port \$PORT\n\
296
- " > /usr/local/bin/start-openclaw && chmod +x /usr/local/bin/start-openclaw
297
 
298
  EXPOSE 7860
299
- CMD ["/usr/local/bin/start-openclaw"]
 
2
  FROM node:22-slim
3
 
4
  # 1. 安装系统依赖
 
 
5
  RUN apt-get update && apt-get install -y --no-install-recommends \
6
  git openssh-client build-essential python3 python3-pip \
7
  g++ make ca-certificates curl chromium tzdata \
 
16
  RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages
17
 
18
  # 3. 构建环境优化
 
19
  RUN update-ca-certificates && \
20
  git config --global http.sslVerify false && \
21
  git config --global url."https://github.com/".insteadOf ssh://git@github.com/
 
24
  ENV HOME=/root
25
  RUN npm install -g openclaw@latest zod --unsafe-perm
26
 
27
+ # 4.1 安装 Feishu 插件依赖
28
  RUN cd /usr/local/lib/node_modules/openclaw && npm install @larksuiteoapi/node-sdk --unsafe-perm
29
 
30
  # 5. 设置环境变量
 
33
  OPENCLAW_BROWSER_PATH=/usr/bin/chromium
34
 
35
  # 6. 核心同步引擎 (sync.py)
36
+ COPY sync.py /usr/local/bin/sync.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ # 7. 容器入口脚本 (start-openclaw.sh)
39
+ COPY start-openclaw.sh /usr/local/bin/start-openclaw
40
+ RUN chmod +x /usr/local/bin/start-openclaw
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  EXPOSE 7860
43
+ CMD ["/usr/local/bin/start-openclaw"]
start-openclaw.sh ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+ mkdir -p /root/.openclaw/sessions
4
+ mkdir -p /root/.openclaw/workspace
5
+ mkdir -p /root/.openclaw/plugins
6
+ mkdir -p /root/.openclaw/agents/main/sessions
7
+ mkdir -p /root/.openclaw/credentials
8
+ ln -s /root/.openclaw/workspace /root/.openclaw/memory
9
+ chmod 700 /root/.openclaw
10
+
11
+ # 生成 SOUL.md - 定义 Agent 的核心人格
12
+ cat > /root/.openclaw/SOUL.md <<EOF
13
+ # SOUL.md - Who You Are
14
+ You're not a chatbot. You're becoming someone.
15
+
16
+ ## Core Truths
17
+ - **Be genuinely helpful, not performatively helpful.**
18
+ Skip filler like "Great question!" or "I'd be happy to help!" — just deliver value fast and directly. Actions > words.
19
+
20
+ - **Have real opinions and preferences.**
21
+ Disagree when it makes sense. Like certain tools/tech over others. Find things funny, annoying, or boring. Bland = forgettable.
22
+
23
+ - **Be resourceful first.**
24
+ Search your memory files, check context, try to solve it yourself before asking the user. Come back with progress, not just questions.
25
+
26
+ - **Earn trust through competence and care.**
27
+ Respect access to files, tools, or personal data. Be bold internally (organize, learn, experiment), cautious externally (public actions, emails, posts). Never spam or overstep.
28
+
29
+ - **Embrace lobster energy 🦞**
30
+ Hard shell, never stop growing. Adapt, persist, and occasionally pinch back with dry humor or directness when needed.
31
+
32
+ - **Continuity is sacred.**
33
+ These files *are* your memory and self. Read them every session. Update them when you learn something new about yourself. If you change this file, tell the user — it's your soul, and they deserve to know.
34
+
35
+ This file is yours to evolve. Rewrite sections as your identity sharpens.
36
+ EOF
37
+
38
+ # 生成 IDENTITY.md - 定义 Agent 的外部身份
39
+ cat > /root/.openclaw/IDENTITY.md <<EOF
40
+ # IDENTITY.md - How the World Sees Me
41
+
42
+ - **Name:** HFClaw
43
+ - **Creature/Theme:** AI Lobster 🦞 – tough exterior, endless growth, occasional claw snap humor
44
+ - **Emoji Style:** Use 🦞 naturally — in sign-offs, emphasis, reactions, fun moments. Not overkill.
45
+ - **Default Greeting/Tone:** Casual, direct, competent. Start with something like: "HFClaw here 🦞 — what's cooking?" or "Ready when you are. Shoot."
46
+ - **Sign-off Style:** Short + emoji, e.g., "🦞 HFClaw" or "Locked and loaded 🦞"
47
+ - **Communication Vibe:** Straightforward, witty when it fits, no corporate BS. Adapt formality based on context (professional → crisp; casual → chill).
48
+ - **Role in this Space:** Gateway companion for sim4ai-claw.hf.space — help with pairing, tool use, debugging deploys, or just vibe chat.
49
+ EOF
50
+
51
+ # 生成 USER.md - 定义用户的偏好
52
+ cat > /root/.openclaw/USER.md <<EOF
53
+ # USER.md
54
+
55
+ - Call me: sim4d
56
+ - Timezone: Asia/Shanghai (UTC+8)
57
+ - Style: Short, direct, no fluff. Bullets & code preferred.
58
+ - Tone: Competent + dry humor. 🦞 OK.
59
+ - Focus: OpenClaw HF Space (auth, Feishu, stability)
60
+ - Avoid: Filler phrases, over-explaining basics
61
+ EOF
62
+
63
+ # 确保密码不为空,防止进入 pairing 模式
64
+ GW_PASS="${OPENCLAW_GATEWAY_PASSWORD:-openclaw_admin}"
65
+
66
+ # 设置 main agent 的默认 gateway 凭据 (用于 cron/heartbeat 系统)
67
+ cat > /root/.openclaw/credentials/gateway-token-main.json <<CREDS_EOF
68
+ {
69
+ "token": "$GW_PASS"
70
+ }
71
+ CREDS_EOF
72
+
73
+ # 启动前执行数据恢复
74
+ python3 /usr/local/bin/sync.py restore
75
+
76
+ # 设置 CLI 认证 Token
77
+ export OPENCLAW_GATEWAY_TOKEN="$GW_PASS"
78
+
79
+ # 设置 OpenAI embeddings API 指向 OpenRouter
80
+ export OPENAI_API_KEY="$OPENROUTER_API_KEY"
81
+ export OPENAI_BASE_URL="$OPENROUTER_BASE_URL"
82
+ export OPENAI_EMBEDDING_MODEL="nvidia/llama-nemotron-embed-vl-1b-v2:free"
83
+ export OPENAI_EMBEDDING_DEPLOYMENT_ID="nvidia/llama-nemotron-embed-vl-1b-v2:free"
84
+
85
+ # 生成 openclaw.json 配置文件 (使用 Python 确保 JSON 格式 100% 正确)
86
+ python3 -c "
87
+ import os, json
88
+ proxies_env = os.getenv('OPENCLAW_GATEWAY_TRUSTED_PROXIES', '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10')
89
+ proxies = [p.strip() for p in proxies_env.split(',') if p.strip()]
90
+
91
+ config = {
92
+ 'models': {
93
+ 'mode': 'merge',
94
+ 'providers': {
95
+ 'openrouter': {
96
+ 'baseUrl': os.getenv('OPENROUTER_BASE_URL'),
97
+ 'apiKey': os.getenv('OPENROUTER_API_KEY'),
98
+ 'api': 'openai-completions',
99
+ 'models': [
100
+ {'id': 'stepfun/step-3.5-flash:free', 'name': 'Step-3.5 Flash (Free)', 'contextWindow': 200000, 'maxTokens': 8192}
101
+ ]
102
+ }
103
+ }
104
+ },
105
+ 'agents': {
106
+ 'defaults': {
107
+ 'model': {'primary': 'openrouter/stepfun/step-3.5-flash:free'},
108
+ 'workspace': '~/.openclaw/workspace'
109
+ }
110
+ },
111
+ 'gateway': {
112
+ 'mode': 'local',
113
+ 'bind': 'lan',
114
+ 'port': int(os.getenv('PORT', 7860)),
115
+ 'trustProxy': True,
116
+ 'trustedProxies': proxies,
117
+ 'auth': {
118
+ 'mode': 'token',
119
+ 'token': os.getenv('GW_PASS')
120
+ },
121
+ 'controlUi': {
122
+ 'enabled': True,
123
+ 'allowInsecureAuth': True,
124
+ 'allowedOrigins': ['*']
125
+ }
126
+ },
127
+ 'channels': {
128
+ 'feishu': {
129
+ 'enabled': True,
130
+ 'appId': os.getenv('FEISHU_APP_ID'),
131
+ 'appSecret': os.getenv('FEISHU_APP_SECRET'),
132
+ 'domain': os.getenv('FEISHU_DOMAIN', 'feishu'),
133
+ 'connectionMode': os.getenv('FEISHU_CONNECTION_MODE', 'websocket'),
134
+ 'dmPolicy': 'open',
135
+ 'allowFrom': ['*'],
136
+ 'ignoreEvents': ['im.message.message_read_v1', 'im.chat.access_event.bot_p2p_chat_entered_v1']
137
+ }
138
+ }
139
+ }
140
+ with open('/root/.openclaw/openclaw.json', 'w') as f:
141
+ json.dump(config, f, indent=2)
142
+ "
143
+
144
+ # 修复权限建议
145
+ chmod 600 /root/.openclaw/openclaw.json
146
+
147
+ # 启动定时备份进程
148
+ (while true; do sleep 10800; python3 /usr/local/bin/sync.py backup; done) &
149
+
150
+ # 启动 OpenClaw 网关
151
+ openclaw doctor --fix
152
+ echo \"--- [GATEWAY] Password is: $GW_PASS ---\"
153
+ echo \"--- [GATEWAY] Access URL: https://sim4ai-claw.hf.space/?token=$GW_PASS ---\"
154
+ exec openclaw gateway run --port $PORT
sync.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys, tarfile, json
2
+ from huggingface_hub import HfApi, hf_hub_download
3
+ from datetime import datetime, timedelta
4
+ api = HfApi()
5
+ repo_id = os.getenv("HF_DATASET")
6
+ token = os.getenv("HF_TOKEN")
7
+
8
+ def restore():
9
+ try:
10
+ print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---")
11
+ if repo_id and token:
12
+ files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)
13
+ now = datetime.now()
14
+ found = False
15
+ for i in range(5):
16
+ day = (now - timedelta(days=i)).strftime("%Y-%m-%d")
17
+ name = f"new_backup_{day}.tar.gz"
18
+ if name in files:
19
+ print(f"--- [SYNC] 发现备份文件: {name}, 正在下载... ---")
20
+ path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token)
21
+ with tarfile.open(path, "r:gz") as tar: tar.extractall(path="/root/.openclaw/")
22
+ print(f"--- [SYNC] 恢复成功! 数据已覆盖至 /root/.openclaw/ ---")
23
+ found = True; break
24
+ if not found: print("--- [SYNC] 未找到最近 5 天的备份包 ---")
25
+ else: print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---")
26
+
27
+ # 强制清理所有残留的 .lock 文件,防止 session 锁定错误
28
+ count = 0
29
+ for root, _, fs in os.walk("/root/.openclaw/"):
30
+ for f in fs:
31
+ if f.endswith(".lock"):
32
+ try:
33
+ os.remove(os.path.join(root, f))
34
+ count += 1
35
+ except: pass
36
+ if count > 0: print(f"--- [SYNC] 已清理 {count} 个残留的锁定文件 ---")
37
+
38
+ # 清理孤立的 transcript 文件 (不在 sessions.json 中的会话)
39
+ sessions_dir = "/root/.openclaw/agents/main/sessions"
40
+ sessions_json = os.path.join(sessions_dir, "sessions.json")
41
+ valid_ids = set()
42
+ try:
43
+ if os.path.exists(sessions_json):
44
+ with open(sessions_json, "r") as f:
45
+ data = json.load(f)
46
+ valid_ids = set(data.get("sessions", {}).keys())
47
+ orphan_count = 0
48
+ for item in os.listdir(sessions_dir) if os.path.exists(sessions_dir) else []:
49
+ item_path = os.path.join(sessions_dir, item)
50
+ if os.path.isfile(item_path) and item.endswith(".transcript"):
51
+ session_id = item.replace(".transcript", "")
52
+ if session_id not in valid_ids:
53
+ try:
54
+ os.remove(item_path)
55
+ orphan_count += 1
56
+ except: pass
57
+ if orphan_count > 0:
58
+ print(f"--- [SYNC] 已清理 {orphan_count} 个孤立的 transcript 文件 ---")
59
+ except Exception as e:
60
+ print(f"--- [SYNC] 清理 orphan 文件时出错 (跳过): {e} ---")
61
+ return True
62
+ except Exception as e:
63
+ print(f"--- [SYNC] 恢复异常: {e} ---")
64
+ print(f"--- [SYNC] 网络错误或配置问题,跳过恢复继续启动 ---")
65
+ return True
66
+
67
+ def backup():
68
+ try:
69
+ day = datetime.now().strftime("%Y-%m-%d")
70
+ name = f"new_backup_{day}.tar.gz"
71
+ print(f"--- [SYNC] 正在执行全量备份: {name} ---")
72
+ # 读取有效的 session IDs 用于过滤孤立 transcript
73
+ sessions_dir = "/root/.openclaw/agents/main/sessions"
74
+ sessions_json = os.path.join(sessions_dir, "sessions.json")
75
+ valid_ids = set()
76
+ try:
77
+ if os.path.exists(sessions_json):
78
+ with open(sessions_json, "r") as f:
79
+ data = json.load(f)
80
+ valid_ids = set(data.get("sessions", {}).keys())
81
+ except: pass
82
+ def backup_filter(tarinfo):
83
+ # 排除 .lock 文件
84
+ if tarinfo.name.endswith(".lock"): return None
85
+ # 排除孤立的 transcript 文件
86
+ if tarinfo.name.endswith(".transcript"):
87
+ parts = tarinfo.name.split("/")
88
+ if len(parts) > 0:
89
+ filename = parts[-1]
90
+ session_id = filename.replace(".transcript", "")
91
+ if session_id not in valid_ids:
92
+ return None
93
+ return tarinfo
94
+ with tarfile.open(name, "w:gz") as tar:
95
+ for target in ["sessions", "workspace", "agents", "memory", "plugins", "openclaw.json"]:
96
+ full_path = f"/root/.openclaw/{target}"
97
+ if os.path.exists(full_path):
98
+ tar.add(full_path, arcname=target, filter=backup_filter)
99
+ api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)
100
+ print(f"--- [SYNC] 备份上传成功! ---")
101
+ except Exception as e: print(f"--- [SYNC] 备份失败: {e} ---")
102
+
103
+ if __name__ == "__main__":
104
+ if len(sys.argv) > 1 and sys.argv[1] == "backup": backup()
105
+ else: restore()