💐 Sync 2026-07-09 02:25:04

This commit is contained in:
github-actions[bot] committed 2026-07-09 02:25:04 +08:00
1 parent f077ddaf47
commit 2af7926aeb
37 files changed
+597 -329

No files matched your search

+2
View File
@@ -13,6 +13,8 @@
- 兼容没有 `su` / `runuser` 的 OpenWrt 固件,微信安装、升级、登录和下线改用 `start-stop-daemon` 兜底以 `openclaw` 用户执行。
- 微信安装网络探测优先使用 `curl`,避免低内存/精简 musl 固件上 Node `fetch` 触发 undici Wasm OOM。
- 微信官方 CLI 安装触发 undici Wasm OOM 时,自动改用 npm 直装到 OpenClaw `npm/projects` 并继续注册插件配置。
- 微信插件安装/升级不再预先停止 Gateway,避免反复安装触发 procd crash-loop。
- LuCI 状态页和 `status_service` 增强 procd 状态识别,能区分真实启动中、stale pidfile 和 crash-loop 抑制,不再误显示“正在启动”。
- 微信登录前补充 Node、python3、插件目录、账号状态目录和配置写权限检查,并清理残留登录进程与旧二维码状态。
- 登录失败时在 LuCI 页面展示真实日志详情,不再只显示“登录失败”。
- 二维码链接提取更稳,页面明确提示“点击链接后用微信扫码”。
+16 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openclaw
PKG_VERSION:=$(strip $(shell cat $(CURDIR)/VERSION 2>/dev/null || echo "1.0.0"))
PKG_RELEASE:=20
PKG_RELEASE:=21
PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com>
PKG_LICENSE:=GPL-3.0
@@ -85,6 +85,21 @@ define Package/$(PKG_NAME)/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
( . /etc/uci-defaults/99-openclaw ) && rm -f /etc/uci-defaults/99-openclaw
OPENCLAW_INSTALL_BASE="$$(uci -q get openclaw.main.install_path 2>/dev/null || echo /opt)"
if [ -r /usr/libexec/openclaw-paths.sh ]; then
. /usr/libexec/openclaw-paths.sh
oc_load_paths "$${OPENCLAW_INSTALL_BASE}" 2>/dev/null || true
else
OPENCLAW_INSTALL_BASE="$${OPENCLAW_INSTALL_BASE%/}"
OC_DATA="$${OPENCLAW_INSTALL_BASE}/openclaw/data"
fi
if [ -n "$${OC_DATA:-}" ] && [ -d "$${OC_DATA}/.openclaw" ] && [ -x /usr/libexec/openclaw-permissions.sh ]; then
/usr/libexec/openclaw-permissions.sh fix-state "$${OC_DATA}/.openclaw" >/dev/null 2>&1 || true
fi
if [ "$$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)" = "1" ] && [ -x /etc/init.d/openclaw ]; then
/etc/init.d/openclaw enable >/dev/null 2>&1 || true
/etc/init.d/openclaw start >/dev/null 2>&1 || true
fi
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null
exit 0
}
@@ -191,6 +191,7 @@ fs.writeFileSync(configPath, JSON.stringify(d, null, 2) + '\n');
"REG_RC=$?; " ..
"if [ $REG_RC -eq 0 ]; then " ..
"chown openclaw:openclaw " .. shellquote(config_file) .. " 2>/dev/null; " ..
"[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh fix-state " .. shellquote(oc_data .. "/.openclaw") .. " >/dev/null 2>&1; " ..
"echo 'Registered openclaw-weixin npm plugin in OpenClaw config.' >> " .. shellquote(log_file) .. "; " ..
"else RC=$REG_RC; echo $RC > " .. shellquote(exit_file) .. "; echo 'Failed to register openclaw-weixin npm plugin in OpenClaw config.' >> " .. shellquote(log_file) .. "; fi; " ..
"else RC=127; echo $RC > " .. shellquote(exit_file) .. "; echo 'Node.js not found, cannot register openclaw-weixin plugin.' >> " .. shellquote(log_file) .. "; fi; " ..
@@ -238,7 +239,13 @@ const timer = setTimeout(() => controller.abort(new Error('timeout')), 10000);
end
local function openclaw_user_runner_cmd()
return "_oc_as_openclaw() { " ..
return "_oc_raise_openclaw_limits() { " ..
"ulimit -v unlimited 2>/dev/null || true; " ..
"ulimit -m unlimited 2>/dev/null || true; " ..
"ulimit -d unlimited 2>/dev/null || true; " ..
"}; " ..
"_oc_as_openclaw() { " ..
"_oc_raise_openclaw_limits; " ..
"if command -v su >/dev/null 2>&1; then su -s /bin/sh openclaw -c \"$1\"; " ..
"elif command -v runuser >/dev/null 2>&1; then runuser -u openclaw -- sh -c \"$1\"; " ..
"elif command -v start-stop-daemon >/dev/null 2>&1; then _oc_pid=/tmp/openclaw-user-$$.pid; _oc_cwd=$(pwd); rm -f \"$_oc_pid\"; start-stop-daemon -S -m -p \"$_oc_pid\" -c openclaw:openclaw -d \"$_oc_cwd\" -x /bin/sh -- -c \"$1\"; _oc_rc=$?; rm -f \"$_oc_pid\"; return $_oc_rc; " ..
@@ -267,6 +274,25 @@ local function wechat_npm_fallback_install_cmd(install_path, log_file, exit_file
"fi; "
end
local function wechat_npm_direct_install_cmd(install_path, log_file, exit_file)
local package_json = "{\n \"private\": true,\n \"dependencies\": {\n \"@tencent-weixin/openclaw-weixin\": \"latest\"\n }\n}\n"
return "echo '使用 npm 直装微信插件,避免官方安装器进入扫码等待...' >> " .. shellquote(log_file) .. "; " ..
"OC_WECHAT_NPM_PROJECT=\"$OC_WECHAT_DATA/.openclaw/npm/projects/tencent-weixin-openclaw-weixin-7783ac86ba\"; " ..
"mkdir -p \"$OC_WECHAT_NPM_PROJECT\" \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/npm/projects\"; " ..
"chown -R openclaw:openclaw \"$OC_WECHAT_NPM_PROJECT\" \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/npm/projects\" 2>/dev/null; " ..
"cat > \"$OC_WECHAT_NPM_PROJECT/package.json\" <<'OC_WECHAT_PACKAGE_JSON'\n" ..
package_json ..
"OC_WECHAT_PACKAGE_JSON\n" ..
"chown openclaw:openclaw \"$OC_WECHAT_NPM_PROJECT/package.json\" 2>/dev/null; " ..
"cd \"$OC_WECHAT_NPM_PROJECT\" && " ..
"_oc_as_openclaw 'HOME=$OC_WECHAT_DATA OPENCLAW_HOME=$OC_WECHAT_DATA OPENCLAW_STATE_DIR=$OC_WECHAT_DATA/.openclaw OPENCLAW_CONFIG_PATH=$OC_WECHAT_DATA/.openclaw/openclaw.json " ..
"NODE_ICU_DATA=" .. install_path .. "/node/share/icu NPM_CONFIG_CACHE=$OC_WECHAT_DATA/.npm npm_config_cache=$OC_WECHAT_DATA/.npm TMPDIR=$OC_WECHAT_DATA/.tmp " ..
"PATH=" .. install_path .. "/node/bin:" .. install_path .. "/global/bin:$PATH " ..
"npm install --omit=dev --omit=peer --legacy-peer-deps --no-audit --no-fund --loglevel=error' >> " .. shellquote(log_file) .. " 2>&1; " ..
"RC=$?; echo $RC > " .. shellquote(exit_file) .. "; " ..
"if [ $RC -eq 0 ]; then echo '✅ npm 直装微信插件完成' >> " .. shellquote(log_file) .. "; else echo '❌ npm 直装微信插件失败 (exit: '$RC')' >> " .. shellquote(log_file) .. "; fi; "
end
local function wechat_tail_detail(text, max_lines)
if not text or text == "" then
return ""
@@ -524,21 +550,36 @@ function action_status()
local gw_check = sys.exec(gw_check_cmd):gsub("%s+", "")
result.gateway_running = (tonumber(gw_check) or 0) > 0
-- 如果端口未监听但 procd 进程存在,说明正在启动中 (gateway 初始化需要数分钟)
-- 如果端口未监听,结合 procd 与真实进程判断状态。
-- 不能只看 pid 字段或 pidfile:procd crash-loop / stale pidfile 会让 LuCI 误显示“正在启动”。
if not result.gateway_running and enabled == "1" then
local procd_pid = sys.exec("ubus call service list '{\"name\":\"openclaw\"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.pid' 2>/dev/null"):gsub("%s+", "")
local procd_running = sys.exec("ubus call service list '{\"name\":\"openclaw\"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.running' 2>/dev/null"):gsub("%s+", "")
local procd_exit = sys.exec("ubus call service list '{\"name\":\"openclaw\"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.exit_code' 2>/dev/null"):gsub("%s+", "")
if procd_pid == "null" or not procd_pid:match("^%d+$") then procd_pid = "" end
if procd_exit == "null" then procd_exit = "" end
result.gateway_exit_code = procd_exit
local procd_pid_alive = false
if procd_pid ~= "" then
procd_pid_alive = (sys.exec("[ -d /proc/" .. procd_pid .. " ] && echo 1 || echo 0"):gsub("%s+", "") == "1")
end
local pidfile_pid = sys.exec("cat /var/run/openclaw.pid 2>/dev/null || true"):gsub("%s+", "")
local pidfile_stale = false
if pidfile_pid ~= "" and pidfile_pid:match("^%d+$") then
pidfile_stale = (sys.exec("[ -d /proc/" .. pidfile_pid .. " ] && echo 0 || echo 1"):gsub("%s+", "") == "1")
end
local crash_loop = sys.exec("logread 2>/dev/null | grep -E 'Instance openclaw::gateway.*crash loop' | tail -1"):gsub("^%s+", ""):gsub("%s+$", "")
if procd_exit ~= "" and tonumber(procd_exit) and tonumber(procd_exit) ~= 0 and procd_running ~= "true" then
result.gateway_failed = true
elseif procd_pid ~= "" or procd_running == "true" then
elseif crash_loop ~= "" and pidfile_stale and procd_running ~= "true" and not procd_pid_alive then
result.gateway_failed = true
result.gateway_crash_loop = true
if result.gateway_exit_code == "" then result.gateway_exit_code = "crash-loop" end
elseif procd_running == "true" or procd_pid_alive then
result.gateway_starting = true
else
local fallback_pid = sys.exec("pgrep -f 'openclaw.*gateway' 2>/dev/null | head -1"):gsub("%s+", "")
if fallback_pid ~= "" then
result.gateway_starting = true
end
result.pid = procd_pid
end
end
@@ -637,10 +678,14 @@ function action_service_ctl()
-- stop 后额外等待确保端口释放
sys.exec("sleep 2")
elseif action == "restart" then
-- 先完整 stop (确保端口释放),再后台 start
sys.exec("/etc/init.d/openclaw stop >/dev/null 2>&1")
sys.exec("sleep 2")
sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &")
-- 常规“重启”只重启 Gateway,不重启 Web PTY,避免 stop+start 带来的长时间等待。
-- 如果 procd 没有 gateway 实例,再回退到 start。
local procd_running = sys.exec("ubus call service list '{\"name\":\"openclaw\"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.running' 2>/dev/null"):gsub("%s+", "")
if procd_running == "true" then
sys.exec("/etc/init.d/openclaw restart_gateway >/dev/null 2>&1 &")
else
sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &")
end
elseif action == "enable" then
sys.exec("/etc/init.d/openclaw enable 2>/dev/null")
elseif action == "disable" then
@@ -1549,10 +1594,7 @@ function action_wechat_install()
return
end
-- 后台执行安装
-- 在启动安装前,确保网关端口可用(自动清理残留 gateway 进程)
local port = uci:get("openclaw", "main", "port") or "18789"
ensure_port_free(port)
-- 后台执行安装。注意:插件安装不需要释放 Gateway 端口,避免误停正在运行的 Gateway 触发 procd crash-loop。
-- 微信插件安装目录路径 (用于安装后权限修复)
local extensions_dir = install_path .. "/data/.openclaw/extensions"
local install_cmd = string.format(
@@ -1569,13 +1611,7 @@ function action_wechat_install()
"else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; fi; " ..
"if [ ! -w %s/.npm ] || [ ! -w %s/.tmp ]; then echo '❌ npm cache/tmp 目录不可写' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; fi; " ..
"_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " ..
"cd %s && " ..
"_oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw " ..
"OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json NPM_CONFIG_CACHE=%s/.npm npm_config_cache=%s/.npm TMPDIR=%s/.tmp " ..
"PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s -y @tencent-weixin/openclaw-weixin-cli install' >> /tmp/openclaw-wechat-install.log 2>&1; " ..
"RC=$?; echo $RC > /tmp/openclaw-wechat-install.exit; " ..
wechat_npm_fallback_install_cmd(install_path, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") ..
wechat_npm_direct_install_cmd(install_path, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") ..
wechat_register_plugin_cmd(install_path, node_bin, "/tmp/openclaw-wechat-install.log") ..
-- 关键修复: 安装完成后强制修复插件目录权限 (确保 Gateway 可读取插件)
-- 原因: npx/npm 以 root 身份创建目录,默认权限 700 导致其他用户无法读取
@@ -1589,8 +1625,6 @@ function action_wechat_install()
oc_data, oc_data,
oc_data, oc_data, oc_data,
install_path, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data,
install_path, install_path, npx_bin,
oc_data
)
sys.exec(install_cmd)
@@ -1731,15 +1765,23 @@ function action_wechat_login()
"if [ $RC -ne 0 ]; then echo '❌ 微信插件注册失败,无法登录' >> /tmp/openclaw-wechat-qrcode.txt; exit 0; fi; " ..
"cd %s && " ..
"_oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " ..
"NODE_ICU_DATA=%s/node/share/icu " ..
"NPM_CONFIG_CACHE=%s/.npm npm_config_cache=%s/.npm TMPDIR=%s/.tmp PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s %s channels login --channel openclaw-weixin' >> /tmp/openclaw-wechat-qrcode.txt 2>&1; " ..
"echo $? > /tmp/openclaw-wechat-login.exit; " ..
") >/dev/null 2>&1 & echo $! > /tmp/openclaw-wechat-login.pid",
install_path, oc_entry, wechat_plugin_dir, node_bin,
oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data,
install_path, install_path, node_bin, oc_entry
oc_data, oc_data, oc_data,
oc_data,
oc_data, oc_data, oc_data,
oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data, oc_data,
install_path,
oc_data, oc_data, oc_data, oc_data,
install_path,
oc_data, oc_data, oc_data,
install_path, install_path,
node_bin, oc_entry
)
sys.exec(login_cmd)
@@ -1798,8 +1840,15 @@ function action_wechat_login_status()
end
end
end
-- 检查是否登录成功
local logged_in = qrcode:find("登录成功") ~= nil or qrcode:find("成功登录") ~= nil or qrcode:find("Login success") ~= nil or qrcode:find("Logged in") ~= nil
-- 检查是否登录成功。微信插件在 OpenClaw 2026.6.11 中可能已经保存认证,
-- 但随后通过 Gateway 动态启动渠道时报 "invalid channels.start channel" 并返回
-- 非 0;这种情况账号已配对成功,LuCI 不应误判为失败。
local logged_in = qrcode:find("登录成功") ~= nil
or qrcode:find("成功登录") ~= nil
or qrcode:find("Login success") ~= nil
or qrcode:find("Logged in") ~= nil
or qrcode:find("已将此 OpenClaw 连接到微信") ~= nil
or qrcode:find("Local login saved auth for openclaw%-weixin") ~= nil
local state = "idle"
if logged_in then
@@ -1825,10 +1874,11 @@ function action_wechat_login_status()
end
end
-- 如果刚登录成功,触发一次重启,确保主进程加载微信账号
-- 如果刚登录成功,触发一次轻量重启,确保主进程加载微信账号。
-- 不使用完整 /etc/init.d/openclaw restart,避免同时重启 Web PTY 和拉长等待时间。
if state == "success" and not nixio.fs.stat("/tmp/openclaw-wechat-restarted", "type") then
sys.exec("touch /tmp/openclaw-wechat-restarted")
sys.exec("/etc/init.d/openclaw restart &")
sys.exec("/etc/init.d/openclaw restart_gateway >/dev/null 2>&1 &")
message = "微信登录成功,正在重新加载微信账号"
end
http.prepare_content("application/json")
@@ -2013,10 +2063,7 @@ function action_wechat_upgrade_plugin()
return
end
-- 后台执行升级 (其实就是重新安装最新版)
-- 在启动升级前,确保网关端口可用(自动清理残留 gateway 进程)
local port = uci:get("openclaw", "main", "port") or "18789"
ensure_port_free(port)
-- 后台执行升级 (其实就是重新安装最新版)。不要释放 Gateway 端口,避免误停正在运行的 Gateway。
-- 微信插件安装目录路径 (用于升级后权限修复)
local extensions_dir = install_path .. "/data/.openclaw/extensions"
local upgrade_cmd = string.format(
@@ -2033,13 +2080,7 @@ function action_wechat_upgrade_plugin()
"else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; fi; " ..
"if [ ! -w %s/.npm ] || [ ! -w %s/.tmp ]; then echo '❌ npm cache/tmp 目录不可写' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; fi; " ..
"_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " ..
"cd %s && " ..
"_oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw " ..
"OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json NPM_CONFIG_CACHE=%s/.npm npm_config_cache=%s/.npm TMPDIR=%s/.tmp " ..
"PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s -y @tencent-weixin/openclaw-weixin-cli install' >> /tmp/openclaw-wechat-install.log 2>&1; " ..
"RC=$?; echo $RC > /tmp/openclaw-wechat-install.exit; " ..
wechat_npm_fallback_install_cmd(install_path, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") ..
wechat_npm_direct_install_cmd(install_path, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") ..
wechat_register_plugin_cmd(install_path, node_bin, "/tmp/openclaw-wechat-install.log") ..
-- 关键修复: 升级完成后强制修复插件目录权限 (确保 Gateway 可读取插件)
-- 注意: 保持 root:root 属主 (OpenClaw v2026.4.9+ 安全要求),仅修复权限模式
@@ -2052,8 +2093,6 @@ function action_wechat_upgrade_plugin()
oc_data, oc_data,
oc_data, oc_data, oc_data,
install_path, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data,
install_path, install_path, npx_bin,
oc_data
)
sys.exec(upgrade_cmd)
@@ -2114,10 +2153,10 @@ function action_wechat_logout()
-- 在后台执行 logout
local logout_cmd = string.format(
openclaw_user_runner_cmd() .. "cd %s && _oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " ..
openclaw_user_runner_cmd() .. "cd %s && _oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json NODE_ICU_DATA=%s/node/share/icu " ..
"PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s %s channels logout --channel openclaw-weixin --account \"%s\"'",
oc_data, oc_data, oc_data, oc_data, oc_data, install_path, install_path, node_bin, oc_entry, account_id
oc_data, oc_data, oc_data, oc_data, oc_data, install_path, install_path, install_path, node_bin, oc_entry, account_id
)
sys.exec(logout_cmd .. " >/dev/null 2>&1")
@@ -149,7 +149,7 @@ local port = uci:get("openclaw", "main", "port") or "18789"
loading.innerHTML = '<div style="text-align:center;color:#666;">' +
'<div style="font-size:40px;margin-bottom:12px;">⏳</div>' +
'<div style="font-size:15px;margin-bottom:6px;">OpenClaw 网关正在启动中...</div>' +
'<div style="font-size:12px;color:#999;">首次启动可能需要 2~5 分钟,请耐心等待,页面会自动刷新。</div>' +
'<div style="font-size:12px;color:#999;">通常 20~40 秒;首次安装或路由器负载较高时会更久,页面会自动刷新。</div>' +
'</div>';
} else {
statusTextEl.innerHTML = '<span style="color:#cf222e;">● 网关未运行</span>';
@@ -112,7 +112,7 @@
if (d.gateway_running) {
gwEl.innerHTML = '<span class="oc-dot oc-dot-green"></span>监听中 :' + d.port;
} else if (d.gateway_starting) {
gwEl.innerHTML = '<span class="oc-dot oc-dot-gray"></span>初始化中,首次启动可能需要 2~5 分钟...';
gwEl.innerHTML = '<span class="oc-dot oc-dot-gray"></span>初始化中,通常 20~40 秒;首次安装或负载较高时会更久...';
} else if (d.gateway_failed) {
gwEl.innerHTML = '<span class="oc-dot oc-dot-red"></span>启动失败,退出码 ' + (d.gateway_exit_code || '-');
} else {
+45 -6
View File
@@ -448,7 +448,13 @@ if(changed){
fi
fix_openclaw_state_permissions 2>/dev/null || true
rm -rf /tmp/jiti 2>/dev/null || true
# 不再每次启动都清理 /tmp/jiti。
# OpenClaw 在低性能 OpenWrt 设备上依赖 JITI/模块缓存降低冷启动成本;
# 每次 rm -rf 会让 gateway 在第一条 "starting..." 日志前重新编译/装载模块,
# 实测会额外拉长 20 秒以上。需要强制清理时再显式打开 UCI 开关。
if [ "$(uci -q get openclaw.main.clear_jiti_cache_on_start 2>/dev/null || echo 0)" = "1" ]; then
rm -rf /tmp/jiti 2>/dev/null || true
fi
# v2026.4.10: 优化配置迁移 - 只在版本变更时运行 doctor --fix
# 原因: doctor --fix 耗时 ~50 秒,每次重启都运行严重影响启动速度
@@ -637,6 +643,13 @@ _ensure_port_free() {
}
_ensure_port_free "$port"
# Node 22 原生编译缓存。OpenClaw 主入口在路由器上首次 import/compile 较慢;
# 保留该缓存可以显著缩短后续重启时 gateway 第一条日志前的等待时间。
local node_compile_cache="/tmp/openclaw-node-compile-cache"
mkdir -p "$node_compile_cache" 2>/dev/null || true
chown openclaw:openclaw "$node_compile_cache" 2>/dev/null || true
chmod 700 "$node_compile_cache" 2>/dev/null || true
# 启动 OpenClaw Gateway (主服务, 前台运行)
# v2026.4.7/4.9: 环境变量安全验证 (阻止危险变量注入)
_validate_path() {
@@ -660,13 +673,15 @@ _ensure_port_free "$port"
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
OPENCLAW_GATEWAY_TOKEN="$gw_token" \
NODE_COMPILE_CACHE="$node_compile_cache" \
NODE_ICU_DATA="${NODE_BASE}/share/icu" \
NODE_BASE="$NODE_BASE" \
OC_GLOBAL="$OC_GLOBAL" \
OC_DATA="$OC_DATA" \
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
procd_set_param user openclaw
procd_set_param respawn 3600 10 5
procd_set_param term_timeout 2
procd_set_param respawn 3600 1 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/openclaw.pid
@@ -689,6 +704,7 @@ procd_set_param env \
OC_CONFIG_PORT="$pty_port" \
OC_PTY_TOKEN="$pty_token" \
OC_CONFIG_SCRIPT="/usr/share/openclaw/oc-config.sh" \
NODE_COMPILE_CACHE="$node_compile_cache" \
NODE_ICU_DATA="${NODE_BASE}/share/icu" \
NODE_BASE="$NODE_BASE" \
OC_GLOBAL="$OC_GLOBAL" \
@@ -699,6 +715,7 @@ OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
procd_set_param respawn 3600 5 5
procd_set_param term_timeout 2
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/openclaw-pty.pid
@@ -842,9 +859,10 @@ fi
}
status_service() {
local port pty_port
local port pty_port enabled
port=$(uci -q get openclaw.main.port || echo "18789")
pty_port=$(uci -q get openclaw.main.pty_port || echo "18793")
enabled=$(uci -q get openclaw.main.enabled || echo "0")
echo "=== OpenClaw 服务状态 ==="
@@ -908,10 +926,31 @@ local mins=$(( (uptime % 3600) / 60 ))
echo "运行时间: ${hours}小时 ${mins}分钟"
fi
fi
elif pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
echo "网关: 正在启动 (首次启动可能需要 2~5 分钟)"
else
echo "网关: 已停止"
local procd_running procd_pid procd_exit procd_pid_alive pidfile_pid pidfile_stale crash_loop
procd_running=$(ubus call service list '{"name":"openclaw"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.running' 2>/dev/null | tr -d '[:space:]')
procd_pid=$(ubus call service list '{"name":"openclaw"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.pid' 2>/dev/null | tr -d '[:space:]')
procd_exit=$(ubus call service list '{"name":"openclaw"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.exit_code' 2>/dev/null | tr -d '[:space:]')
[ "$procd_pid" = "null" ] && procd_pid=""
[ "$procd_exit" = "null" ] && procd_exit=""
procd_pid_alive=0
[ -n "$procd_pid" ] && [ -d "/proc/$procd_pid" ] && procd_pid_alive=1
pidfile_pid=$(cat /var/run/openclaw.pid 2>/dev/null | tr -d '[:space:]')
pidfile_stale=0
[ -n "$pidfile_pid" ] && [ ! -d "/proc/$pidfile_pid" ] && pidfile_stale=1
crash_loop=$(logread 2>/dev/null | grep -E 'Instance openclaw::gateway.*crash loop' | tail -1)
if [ "$enabled" != "1" ]; then
echo "网关: 已禁用"
elif [ -n "$procd_exit" ] && [ "$procd_exit" != "0" ] && [ "$procd_running" != "true" ]; then
echo "网关: 启动失败 (退出码 $procd_exit)"
elif [ -n "$crash_loop" ] && [ "$pidfile_stale" = "1" ] && [ "$procd_running" != "true" ] && [ "$procd_pid_alive" != "1" ]; then
echo "网关: 启动失败 (procd crash-loop 抑制)"
elif [ "$procd_running" = "true" ] || [ "$procd_pid_alive" = "1" ]; then
echo "网关: 正在启动 (通常 20~40 秒,首次安装或负载较高时会更久)"
[ -n "$procd_pid" ] && echo "进程ID: $procd_pid"
else
echo "网关: 已停止"
fi
fi
# PTY port
@@ -229,6 +229,35 @@ assert_node_runtime() {
if command -v oc_assert_node_min_version >/dev/null 2>&1; then
oc_assert_node_min_version "$NODE_BIN" "$required" || exit 1
fi
# OpenClaw CLI 依赖 Unicode property escapes(例如 \p{Extended_Pictographic})。
# ARM64 musl 自托管 Node 包必须带上 NODE_ICU_DATA,否则用户直接执行
# `openclaw status` 等子命令会在 CLI 入口阶段抛 SyntaxError。
if ! NODE_ICU_DATA="${NODE_BASE}/share/icu" "$NODE_BIN" -e "new RegExp('\\\\p{Extended_Pictographic}','u')" >/dev/null 2>&1; then
log_error "Node.js ICU/Unicode 正则能力异常,请重新安装 Node.js 运行时"
exit 1
fi
}
install_openclaw_cli_wrapper() {
local oc_entry
oc_entry=$(find_oc_entry)
[ -n "$oc_entry" ] || return 1
ensure_mkdir "$OC_GLOBAL/bin"
# npm 默认生成的 openclaw 可能是指向 openclaw.mjs 的 symlink。
# 写 wrapper 前必须先 unlink,否则重定向会顺着 symlink 覆盖真实 CLI 入口。
rm -f "$OC_GLOBAL/bin/openclaw" 2>/dev/null || true
cat > "$OC_GLOBAL/bin/openclaw" << EOF
#!/bin/sh
export HOME="${OC_DATA}"
export OPENCLAW_HOME="${OC_DATA}"
export OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw"
export OPENCLAW_CONFIG_PATH="${OC_DATA}/.openclaw/openclaw.json"
export NODE_ICU_DATA="${NODE_BASE}/share/icu"
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:\$PATH"
exec "${NODE_BIN}" "${oc_entry}" "\$@"
EOF
chmod +x "$OC_GLOBAL/bin/openclaw" 2>/dev/null || true
}
detect_arch() {
@@ -471,6 +500,7 @@ install_openclaw() {
fi
if [ -n "$oc_ver" ]; then
install_openclaw_cli_wrapper || true
log_info "OpenClaw v${oc_ver} 安装成功"
assert_node_runtime
else
@@ -510,6 +540,7 @@ do_setup() {
# 检查是否已安装
if [ -x "$NODE_BIN" ] && [ -n "$(find_oc_entry)" ]; then
install_openclaw_cli_wrapper || true
local oc_ver=""
local oc_entry="$(find_oc_entry)"
local oc_pkg_dir="$(dirname "$oc_entry")"
@@ -665,6 +696,7 @@ do_upgrade() {
echo ""
if [ -n "$new_ver" ]; then
install_openclaw_cli_wrapper || true
assert_node_runtime
if [ "$current_ver" = "$new_ver" ]; then
log_info "当前已是最新版本 v${new_ver}"
@@ -827,6 +859,7 @@ do_setup_offline() {
local oc_entry=$(find_oc_entry)
if [ -n "$oc_entry" ]; then
local oc_ver=$("$NODE_BIN" "$oc_entry" --version 2>/dev/null | tr -d '[:space:]')
install_openclaw_cli_wrapper || true
log_info "OpenClaw v${oc_ver} 安装成功"
assert_node_runtime
else
@@ -338,7 +338,15 @@ async function restartGateway() {
console.log(`\n${C.yellow}正在重启 Gateway...${C.reset}`);
try {
await runCommand('/etc/init.d/openclaw', ['restart_gateway']);
await runCommand('/bin/sh', ['-c', [
"if [ \"$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)\" != \"1\" ]; then",
" uci -q set openclaw.main.enabled='1';",
" uci -q commit openclaw;",
" /etc/init.d/openclaw enable >/dev/null 2>&1 || true;",
'fi;',
'/etc/init.d/openclaw restart_gateway >/dev/null 2>&1 || true;',
'/etc/init.d/openclaw start >/dev/null 2>&1 || true'
].join(' ')]);
} catch {}
const spin = spinner({ text: 'Gateway 启动中,请稍候 (约 15-30 秒)...' });
@@ -558,12 +558,22 @@ restart_gateway() {
# 修复数据目录权限 (root 用户操作可能改变了文件属主)
fix_openclaw_state_permissions 2>/dev/null || true
# 首次安装后 UCI 默认保持 disabled。用户在配置向导中确认“立即重启”
# 即表示希望当前配置生效,这里同步启用服务,避免提示重启但实际仍为“已禁用”。
if [ "$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)" != "1" ]; then
echo -e " ${CYAN}检测到 Gateway 未启用,正在启用服务...${NC}"
uci -q set openclaw.main.enabled='1' 2>/dev/null || true
uci -q commit openclaw 2>/dev/null || true
/etc/init.d/openclaw enable >/dev/null 2>&1 || true
fi
local port
port=$(json_get gateway.port)
port=${port:-18789}
# ── kill gateway 进程,让 procd respawn ──
/etc/init.d/openclaw restart_gateway >/dev/null 2>&1
/etc/init.d/openclaw start >/dev/null 2>&1
# ── 等待端口恢复 (最多 30 秒,含端口释放 + Node.js 冷启动) ──
echo -e " ${YELLOW}⏳ Gateway 启动中,请稍候 (约 15-30 秒)...${NC}"
+41 -1
View File
@@ -94,7 +94,9 @@ chmod +x "$DATA_DIR/etc/profile.d/openclaw.sh"
# 计算安装大小
INSTALLED_SIZE=$(du -sk "$DATA_DIR" | awk '{print $1}')
(cd "$DATA_DIR" && tar czf "$STAGING/data.tar.gz" .)
# 强制 data.tar.gz 内文件归属为 root:root,避免 GitHub runner / 本地构建用户
# 的 UID/GID 泄漏到用户机器(例如安装后出现 1001:1001)。
(cd "$DATA_DIR" && tar --owner=0 --group=0 --numeric-owner -czf "$STAGING/data.tar.gz" .)
# ── 构建 control.tar.gz ──
CTRL_DIR="$STAGING/control"
@@ -169,10 +171,48 @@ cat > "$CTRL_DIR/postinst" << 'EOF'
# 清理 LuCI 缓存
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* /tmp/luci-indexcache.*.json 2>/dev/null
# 双保险:确保系统侧文件保持 root:root,避免非 SDK 打包时构建机 UID/GID
# 泄漏到目标机器。不要触碰 /opt/openclaw 运行数据。
for p in \
/etc/init.d/openclaw \
/etc/profile.d/openclaw.sh \
/usr/bin/openclaw-env \
/usr/libexec/openclaw-permissions.sh \
/usr/lib/lua/luci/controller/openclaw.lua \
/usr/lib/lua/luci/model/cbi/openclaw \
/usr/lib/lua/luci/view/openclaw \
/usr/lib/lua/openclaw \
/usr/share/openclaw \
/usr/share/rpcd/acl.d/luci-app-openclaw.json
do
[ -e "$p" ] && chown -R root:root "$p" 2>/dev/null || true
done
# 升级/重装后修复已存在的 OpenClaw 运行数据权限。尤其是 npm 外部插件:
# npm 安装阶段需要 openclaw 可写,运行阶段插件源码目录必须 root:root,
# 否则 OpenClaw 会因为 suspicious ownership 拒绝加载微信插件。
OPENCLAW_INSTALL_BASE="$(uci -q get openclaw.main.install_path 2>/dev/null || echo /opt)"
if [ -r /usr/libexec/openclaw-paths.sh ]; then
. /usr/libexec/openclaw-paths.sh
oc_load_paths "$OPENCLAW_INSTALL_BASE" 2>/dev/null || true
else
OPENCLAW_INSTALL_BASE="${OPENCLAW_INSTALL_BASE%/}"
OC_DATA="${OPENCLAW_INSTALL_BASE}/openclaw/data"
fi
if [ -n "${OC_DATA:-}" ] && [ -d "${OC_DATA}/.openclaw" ] && [ -x /usr/libexec/openclaw-permissions.sh ]; then
/usr/libexec/openclaw-permissions.sh fix-state "${OC_DATA}/.openclaw" >/dev/null 2>&1 || true
fi
# 重启 Web PTY (使其加载新文件和新 token)
PTY_PID=$(pgrep -f 'web-pty.js' 2>/dev/null | head -1)
[ -n "$PTY_PID" ] && kill "$PTY_PID" 2>/dev/null || true
# 如果用户原本启用了 OpenClaw,opkg reinstall/upgrade 后恢复服务。
if [ "$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)" = "1" ] && [ -x /etc/init.d/openclaw ]; then
/etc/init.d/openclaw enable >/dev/null 2>&1 || true
/etc/init.d/openclaw start >/dev/null 2>&1 || true
fi
exit 0
}
+21 -1
View File
@@ -132,6 +132,24 @@ echo "正在安装文件..."
ARCHIVE=$(awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$0")
tail -n +$ARCHIVE "$0" | tar xzf - -C / 2>/dev/null
# 双保险:即使构建环境 tar 不支持 owner 规范化,也不要把构建机 UID/GID
# 带到路由器系统文件中。这里仅修复本插件安装的系统侧文件,不触碰
# /opt/openclaw 运行数据和 OpenClaw npm 插件状态目录。
for p in \
/etc/init.d/openclaw \
/etc/profile.d/openclaw.sh \
/usr/bin/openclaw-env \
/usr/libexec/openclaw-permissions.sh \
/usr/lib/lua/luci/controller/openclaw.lua \
/usr/lib/lua/luci/model/cbi/openclaw \
/usr/lib/lua/luci/view/openclaw \
/usr/lib/lua/openclaw \
/usr/share/openclaw \
/usr/share/rpcd/acl.d/luci-app-openclaw.json
do
[ -e "$p" ] && chown -R root:root "$p" 2>/dev/null || true
done
# UCI 配置文件保护: 升级时不覆盖用户已有配置
if [ -f /etc/config/openclaw ] && [ -f /etc/config/openclaw.default ]; then
# 已有配置, 移除默认文件 (保留用户配置)
@@ -261,7 +279,9 @@ echo "[4/4] 打包..."
mkdir -p "$OUT_DIR"
# 创建 payload tarball
(cd "$STAGING/payload" && tar czf "$STAGING/payload.tar.gz" .)
# 强制 payload 内文件归属为 root:root,避免 GitHub runner / 本地构建用户
# 的 UID/GID 泄漏到用户机器(例如安装后出现 1001:1001)。
(cd "$STAGING/payload" && tar --owner=0 --group=0 --numeric-owner -czf "$STAGING/payload.tar.gz" .)
# 组合: installer header + payload
RUN_FILE="$OUT_DIR/${PKG_NAME}_${PKG_VERSION}.run"
@@ -11,6 +11,12 @@ grep -q "NODE_VERSION_V2=\"22.23.0\"" root/usr/bin/openclaw-env || fail "default
grep -q "OC_NODE_MIN_VERSION=\"\${OC_NODE_MIN_VERSION:-22.19.0}\"" root/usr/bin/openclaw-env || fail "minimum Node.js version not pinned"
grep -q "oc_assert_node_min_version" root/usr/bin/openclaw-env || fail "Node.js minimum version check missing"
grep -q 'oc_node_version_ge "$from_pkg" "$required"' root/usr/bin/openclaw-env || fail "package Node.js requirement must not lower static minimum"
grep -q "install_openclaw_cli_wrapper" root/usr/bin/openclaw-env || fail "OpenClaw CLI wrapper must set runtime env"
grep -q 'export NODE_ICU_DATA="${NODE_BASE}/share/icu"' root/usr/bin/openclaw-env || fail "OpenClaw CLI wrapper must export NODE_ICU_DATA"
grep -q 'rm -f "$OC_GLOBAL/bin/openclaw"' root/usr/bin/openclaw-env || fail "OpenClaw CLI wrapper must unlink npm symlink before writing"
grep -q "Extended_Pictographic" root/usr/bin/openclaw-env || fail "Node runtime must validate Unicode property escapes"
grep -q "uci -q set openclaw.main.enabled='1'" root/usr/share/openclaw/oc-config.sh || fail "traditional config restart must enable gateway after first install"
grep -q "uci -q set openclaw.main.enabled='1'" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive config restart must enable gateway after first install"
if grep -q 'v1_tarball' root/usr/bin/openclaw-env; then
fail "installer must not silently fall back to legacy Node.js tarball"
fi
@@ -18,6 +24,10 @@ fi
grep -q "wechat.htm" Makefile || fail "Makefile must install wechat.htm"
grep -q "luci-app-openclaw.json" Makefile || fail "Makefile must install rpcd ACL"
grep -q "openclaw-permissions.sh" Makefile || fail "Makefile must install permission helper"
grep -q 'openclaw-permissions.sh fix-state "$${OC_DATA}/.openclaw"' Makefile || fail "postinst must repair existing OpenClaw state permissions after reinstall"
grep -q '/etc/init.d/openclaw start >/dev/null 2>&1' Makefile || fail "postinst must restart enabled OpenClaw service after reinstall"
grep -q 'openclaw-permissions.sh fix-state "${OC_DATA}/.openclaw"' scripts/build_ipk.sh || fail "release ipk postinst must repair existing OpenClaw state permissions after reinstall"
grep -q '/etc/init.d/openclaw start >/dev/null 2>&1' scripts/build_ipk.sh || fail "release ipk postinst must restart enabled OpenClaw service after reinstall"
if grep -q "openclaw.zh-cn.lmo" Makefile; then
fail "main package must not install openclaw.zh-cn.lmo"
fi
@@ -52,11 +62,19 @@ grep -q "packageName: '@tencent-weixin/openclaw-weixin'" luasrc/controller/openc
grep -q "channels\\['openclaw-weixin'\\].enabled = true" luasrc/controller/openclaw.lua || fail "wechat channel enable registration missing"
grep -q "Registered openclaw-weixin npm plugin" luasrc/controller/openclaw.lua || fail "wechat install log must confirm config registration"
grep -q "wechat_network_probe_cmd" luasrc/controller/openclaw.lua || fail "wechat network probe helper missing"
grep -q "NODE_ICU_DATA=%s/node/share/icu" luasrc/controller/openclaw.lua || fail "wechat LuCI CLI commands must export NODE_ICU_DATA"
grep -q "openclaw_user_runner_cmd" luasrc/controller/openclaw.lua || fail "wechat commands need user runner compatibility helper"
grep -q "ulimit -v unlimited" luasrc/controller/openclaw.lua || fail "wechat commands must lift LuCI inherited address-space limit before Node CLI"
grep -q "start-stop-daemon -S -m -p" luasrc/controller/openclaw.lua || fail "wechat commands must support OpenWrt without su"
grep -q "command -v curl" luasrc/controller/openclaw.lua || fail "wechat network probe should prefer curl"
grep -q "%%{http_code}" luasrc/controller/openclaw.lua || fail "wechat curl probe percent must be escaped for string.format"
grep -q "wechat_npm_fallback_install_cmd" luasrc/controller/openclaw.lua || fail "wechat install needs npm fallback for OpenWrt Node undici OOM"
if awk '/function action_wechat_install\(\)/,/function action_wechat_install_log\(\)/' luasrc/controller/openclaw.lua | grep -q "ensure_port_free"; then
fail "wechat install must not stop gateway before plugin installation"
fi
if awk '/function action_wechat_upgrade_plugin\(\)/,/function action_wechat_uninstall_plugin\(\)/' luasrc/controller/openclaw.lua | grep -q "ensure_port_free"; then
fail "wechat upgrade must not stop gateway before plugin upgrade"
fi
grep -q "npm install --omit=dev --omit=peer" luasrc/controller/openclaw.lua || fail "wechat npm fallback install command missing"
grep -q "tencent-weixin-openclaw-weixin-7783ac86ba" luasrc/controller/openclaw.lua || fail "wechat npm fallback project dir missing"
grep -q "export OC_WECHAT_DATA" luasrc/controller/openclaw.lua || fail "wechat npm fallback must export data dir into openclaw user shell"
@@ -66,6 +84,9 @@ grep -q "is_newer_version(latest_version, current_version)" luasrc/controller/op
grep -q "ilinkai.weixin.qq.com" luasrc/controller/openclaw.lua || fail "wechat network probe endpoint missing"
grep -q "微信接口连通性检查" luasrc/controller/openclaw.lua || fail "wechat network probe log missing"
grep -q "error_detail" luasrc/controller/openclaw.lua || fail "wechat login failure detail response missing"
grep -q "已将此 OpenClaw 连接到微信" luasrc/controller/openclaw.lua || fail "wechat login status must treat saved auth as success"
grep -q "Local login saved auth for openclaw%-weixin" luasrc/controller/openclaw.lua || fail "wechat login status must tolerate channels.start restart warning"
grep -q "/etc/init.d/openclaw restart_gateway >/dev/null 2>&1 &" luasrc/controller/openclaw.lua || fail "wechat login success should use lightweight gateway restart"
grep -q "self_heal_wechat_npm_plugin_config" root/etc/init.d/openclaw || fail "init.d wechat npm plugin self-heal missing"
grep -q "已自愈注册 openclaw-weixin npm plugin" root/etc/init.d/openclaw || fail "init.d wechat self-heal log missing"
grep -q "OC_WECHAT_PLUGIN_DIR" root/etc/init.d/openclaw || fail "init.d wechat self-heal plugin dir env missing"
@@ -74,6 +95,14 @@ grep -q "fix_openclaw_state_permissions" root/etc/init.d/openclaw || fail "init.
grep -q 'NODE_ICU_DATA="${NODE_BASE}/share/icu" PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH"' root/etc/init.d/openclaw || fail "doctor migration must run with Node ICU data"
grep -q "Token 同步 (doctor 后): UCI -> JSON" root/etc/init.d/openclaw || fail "doctor must restore missing JSON token from UCI"
grep -q 'OPENCLAW_GATEWAY_TOKEN="$gw_token"' root/etc/init.d/openclaw || fail "gateway must receive token env fallback"
grep -q "clear_jiti_cache_on_start" root/etc/init.d/openclaw || fail "JITI cache cleanup must be opt-in to avoid slow startup"
grep -q "procd_set_param term_timeout 2" root/etc/init.d/openclaw || fail "procd term timeout must be bounded for faster restart"
grep -q "procd_set_param respawn 3600 1 5" root/etc/init.d/openclaw || fail "gateway respawn timeout must be short for faster restart_gateway"
grep -q 'openclaw.main.clear_jiti_cache_on_start' root/etc/init.d/openclaw || fail "JITI cleanup must be guarded by UCI flag"
grep -q "openclaw-node-compile-cache" root/etc/init.d/openclaw || fail "Node compile cache must be enabled for faster warm restarts"
grep -q 'NODE_COMPILE_CACHE="$node_compile_cache"' root/etc/init.d/openclaw || fail "gateway and pty must receive Node compile cache env"
grep -q "通常 20~40 秒" root/etc/init.d/openclaw luasrc/view/openclaw/status.htm luasrc/view/openclaw/console.htm || fail "startup hint must match measured OpenWrt timing"
grep -q "restart_gateway >/dev/null 2>&1 &" luasrc/controller/openclaw.lua || fail "LuCI restart action should use lightweight gateway restart"
grep -q "oc_fix_npm_projects_permissions" root/usr/libexec/openclaw-permissions.sh || fail "npm projects permission helper missing"
grep -q "openclaw.plugin.json" root/usr/libexec/openclaw-permissions.sh || fail "npm plugin root detection must use plugin manifest"
grep -q "node_modules/@tencent-weixin/openclaw-weixin" root/usr/libexec/openclaw-permissions.sh || fail "wechat npm plugin compatibility permission fix missing"
@@ -125,6 +154,10 @@ fi
grep -q "root/usr/libexec" scripts/build_ipk.sh || fail "ipk script must package shell helpers"
grep -q "root/usr/libexec" scripts/build_run.sh || fail "run script must package shell helpers"
grep -q "for dep in luci-compat luci-base curl openssl-util script-utils tar libstdcpp6" scripts/build_run.sh || fail ".run installer must install runtime dependencies"
grep -q -- "--owner=0 --group=0 --numeric-owner" scripts/build_run.sh || fail ".run payload must normalize file ownership to root"
grep -q -- "--owner=0 --group=0 --numeric-owner" scripts/build_ipk.sh || fail ".ipk payload must normalize file ownership to root"
grep -q "chown -R root:root" scripts/build_run.sh || fail ".run installer must repair root-owned system files after extraction"
grep -q "chown -R root:root" scripts/build_ipk.sh || fail ".ipk postinst must repair root-owned system files after extraction"
grep -q "先解压到临时目录并确认完整,再替换 NODE_BASE" root/usr/bin/openclaw-env || fail "Node install must not delete existing runtime before extraction succeeds"
grep -q "OC_SETUP_FRESH_ROOT" root/usr/bin/openclaw-env || fail "setup cleanup must preserve existing runtime roots"
@@ -16,10 +16,19 @@ grep -Fq "ubus call service list" "$CONTROLLER" || fail "status API should inspe
grep -Fq "openclaw.instances.gateway" "$CONTROLLER" || fail "status API should inspect gateway instance state"
grep -Fq "gateway_failed" "$CONTROLLER" || fail "status API should report gateway failure state"
grep -Fq "gateway_exit_code" "$CONTROLLER" || fail "status API should expose recent gateway exit code"
grep -Fq "gateway_crash_loop" "$CONTROLLER" || fail "status API should expose procd crash-loop state"
grep -Fq "procd_pid_alive" "$CONTROLLER" || fail "status API should ignore stale procd pidfiles"
grep -Fq "pidfile_stale" "$CONTROLLER" || fail "status API should require current stale pidfile evidence for crash-loop"
grep -Fq 'enabled=$(uci -q get openclaw.main.enabled' "$INIT_SCRIPT" || fail "status_service should honor disabled service state"
grep -Fq "网关: 已禁用" "$INIT_SCRIPT" || fail "status_service should report disabled gateway before crash-loop"
if grep -Fq 'pgrep -f "openclaw.*gateway"' "$CONTROLLER" "$INIT_SCRIPT" || grep -Fq "pgrep -f 'openclaw.*gateway'" "$CONTROLLER" "$INIT_SCRIPT"; then
fail "status checks must not use broad pgrep fallback that can match the status shell itself"
fi
grep -Fq "启动失败" "$STATUS_VIEW" || fail "status panel should distinguish startup failure from startup in progress"
grep -Fq "gateway_failed" "$STATUS_VIEW" || fail "status panel should render gateway failure state"
grep -Fq "ubus call service list" "$INIT_SCRIPT" || fail "status_service should inspect procd state via ubus"
grep -Fq "crash-loop 抑制" "$INIT_SCRIPT" || fail "status_service should report procd crash-loop suppression"
echo "ok"