mirror of
https://github.com/hotwa/luci-app-openclaw.git
synced 2026-03-30 20:25:44 +00:00
fix/feat: update luci configuration pages, init scripts, and env setup tools
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<radios.length;i++){if(radios[i].checked){choice=radios[i].value;break;}}'
|
||||
html[#html+1] = 'var verParam=(choice==="stable")?"stable":"latest";'
|
||||
html[#html+1] = 'ocSetup(verParam);'
|
||||
html[#html+1] = '}'
|
||||
|
||||
-- 安装运行环境 (带实时日志)
|
||||
html[#html+1] = 'function ocSetup(version){'
|
||||
html[#html+1] = 'var btn=document.getElementById("btn-setup");'
|
||||
html[#html+1] = 'btn.disabled=true;btn.textContent="⏳ 检测系统配置...";'
|
||||
html[#html+1] = '(new XHR()).get("' .. check_system_url .. '",null,function(x){'
|
||||
html[#html+1] = 'try{'
|
||||
html[#html+1] = 'var r=JSON.parse(x.responseText);'
|
||||
html[#html+1] = 'var panel=document.getElementById("setup-log-panel");'
|
||||
html[#html+1] = 'var logEl=document.getElementById("setup-log-content");'
|
||||
html[#html+1] = 'var titleEl=document.getElementById("setup-log-title");'
|
||||
html[#html+1] = 'var statusEl=document.getElementById("setup-log-status");'
|
||||
html[#html+1] = 'var resultEl=document.getElementById("setup-log-result");'
|
||||
html[#html+1] = 'var actionEl=document.getElementById("action-result");'
|
||||
html[#html+1] = 'btn.disabled=true;btn.textContent="⏳ 安装中...";'
|
||||
html[#html+1] = 'actionEl.textContent="";'
|
||||
html[#html+1] = 'panel.style.display="block";'
|
||||
html[#html+1] = 'logEl.textContent="正在启动安装 ("+((version==="stable")?"稳定版":"最新版")+")...\\n";'
|
||||
html[#html+1] = 'titleEl.textContent="📋 安装日志";'
|
||||
html[#html+1] = 'statusEl.innerHTML="<span style=\\"color:#7aa2f7;\\">⏳ 安装进行中...</span>";'
|
||||
html[#html+1] = 'resultEl.style.display="none";'
|
||||
html[#html+1] = 'titleEl.textContent="📋 安装日志";'
|
||||
html[#html+1] = 'logEl.textContent="";'
|
||||
html[#html+1] = 'logEl.textContent+="════════════════════════════════════════\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="🔍 系统配置检测\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="════════════════════════════════════════\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="内存: "+r.memory_mb+" MB (需要 ≥ 1024 MB) — "+(r.memory_ok?"✅ 通过":"❌ 不达标")+"\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="磁盘: "+r.disk_mb+" MB 可用 (需要 ≥ 1536 MB) — "+(r.disk_ok?"✅ 通过":"❌ 不达标")+"\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="\\n";'
|
||||
html[#html+1] = 'if(!r.pass){'
|
||||
html[#html+1] = 'ocCloseSetupDialog();'
|
||||
html[#html+1] = 'btn.disabled=false;btn.textContent="📦 安装运行环境";'
|
||||
html[#html+1] = 'statusEl.innerHTML="<span style=\\"color:#cf222e;\\">❌ 系统配置不满足要求</span>";'
|
||||
html[#html+1] = 'logEl.textContent+="❌ 系统配置不满足要求,安装已终止\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="💡 请升级硬件配置或清理磁盘空间后重试\\n";'
|
||||
html[#html+1] = 'resultEl.style.display="block";'
|
||||
html[#html+1] = 'resultEl.innerHTML="<div style=\\"border:1px solid #f5c6cb;background:#ffeef0;padding:12px 16px;border-radius:6px;\\">"+'
|
||||
html[#html+1] = '"<strong style=\\"color:#cf222e;font-size:14px;\\">❌ 系统配置不满足要求</strong><br/>"+'
|
||||
html[#html+1] = '"<div style=\\"margin-top:8px;font-size:12px;color:#666;\\">💡 请升级硬件配置或清理磁盘空间后重试。</div></div>";'
|
||||
html[#html+1] = 'return;'
|
||||
html[#html+1] = '}'
|
||||
html[#html+1] = 'statusEl.innerHTML="<span style=\\"color:#7aa2f7;\\">⏳ 安装进行中...</span>";'
|
||||
html[#html+1] = 'logEl.textContent+="✅ 系统配置检测通过,开始安装...\\n\\n";'
|
||||
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<radios.length;i++){if(radios[i].checked){choice=radios[i].value;break;}}'
|
||||
html[#html+1] = 'var verParam=(choice==="stable")?"stable":"latest";'
|
||||
html[#html+1] = 'ocSetup(verParam,r.memory_mb,r.disk_mb);'
|
||||
html[#html+1] = '}catch(e){'
|
||||
html[#html+1] = 'ocCloseSetupDialog();'
|
||||
html[#html+1] = 'btn.disabled=false;btn.textContent="📦 安装运行环境";'
|
||||
html[#html+1] = 'alert("系统检测失败,请重试");'
|
||||
html[#html+1] = '}});'
|
||||
html[#html+1] = '}'
|
||||
|
||||
-- 安装运行环境 (带实时日志)
|
||||
html[#html+1] = 'function ocSetup(version,mem_mb,disk_mb){'
|
||||
html[#html+1] = 'var btn=document.getElementById("btn-setup");'
|
||||
html[#html+1] = 'var logEl=document.getElementById("setup-log-content");'
|
||||
html[#html+1] = 'btn.disabled=true;btn.textContent="⏳ 安装中...";'
|
||||
html[#html+1] = 'logEl.textContent+="════════════════════════════════════════\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="📦 安装运行环境 ("+((version==="stable")?"稳定版":"最新版")+")\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="════════════════════════════════════════\\n";'
|
||||
html[#html+1] = 'logEl.textContent+="正在启动安装...\\n";'
|
||||
html[#html+1] = '(new XHR()).get("' .. ctl_url .. '?action=setup&version="+encodeURIComponent(version),null,function(x){'
|
||||
html[#html+1] = 'try{JSON.parse(x.responseText);}catch(e){}'
|
||||
html[#html+1] = 'ocPollSetupLog();'
|
||||
@@ -122,24 +157,30 @@ act.cfgvalue = function(self, section)
|
||||
html[#html+1] = '}'
|
||||
|
||||
-- 轮询安装日志
|
||||
html[#html+1] = 'var _lastLogLen=0;'
|
||||
html[#html+1] = 'function ocPollSetupLog(){'
|
||||
html[#html+1] = 'if(_setupTimer)clearInterval(_setupTimer);'
|
||||
html[#html+1] = '_lastLogLen=0;'
|
||||
html[#html+1] = '_setupTimer=setInterval(function(){'
|
||||
html[#html+1] = '(new XHR()).get("' .. log_url .. '",null,function(x){'
|
||||
html[#html+1] = 'try{'
|
||||
html[#html+1] = 'var r=JSON.parse(x.responseText);'
|
||||
html[#html+1] = 'var logEl=document.getElementById("setup-log-content");'
|
||||
html[#html+1] = 'var statusEl=document.getElementById("setup-log-status");'
|
||||
html[#html+1] = 'if(r.log)logEl.textContent=r.log;'
|
||||
html[#html+1] = 'if(r.log&&r.log.length>_lastLogLen){'
|
||||
html[#html+1] = 'var newLog=r.log.substring(_lastLogLen);'
|
||||
html[#html+1] = 'logEl.textContent+=newLog;'
|
||||
html[#html+1] = '_lastLogLen=r.log.length;'
|
||||
html[#html+1] = '}'
|
||||
html[#html+1] = 'logEl.scrollTop=logEl.scrollHeight;'
|
||||
html[#html+1] = 'if(r.state==="running"){'
|
||||
html[#html+1] = 'statusEl.innerHTML="<span style=\\"color:#7aa2f7;\\">⏳ 安装进行中...</span>";'
|
||||
html[#html+1] = '}else if(r.state==="success"){'
|
||||
html[#html+1] = 'clearInterval(_setupTimer);_setupTimer=null;'
|
||||
html[#html+1] = 'ocSetupDone(true,r.log);'
|
||||
html[#html+1] = 'ocSetupDone(true,logEl.textContent);'
|
||||
html[#html+1] = '}else if(r.state==="failed"){'
|
||||
html[#html+1] = 'clearInterval(_setupTimer);_setupTimer=null;'
|
||||
html[#html+1] = 'ocSetupDone(false,r.log);'
|
||||
html[#html+1] = 'ocSetupDone(false,logEl.textContent);'
|
||||
html[#html+1] = '}'
|
||||
html[#html+1] = '}catch(e){}'
|
||||
html[#html+1] = '});'
|
||||
|
||||
@@ -258,24 +258,33 @@ all) gw_bind="custom" ;; # custom = 0.0.0.0
|
||||
esac
|
||||
|
||||
# 确保网关端口未被残留进程占用 (防止 restart 时 crash loop)
|
||||
# v2026.3.14 优化: 快速轮询 + 批量 kill
|
||||
_ensure_port_free() {
|
||||
local p="$1" max_wait="${2:-5}" i=0
|
||||
local p="$1"
|
||||
local i=0 max_wait=10 # 10 * 0.2 = 2 秒
|
||||
|
||||
# 先检查端口是否已被占用
|
||||
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
|
||||
|
||||
# 端口被占用,尝试清理
|
||||
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
|
||||
|
||||
@@ -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 "╚══════════════════════════════════════════════════════════════╝"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user