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- Dockerfile +6 -262
- start-openclaw.sh +154 -0
- 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 插件依赖
|
| 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 |
-
|
| 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
|
| 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()
|