From ee10bb0bd59e9dee2dd642c4cdbc9809401b8d54 Mon Sep 17 00:00:00 2001 From: 10000ge10000 <10000ge10000@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:29:45 +0800 Subject: [PATCH] fix/feat: update luci configuration pages, init scripts, and env setup tools --- luasrc/controller/openclaw.lua | 82 ++++++++++++++++++++- luasrc/model/cbi/openclaw/basic.lua | 75 +++++++++++++++----- root/etc/init.d/openclaw | 102 +++++++++++++++------------ root/usr/bin/openclaw-env | 17 +---- root/usr/share/openclaw/oc-config.sh | 62 ++++++++++++++++ 5 files changed, 260 insertions(+), 78 deletions(-) diff --git a/luasrc/controller/openclaw.lua b/luasrc/controller/openclaw.lua index 4a96cca..4a83e93 100644 --- a/luasrc/controller/openclaw.lua +++ b/luasrc/controller/openclaw.lua @@ -41,6 +41,9 @@ function index() -- 配置备份 API (v2026.3.8+: openclaw backup create/verify) entry({"admin", "services", "openclaw", "backup"}, call("action_backup"), nil).leaf = true + + -- 系统配置检测 API (安装前检测) + entry({"admin", "services", "openclaw", "check_system"}, call("action_check_system"), nil).leaf = true end -- ═══════════════════════════════════════════ @@ -372,19 +375,21 @@ function action_uninstall() sys.exec("/etc/init.d/openclaw disable 2>/dev/null") -- 设置 UCI enabled=0 sys.exec("uci set openclaw.main.enabled=0; uci commit openclaw 2>/dev/null") - -- 删除 Node.js + OpenClaw 运行环境 + -- 删除 Node.js + OpenClaw 运行环境 (包含所有插件: qqbot, 飞书等) sys.exec("rm -rf /opt/openclaw") -- 清理旧数据迁移后可能残留的目录 sys.exec("rm -rf /root/.openclaw 2>/dev/null") -- 清理临时文件 - sys.exec("rm -f /tmp/openclaw-setup.* /tmp/openclaw-update.log /var/run/openclaw*.pid") + sys.exec("rm -f /tmp/openclaw-setup.* /tmp/openclaw-update.log /tmp/openclaw-plugin-upgrade.* /var/run/openclaw*.pid") + -- 清理 LuCI 缓存 + sys.exec("rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null") -- 删除 openclaw 系统用户 sys.exec("sed -i '/^openclaw:/d' /etc/passwd /etc/shadow /etc/group 2>/dev/null") http.prepare_content("application/json") http.write_json({ status = "ok", - message = "运行环境已卸载。已清理: Node.js 运行环境 (/opt/openclaw)、旧数据目录 (/root/.openclaw)、临时文件。" + message = "运行环境已卸载。已清理: Node.js 运行环境 (/opt/openclaw)、所有插件 (qqbot/飞书等)、旧数据目录 (/root/.openclaw)、临时文件、LuCI 缓存。" }) end @@ -801,3 +806,74 @@ function action_backup() http.write_json({ status = "error", message = "未知备份操作: " .. action }) end end + +-- ═══════════════════════════════════════════ +-- 系统配置检测 API (安装前检测) +-- 检测内存和磁盘空间是否满足最低要求 +-- 要求: 内存 > 1GB, 磁盘可用空间 > 1.5GB +-- ═══════════════════════════════════════════ +function action_check_system() + local http = require "luci.http" + local sys = require "luci.sys" + + -- 最低要求配置 + local MIN_MEMORY_MB = 1024 -- 1GB + local MIN_DISK_MB = 1536 -- 1.5GB + + local result = { + memory_mb = 0, + memory_ok = false, + disk_mb = 0, + disk_ok = false, + disk_path = "", + pass = false, + message = "" + } + + -- 检测总内存 (从 /proc/meminfo 读取 MemTotal) + local meminfo = io.open("/proc/meminfo", "r") + if meminfo then + for line in meminfo:lines() do + local mem_total = line:match("MemTotal:%s+(%d+)%s+kB") + if mem_total then + result.memory_mb = math.floor(tonumber(mem_total) / 1024) + break + end + end + meminfo:close() + end + result.memory_ok = result.memory_mb >= MIN_MEMORY_MB + + -- 检测磁盘可用空间 + -- 优先检测 /opt 所在分区,如果 /opt 不存在则检测 /overlay 或 / + local disk_paths = {"/opt", "/overlay", "/"} + for _, path in ipairs(disk_paths) do + local df_output = sys.exec("df -m " .. path .. " 2>/dev/null | tail -1 | awk '{print $4}'"):gsub("%s+", "") + if df_output and df_output ~= "" and tonumber(df_output) then + result.disk_mb = tonumber(df_output) + result.disk_path = path + break + end + end + result.disk_ok = result.disk_mb >= MIN_DISK_MB + + -- 综合判断 + result.pass = result.memory_ok and result.disk_ok + + -- 生成提示信息 + if result.pass then + result.message = "系统配置检测通过" + else + local issues = {} + if not result.memory_ok then + table.insert(issues, string.format("内存不足: 当前 %d MB,需要至少 %d MB", result.memory_mb, MIN_MEMORY_MB)) + end + if not result.disk_ok then + table.insert(issues, string.format("磁盘空间不足: 当前 %d MB 可用,需要至少 %d MB", result.disk_mb, MIN_DISK_MB)) + end + result.message = table.concat(issues, ";") + end + + http.prepare_content("application/json") + http.write_json(result) +end diff --git a/luasrc/model/cbi/openclaw/basic.lua b/luasrc/model/cbi/openclaw/basic.lua index 66af6a3..cd0b87d 100644 --- a/luasrc/model/cbi/openclaw/basic.lua +++ b/luasrc/model/cbi/openclaw/basic.lua @@ -27,6 +27,7 @@ act.cfgvalue = function(self, section) local uninstall_url = luci.dispatcher.build_url("admin", "services", "openclaw", "uninstall") local plugin_upgrade_url = luci.dispatcher.build_url("admin", "services", "openclaw", "plugin_upgrade") local plugin_upgrade_log_url = luci.dispatcher.build_url("admin", "services", "openclaw", "plugin_upgrade_log") + local check_system_url = luci.dispatcher.build_url("admin", "services", "openclaw", "check_system") local html = {} -- 按钮区域 @@ -91,30 +92,64 @@ act.cfgvalue = function(self, section) html[#html+1] = 'document.getElementById("oc-setup-dialog").style.display="none";' html[#html+1] = '}' html[#html+1] = 'function ocConfirmSetup(){' - html[#html+1] = 'ocCloseSetupDialog();' - html[#html+1] = 'var radios=document.getElementsByName("oc-ver-choice");' - html[#html+1] = 'var choice="stable";' - html[#html+1] = 'for(var i=0;i/dev/null 2>&1; then + ss -tulnp 2>/dev/null | grep -q ":${p} " || return 0 + else + netstat -tulnp 2>/dev/null | grep -q ":${p} " || return 0 + fi + + # 端口被占用,尝试清理 + for occ_pid in $(pgrep -f "openclaw-gateway" 2>/dev/null); do + kill "$occ_pid" 2>/dev/null + done + while [ $i -lt $max_wait ]; do if command -v ss >/dev/null 2>&1; then ss -tulnp 2>/dev/null | grep -q ":${p} " || return 0 else netstat -tulnp 2>/dev/null | grep -q ":${p} " || return 0 fi - # 尝试杀掉占用端口的 gateway 进程 - if [ $i -eq 0 ]; then - local occ_pid - for occ_pid in $(pgrep -f "openclaw-gateway" 2>/dev/null); do - kill "$occ_pid" 2>/dev/null - done - fi - sleep 1 + usleep 200000 2>/dev/null || sleep 0.2 i=$((i + 1)) done + # 最后手段: SIGKILL local port_pid="" if command -v ss >/dev/null 2>&1; then @@ -283,7 +292,7 @@ _ensure_port_free() { else port_pid=$(netstat -tulnp 2>/dev/null | grep ":${p} " | sed -n 's|.* \([0-9]*\)/.*|\1|p' | head -1) fi - [ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null && sleep 1 + [ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null && usleep 300000 2>/dev/null return 0 } _ensure_port_free "$port" @@ -350,36 +359,43 @@ stop_service() { local port port=$(uci -q get openclaw.main.port || echo "18789") -# 杀掉所有 openclaw / openclaw-gateway 残留进程 -# (排除 web-pty.js 和 oc-config.sh,它们由 pty 实例管理) -local pid -for pid in $(pgrep -f "openclaw-gateway" 2>/dev/null) \ - $(pgrep -f "openclaw.*gateway.*run" 2>/dev/null); do - kill "$pid" 2>/dev/null +# v2026.3.14 优化: 快速终止进程树,减少等待时间 +# 1) 先获取所有相关 PID +local gw_pids="" +gw_pids=$(pgrep -f "openclaw-gateway" 2>/dev/null) +gw_pids="$gw_pids $(pgrep -f "openclaw.*gateway.*run" 2>/dev/null)" + +# 2) 同时发送 SIGTERM 给所有进程 +for pid in $gw_pids; do + [ -n "$pid" ] && kill "$pid" 2>/dev/null done -# 等待端口真正释放 (最多 8 秒) +# 3) 快速轮询等待端口释放 (200ms 间隔,最多 3 秒) local wait_count=0 -while [ $wait_count -lt 8 ]; do +local max_wait=15 # 15 * 0.2 = 3 秒 +while [ $wait_count -lt $max_wait ]; do if command -v ss >/dev/null 2>&1; then ss -tulnp 2>/dev/null | grep -q ":${port} " || break else netstat -tulnp 2>/dev/null | grep -q ":${port} " || break fi - sleep 1 + usleep 200000 2>/dev/null || sleep 0.2 wait_count=$((wait_count + 1)) done -# 如果端口仍被占用,强制杀掉占用者 -if [ $wait_count -ge 8 ]; then +# 4) 如果端口仍被占用,强制 SIGKILL +if [ $wait_count -ge $max_wait ]; then local port_pid="" if command -v ss >/dev/null 2>&1; then port_pid=$(ss -tulnp 2>/dev/null | grep ":${port} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1) else port_pid=$(netstat -tulnp 2>/dev/null | grep ":${port} " | sed -n 's|.* \([0-9]*\)/.*|\1|p' | head -1) fi - [ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null - sleep 1 + if [ -n "$port_pid" ]; then + kill -9 "$port_pid" 2>/dev/null + # 等待内核回收 (缩短到 0.5 秒) + usleep 500000 2>/dev/null || sleep 0.5 + fi fi } @@ -390,8 +406,8 @@ procd_add_reload_trigger "openclaw" reload_service() { stop -# stop_service 已确保端口释放,但额外等待 1 秒让内核回收 -sleep 1 +# v2026.3.14: stop_service 已优化等待逻辑,缩短额外等待时间 +usleep 300000 2>/dev/null || sleep 0.3 start } @@ -407,47 +423,45 @@ restart_gateway() { local port port=$(uci -q get openclaw.main.port || echo "18789") -# ── 第一步: kill 监听端口的 gateway 子进程 (openclaw-gateway) ── -# openclaw 启动后会 fork 出 openclaw-gateway 子进程实际监听端口 -# 必须先杀子进程释放端口,否则 procd respawn 的新实例会因端口冲突而崩溃 -local port_pid="" +# v2026.3.14 优化: 快速终止并重启 +# 1) 同时获取端口 PID 和 procd 管理的 PID +local port_pid="" gw_pid="" if command -v ss >/dev/null 2>&1; then port_pid=$(ss -tulnp 2>/dev/null | grep ":${port} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1) else port_pid=$(netstat -tulnp 2>/dev/null | grep ":${port} " | sed -n 's|.* \([0-9]*\)/.*|\1|p' | head -1) fi -[ -n "$port_pid" ] && kill "$port_pid" 2>/dev/null - -# ── 第二步: kill procd 管理的 gateway 主进程 (openclaw) ── -# procd 追踪的是主进程 PID,kill 它才能触发 respawn -local gw_pid="" gw_pid=$(ubus call service list '{"name":"openclaw"}' 2>/dev/null | \ jsonfilter -e '$.openclaw.instances.gateway.pid' 2>/dev/null) || true -if [ -n "$gw_pid" ] && kill -0 "$gw_pid" 2>/dev/null; then - kill "$gw_pid" 2>/dev/null -fi +# 2) 同时发送 SIGTERM 给所有相关进程 +[ -n "$port_pid" ] && kill "$port_pid" 2>/dev/null +[ -n "$gw_pid" ] && kill -0 "$gw_pid" 2>/dev/null && kill "$gw_pid" 2>/dev/null -# ── 第三步: 兜底 — kill 所有 openclaw gateway 相关残留进程 ── -# 避免任何残留进程占据端口 -sleep 1 +# 3) 兜底: kill 所有残留进程 for pid in $(pgrep -f "openclaw-gateway" 2>/dev/null) $(pgrep -f "openclaw.*gateway.*run" 2>/dev/null); do kill "$pid" 2>/dev/null done -# ── 第四步: 等待端口真正释放 (最多 5 秒) ── +# 4) 快速轮询等待端口释放 (200ms 间隔,最多 2 秒) local wait_count=0 -while [ $wait_count -lt 5 ]; do +while [ $wait_count -lt 10 ]; do if command -v ss >/dev/null 2>&1; then ss -tulnp 2>/dev/null | grep -q ":${port} " || break else netstat -tulnp 2>/dev/null | grep -q ":${port} " || break fi - sleep 1 + usleep 200000 2>/dev/null || sleep 0.2 wait_count=$((wait_count + 1)) done -# ── 第五步: 如果 procd 中没有 gateway 服务注册 (首次/崩溃),调用 start ── +# 5) 如果端口仍被占用,强制 SIGKILL +if [ $wait_count -ge 10 ]; then + [ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null + usleep 300000 2>/dev/null || sleep 0.3 +fi + +# 6) 如果 procd 中没有 gateway 服务注册,调用 start if [ -z "$gw_pid" ]; then /etc/init.d/openclaw start >/dev/null 2>&1 fi diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env index 764618e..621a092 100755 --- a/root/usr/bin/openclaw-env +++ b/root/usr/bin/openclaw-env @@ -452,13 +452,8 @@ do_setup() { echo "╔══════════════════════════════════════════════════════════════╗" echo "║ ✅ 安装完成! ║" echo "║ ║" - echo "║ 下一步: ║" - echo "║ uci set openclaw.main.enabled=1 ║" - echo "║ uci commit openclaw ║" - echo "║ /etc/init.d/openclaw enable ║" - echo "║ /etc/init.d/openclaw start ║" - echo "║ ║" - echo "║ 或在 LuCI → 服务 → OpenClaw 中启用 ║" + echo "║ 如通过 LuCI 安装,服务已自动启用并启动。 ║" + echo "║ 如通过命令行安装,请在 LuCI → 服务 → OpenClaw 中启用。 ║" echo "╚══════════════════════════════════════════════════════════════╝" } @@ -724,13 +719,7 @@ do_setup_offline() { echo "╔══════════════════════════════════════════════════════════════╗" echo "║ ✅ 离线安装完成! ║" echo "║ ║" - echo "║ 下一步: ║" - echo "║ uci set openclaw.main.enabled=1 ║" - echo "║ uci commit openclaw ║" - echo "║ /etc/init.d/openclaw enable ║" - echo "║ /etc/init.d/openclaw start ║" - echo "║ ║" - echo "║ 或在 LuCI → 服务 → OpenClaw 中启用 ║" + echo "║ 请在 LuCI → 服务 → OpenClaw 中启用服务。 ║" echo "╚══════════════════════════════════════════════════════════════╝" } diff --git a/root/usr/share/openclaw/oc-config.sh b/root/usr/share/openclaw/oc-config.sh index f6d280b..af9d5dd 100755 --- a/root/usr/share/openclaw/oc-config.sh +++ b/root/usr/share/openclaw/oc-config.sh @@ -2059,7 +2059,69 @@ reset_to_defaults() { if [ "$confirm" = "yes" ]; then echo "" echo -e " ${CYAN}正在清除渠道配置...${NC}" + # 清除 openclaw.json 中的 channels 配置 oc_cmd config unset channels >/dev/null 2>&1 || true + + # v2026.3.14: 同时清除 plugins 中与渠道相关的配置 + # 防止重置后插件配置残留导致状态不一致 + if [ -f "$CONFIG_FILE" ] && [ -x "$NODE_BIN" ]; then + "$NODE_BIN" -e " + const fs=require('fs'); + try{ + const d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8')); + let modified=false; + + // 清除 plugins.entries 中与消息渠道相关的插件 + if(d.plugins && d.plugins.entries){ + const channelPlugins=['openclaw-qqbot','@tencent-connect/openclaw-qqbot','openclaw-lark','@larksuite/openclaw-lark']; + channelPlugins.forEach(p=>{ + if(d.plugins.entries[p]){ + delete d.plugins.entries[p]; + modified=true; + } + }); + } + + // 清除 plugins.allow 中的渠道插件 + if(Array.isArray(d.plugins && d.plugins.allow)){ + const beforeLen=d.plugins.allow.length; + d.plugins.allow=d.plugins.allow.filter(p=> + !p.includes('qqbot') && + !p.includes('lark') && + !p.includes('telegram') && + !p.includes('discord') && + !p.includes('slack') && + !p.includes('whatsapp') + ); + if(d.plugins.allow.length!==beforeLen)modified=true; + } + + if(modified){ + fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2)); + console.log('CLEANED'); + } + }catch(e){} + " 2>/dev/null + chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true + fi + + # 清除飞书扩展目录中的敏感数据 (保留插件本体) + local feishu_ext_dir="${OC_STATE_DIR}/extensions/openclaw-lark" + if [ -d "$feishu_ext_dir" ]; then + # 只清除配置文件,保留插件代码 + rm -f "${feishu_ext_dir}/.credentials"* 2>/dev/null + rm -f "${feishu_ext_dir}/config.json" 2>/dev/null + rm -rf "${feishu_ext_dir}/.cache" 2>/dev/null + echo -e " ${CYAN}已清理飞书插件缓存数据${NC}" + fi + + # 清除 QQ 机器人扩展目录中的敏感数据 + local qqbot_ext_dir="${OC_STATE_DIR}/extensions/openclaw-qqbot" + if [ -d "$qqbot_ext_dir" ]; then + rm -f "${qqbot_ext_dir}/credentials"* 2>/dev/null + rm -f "${qqbot_ext_dir}/config.json" 2>/dev/null + fi + echo -e " ${GREEN}✅ 渠道配置已清除${NC}" echo -e " ${YELLOW}请通过菜单 [4] 重新配置消息渠道${NC}" ask_restart