mirror of
https://github.com/hotwa/luci-app-openclaw.git
synced 2026-03-30 20:25:44 +00:00
596 lines
22 KiB
Lua
596 lines
22 KiB
Lua
-- luci-app-openclaw — LuCI Controller
|
||
module("luci.controller.openclaw", package.seeall)
|
||
|
||
-- 公共辅助: 获取 OpenClaw 版本号
|
||
local function get_openclaw_version()
|
||
local sys = require "luci.sys"
|
||
-- 优先从 package.json 读取版本号 (轻量),避免每次启动 node 进程
|
||
local dirs = {
|
||
"/opt/openclaw/global/lib/node_modules/openclaw",
|
||
"/opt/openclaw/global/node_modules/openclaw",
|
||
}
|
||
-- pnpm 版本目录
|
||
local pnpm_glob = sys.exec("ls -d /opt/openclaw/global/*/node_modules/openclaw 2>/dev/null"):gsub("%s+$", "")
|
||
for d in pnpm_glob:gmatch("[^\n]+") do
|
||
dirs[#dirs + 1] = d
|
||
end
|
||
for _, d in ipairs(dirs) do
|
||
local pkg = d .. "/package.json"
|
||
local f = io.open(pkg, "r")
|
||
if f then
|
||
local content = f:read("*a")
|
||
f:close()
|
||
local ver = content:match('"version"%s*:%s*"([^"]+)"')
|
||
if ver and ver ~= "" then return ver end
|
||
end
|
||
end
|
||
return ""
|
||
end
|
||
|
||
function index()
|
||
-- 主入口: 服务 → OpenClaw (🧠 作为菜单图标)
|
||
local page = entry({"admin", "services", "openclaw"}, alias("admin", "services", "openclaw", "basic"), _("OpenClaw"), 90)
|
||
page.dependent = false
|
||
|
||
-- 基本设置 (CBI)
|
||
entry({"admin", "services", "openclaw", "basic"}, cbi("openclaw/basic"), _("基本设置"), 10).leaf = true
|
||
|
||
-- 配置管理 (View — 嵌入 oc-config Web 终端)
|
||
entry({"admin", "services", "openclaw", "advanced"}, template("openclaw/advanced"), _("配置管理"), 20).leaf = true
|
||
|
||
-- Web 控制台 (View — 嵌入 OpenClaw Web UI)
|
||
entry({"admin", "services", "openclaw", "console"}, template("openclaw/console"), _("Web 控制台"), 30).leaf = true
|
||
|
||
-- 状态 API (AJAX 接口, 供前端 XHR 调用)
|
||
entry({"admin", "services", "openclaw", "status_api"}, call("action_status"), nil).leaf = true
|
||
|
||
-- 服务控制 API
|
||
entry({"admin", "services", "openclaw", "service_ctl"}, call("action_service_ctl"), nil).leaf = true
|
||
|
||
-- 安装/升级日志 API (轮询)
|
||
entry({"admin", "services", "openclaw", "setup_log"}, call("action_setup_log"), nil).leaf = true
|
||
|
||
-- 版本检查 API
|
||
entry({"admin", "services", "openclaw", "check_update"}, call("action_check_update"), nil).leaf = true
|
||
|
||
-- 执行升级 API
|
||
entry({"admin", "services", "openclaw", "do_update"}, call("action_do_update"), nil).leaf = true
|
||
|
||
-- 升级日志 API (轮询)
|
||
entry({"admin", "services", "openclaw", "upgrade_log"}, call("action_upgrade_log"), nil).leaf = true
|
||
|
||
-- 卸载运行环境 API
|
||
entry({"admin", "services", "openclaw", "uninstall"}, call("action_uninstall"), nil).leaf = true
|
||
|
||
-- 获取网关 Token API (仅认证用户可访问)
|
||
entry({"admin", "services", "openclaw", "get_token"}, call("action_get_token"), nil).leaf = true
|
||
|
||
-- 插件升级 API
|
||
entry({"admin", "services", "openclaw", "plugin_upgrade"}, call("action_plugin_upgrade"), nil).leaf = true
|
||
|
||
-- 插件升级日志 API (轮询)
|
||
entry({"admin", "services", "openclaw", "plugin_upgrade_log"}, call("action_plugin_upgrade_log"), nil).leaf = true
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 状态查询 API: 返回 JSON
|
||
-- ═══════════════════════════════════════════
|
||
function action_status()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
local uci = require "luci.model.uci".cursor()
|
||
|
||
local port = uci:get("openclaw", "main", "port") or "18789"
|
||
local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
|
||
local enabled = uci:get("openclaw", "main", "enabled") or "0"
|
||
|
||
-- 验证端口值为纯数字,防止命令注入
|
||
if not port:match("^%d+$") then port = "18789" end
|
||
if not pty_port:match("^%d+$") then pty_port = "18793" end
|
||
|
||
local result = {
|
||
enabled = enabled,
|
||
port = port,
|
||
pty_port = pty_port,
|
||
gateway_running = false,
|
||
gateway_starting = false,
|
||
pty_running = false,
|
||
pid = "",
|
||
memory_kb = 0,
|
||
uptime = "",
|
||
node_version = "",
|
||
openclaw_version = "",
|
||
plugin_version = "",
|
||
}
|
||
|
||
-- 插件版本
|
||
local pvf = io.open("/usr/share/openclaw/VERSION", "r")
|
||
if pvf then
|
||
result.plugin_version = pvf:read("*a"):gsub("%s+", "")
|
||
pvf:close()
|
||
end
|
||
|
||
-- 检查 Node.js
|
||
local node_bin = "/opt/openclaw/node/bin/node"
|
||
local f = io.open(node_bin, "r")
|
||
if f then
|
||
f:close()
|
||
local node_ver = sys.exec(node_bin .. " --version 2>/dev/null"):gsub("%s+", "")
|
||
result.node_version = node_ver
|
||
end
|
||
|
||
-- 检查 OpenClaw 版本
|
||
local oc_ver = get_openclaw_version()
|
||
if oc_ver and oc_ver ~= "" then
|
||
result.openclaw_version = "v" .. oc_ver
|
||
end
|
||
|
||
-- 网关端口检查
|
||
local gw_check = sys.exec("netstat -tlnp 2>/dev/null | grep -c ':" .. port .. " ' || echo 0"):gsub("%s+", "")
|
||
result.gateway_running = (tonumber(gw_check) or 0) > 0
|
||
|
||
-- 如果端口未监听但 procd 进程存在,说明正在启动中 (gateway 初始化需要数分钟)
|
||
if not result.gateway_running and enabled == "1" then
|
||
local procd_pid = sys.exec("pgrep -f 'openclaw.*gateway' 2>/dev/null | head -1"):gsub("%s+", "")
|
||
if procd_pid ~= "" then
|
||
result.gateway_starting = true
|
||
end
|
||
end
|
||
|
||
-- PTY 端口检查
|
||
local pty_check = sys.exec("netstat -tlnp 2>/dev/null | grep -c ':" .. pty_port .. " ' || echo 0"):gsub("%s+", "")
|
||
result.pty_running = (tonumber(pty_check) or 0) > 0
|
||
|
||
-- 读取当前活跃模型
|
||
local config_file = "/opt/openclaw/data/.openclaw/openclaw.json"
|
||
local cf = io.open(config_file, "r")
|
||
if cf then
|
||
local content = cf:read("*a")
|
||
cf:close()
|
||
-- 简单正则提取 "primary": "xxx"
|
||
local model = content:match('"primary"%s*:%s*"([^"]+)"')
|
||
if model and model ~= "" then
|
||
result.active_model = model
|
||
end
|
||
end
|
||
|
||
-- PID 和内存
|
||
if result.gateway_running then
|
||
local pid = sys.exec("netstat -tlnp 2>/dev/null | awk '/:" .. port .. " /{split($NF,a,\"/\");print a[1];exit}'"):gsub("%s+", "")
|
||
if pid and pid ~= "" then
|
||
result.pid = pid
|
||
-- 内存 (VmRSS from /proc)
|
||
local rss = sys.exec("awk '/VmRSS/{print $2}' /proc/" .. pid .. "/status 2>/dev/null"):gsub("%s+", "")
|
||
result.memory_kb = tonumber(rss) or 0
|
||
-- 运行时间
|
||
local stat_time = sys.exec("stat -c %Y /proc/" .. pid .. " 2>/dev/null"):gsub("%s+", "")
|
||
local start_ts = tonumber(stat_time) or 0
|
||
if start_ts > 0 then
|
||
local uptime_s = os.time() - start_ts
|
||
local hours = math.floor(uptime_s / 3600)
|
||
local mins = math.floor((uptime_s % 3600) / 60)
|
||
local secs = uptime_s % 60
|
||
if hours > 0 then
|
||
result.uptime = string.format("%dh %dm %ds", hours, mins, secs)
|
||
elseif mins > 0 then
|
||
result.uptime = string.format("%dm %ds", mins, secs)
|
||
else
|
||
result.uptime = string.format("%ds", secs)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json(result)
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 服务控制 API: start/stop/restart/setup
|
||
-- ═══════════════════════════════════════════
|
||
function action_service_ctl()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
local action = http.formvalue("action") or ""
|
||
|
||
if action == "start" then
|
||
sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &")
|
||
elseif action == "stop" then
|
||
sys.exec("/etc/init.d/openclaw stop >/dev/null 2>&1")
|
||
-- 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 &")
|
||
elseif action == "enable" then
|
||
sys.exec("/etc/init.d/openclaw enable 2>/dev/null")
|
||
elseif action == "disable" then
|
||
sys.exec("/etc/init.d/openclaw disable 2>/dev/null")
|
||
elseif action == "setup" then
|
||
-- 先清理旧日志和状态
|
||
sys.exec("rm -f /tmp/openclaw-setup.log /tmp/openclaw-setup.pid /tmp/openclaw-setup.exit")
|
||
-- 获取用户选择的版本 (stable=指定版本, latest=最新版)
|
||
local version = http.formvalue("version") or ""
|
||
local env_prefix = ""
|
||
if version == "stable" then
|
||
-- 稳定版: 读取 openclaw-env 中定义的 OC_TESTED_VERSION
|
||
local tested_ver = sys.exec("grep '^OC_TESTED_VERSION=' /usr/bin/openclaw-env 2>/dev/null | cut -d'\"' -f2"):gsub("%s+", "")
|
||
if tested_ver ~= "" then
|
||
env_prefix = "OC_VERSION=" .. tested_ver .. " "
|
||
end
|
||
elseif version ~= "" and version ~= "latest" then
|
||
-- 校验版本号格式 (仅允许数字、点、横线、字母)
|
||
if version:match("^[%d%.%-a-zA-Z]+$") then
|
||
env_prefix = "OC_VERSION=" .. version .. " "
|
||
end
|
||
end
|
||
-- 后台安装,成功后自动启用并启动服务
|
||
-- 注: openclaw-env 脚本有 set -e,init_openclaw 中的非关键失败不应阻止启动
|
||
sys.exec("( " .. env_prefix .. "/usr/bin/openclaw-env setup > /tmp/openclaw-setup.log 2>&1; RC=$?; echo $RC > /tmp/openclaw-setup.exit; if [ $RC -eq 0 ]; then uci set openclaw.main.enabled=1; uci commit openclaw; /etc/init.d/openclaw enable 2>/dev/null; sleep 1; /etc/init.d/openclaw start >> /tmp/openclaw-setup.log 2>&1; fi ) & echo $! > /tmp/openclaw-setup.pid")
|
||
http.prepare_content("application/json")
|
||
http.write_json({ status = "ok", message = "安装已启动,请查看安装日志..." })
|
||
return
|
||
else
|
||
http.prepare_content("application/json")
|
||
http.write_json({ status = "error", message = "未知操作: " .. action })
|
||
return
|
||
end
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({ status = "ok", action = action })
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 安装日志轮询 API
|
||
-- ═══════════════════════════════════════════
|
||
function action_setup_log()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
-- 读取日志内容
|
||
local log = ""
|
||
local f = io.open("/tmp/openclaw-setup.log", "r")
|
||
if f then
|
||
log = f:read("*a") or ""
|
||
f:close()
|
||
end
|
||
|
||
-- 检查进程是否还在运行
|
||
local running = false
|
||
local pid_file = io.open("/tmp/openclaw-setup.pid", "r")
|
||
if pid_file then
|
||
local pid = pid_file:read("*a"):gsub("%s+", "")
|
||
pid_file:close()
|
||
if pid ~= "" then
|
||
local check = sys.exec("kill -0 " .. pid .. " 2>/dev/null && echo yes || echo no"):gsub("%s+", "")
|
||
running = (check == "yes")
|
||
end
|
||
end
|
||
|
||
-- 读取退出码
|
||
local exit_code = -1
|
||
if not running then
|
||
local exit_file = io.open("/tmp/openclaw-setup.exit", "r")
|
||
if exit_file then
|
||
local code = exit_file:read("*a"):gsub("%s+", "")
|
||
exit_file:close()
|
||
exit_code = tonumber(code) or -1
|
||
end
|
||
end
|
||
|
||
-- 判断状态
|
||
local state = "idle"
|
||
if running then
|
||
state = "running"
|
||
elseif exit_code == 0 then
|
||
state = "success"
|
||
elseif exit_code > 0 then
|
||
state = "failed"
|
||
end
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({
|
||
state = state,
|
||
exit_code = exit_code,
|
||
log = log
|
||
})
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 版本检查 API
|
||
-- ═══════════════════════════════════════════
|
||
function action_check_update()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
-- 当前 OpenClaw 版本
|
||
local current = get_openclaw_version()
|
||
|
||
-- 最新 OpenClaw 版本 (从 npm registry 查询)
|
||
local latest = sys.exec("PATH=/opt/openclaw/node/bin:/opt/openclaw/global/bin:$PATH npm view openclaw version 2>/dev/null"):gsub("%s+", "")
|
||
|
||
local has_update = false
|
||
if current ~= "" and latest ~= "" and current ~= latest then
|
||
has_update = true
|
||
end
|
||
|
||
-- 插件版本检查 (从 GitHub API 获取最新 release tag)
|
||
local plugin_current = ""
|
||
local pf = io.open("/usr/share/openclaw/VERSION", "r")
|
||
or io.open("/root/luci-app-openclaw/VERSION", "r")
|
||
if pf then
|
||
plugin_current = pf:read("*a"):gsub("%s+", "")
|
||
pf:close()
|
||
end
|
||
|
||
local plugin_latest = ""
|
||
local plugin_has_update = false
|
||
-- 仅在请求参数含 check_plugin=1 或 quick=1 时检查插件版本
|
||
local check_plugin = http.formvalue("check_plugin") or ""
|
||
local quick = http.formvalue("quick") or ""
|
||
if check_plugin == "1" or quick == "1" then
|
||
-- 使用 GitHub API 获取最新 release tag (轻量, 不下载任何文件)
|
||
local gh_resp = sys.exec("curl -sf --connect-timeout 5 --max-time 10 'https://api.github.com/repos/10000ge10000/luci-app-openclaw/releases/latest' 2>/dev/null | grep -o '\"tag_name\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | head -1 | cut -d'\"' -f4")
|
||
gh_resp = gh_resp:gsub("%s+", "")
|
||
if gh_resp ~= "" then
|
||
-- tag 可能是 v1.0.3 或 1.0.3
|
||
plugin_latest = gh_resp:gsub("^v", "")
|
||
end
|
||
if plugin_current ~= "" and plugin_latest ~= "" and plugin_current ~= plugin_latest then
|
||
plugin_has_update = true
|
||
end
|
||
end
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({
|
||
status = "ok",
|
||
current = current,
|
||
latest = latest,
|
||
has_update = has_update,
|
||
plugin_current = plugin_current,
|
||
plugin_latest = plugin_latest,
|
||
plugin_has_update = plugin_has_update
|
||
})
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 执行升级 API (后台执行 + 日志轮询)
|
||
-- ═══════════════════════════════════════════
|
||
function action_do_update()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
-- 清理旧日志和状态
|
||
sys.exec("rm -f /tmp/openclaw-upgrade.log /tmp/openclaw-upgrade.pid /tmp/openclaw-upgrade.exit")
|
||
|
||
-- 后台执行升级,升级完成后自动重启服务
|
||
sys.exec("( /usr/bin/openclaw-env upgrade > /tmp/openclaw-upgrade.log 2>&1; RC=$?; echo $RC > /tmp/openclaw-upgrade.exit; if [ $RC -eq 0 ]; then echo '' >> /tmp/openclaw-upgrade.log; echo '正在重启服务...' >> /tmp/openclaw-upgrade.log; /etc/init.d/openclaw restart >> /tmp/openclaw-upgrade.log 2>&1; echo ' [✓] 服务已重启' >> /tmp/openclaw-upgrade.log; fi ) & echo $! > /tmp/openclaw-upgrade.pid")
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({
|
||
status = "ok",
|
||
message = "升级已在后台启动,请查看升级日志..."
|
||
})
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 升级日志轮询 API
|
||
-- ═══════════════════════════════════════════
|
||
function action_upgrade_log()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
-- 读取日志内容
|
||
local log = ""
|
||
local f = io.open("/tmp/openclaw-upgrade.log", "r")
|
||
if f then
|
||
log = f:read("*a") or ""
|
||
f:close()
|
||
end
|
||
|
||
-- 检查进程是否还在运行
|
||
local running = false
|
||
local pid_file = io.open("/tmp/openclaw-upgrade.pid", "r")
|
||
if pid_file then
|
||
local pid = pid_file:read("*a"):gsub("%s+", "")
|
||
pid_file:close()
|
||
if pid ~= "" then
|
||
local check = sys.exec("kill -0 " .. pid .. " 2>/dev/null && echo yes || echo no"):gsub("%s+", "")
|
||
running = (check == "yes")
|
||
end
|
||
end
|
||
|
||
-- 读取退出码
|
||
local exit_code = -1
|
||
if not running then
|
||
local exit_file = io.open("/tmp/openclaw-upgrade.exit", "r")
|
||
if exit_file then
|
||
local code = exit_file:read("*a"):gsub("%s+", "")
|
||
exit_file:close()
|
||
exit_code = tonumber(code) or -1
|
||
end
|
||
end
|
||
|
||
-- 判断状态
|
||
local state = "idle"
|
||
if running then
|
||
state = "running"
|
||
elseif exit_code == 0 then
|
||
state = "success"
|
||
elseif exit_code > 0 then
|
||
state = "failed"
|
||
end
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({
|
||
state = state,
|
||
exit_code = exit_code,
|
||
log = log
|
||
})
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 卸载运行环境 API
|
||
-- ═══════════════════════════════════════════
|
||
function action_uninstall()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
-- 停止服务
|
||
sys.exec("/etc/init.d/openclaw stop >/dev/null 2>&1")
|
||
-- 禁用开机启动
|
||
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 运行环境
|
||
sys.exec("rm -rf /opt/openclaw")
|
||
-- 清理临时文件
|
||
sys.exec("rm -f /tmp/openclaw-setup.* /tmp/openclaw-update.log /var/run/openclaw*.pid")
|
||
-- 删除 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、OpenClaw 及相关数据已清理。"
|
||
})
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 获取 Token API
|
||
-- 仅通过 LuCI 认证后可调用,避免 Token 嵌入 HTML 源码
|
||
-- 返回网关 Token 和 PTY Token
|
||
-- ═══════════════════════════════════════════
|
||
function action_get_token()
|
||
local http = require "luci.http"
|
||
local uci = require "luci.model.uci".cursor()
|
||
local token = uci:get("openclaw", "main", "token") or ""
|
||
local pty_token = uci:get("openclaw", "main", "pty_token") or ""
|
||
http.prepare_content("application/json")
|
||
http.write_json({ token = token, pty_token = pty_token })
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 插件升级 API (后台下载 .run 并执行)
|
||
-- 参数: version — 目标版本号 (如 1.0.8)
|
||
-- ═══════════════════════════════════════════
|
||
function action_plugin_upgrade()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
local version = http.formvalue("version") or ""
|
||
if version == "" then
|
||
http.prepare_content("application/json")
|
||
http.write_json({ status = "error", message = "缺少版本号参数" })
|
||
return
|
||
end
|
||
|
||
-- 安全检查: version 只允许数字和点
|
||
if not version:match("^[%d%.]+$") then
|
||
http.prepare_content("application/json")
|
||
http.write_json({ status = "error", message = "版本号格式无效" })
|
||
return
|
||
end
|
||
|
||
-- 清理旧日志和状态
|
||
sys.exec("rm -f /tmp/openclaw-plugin-upgrade.log /tmp/openclaw-plugin-upgrade.pid /tmp/openclaw-plugin-upgrade.exit")
|
||
|
||
-- 后台执行: 下载 .run 并执行安装
|
||
local run_url = "https://github.com/10000ge10000/luci-app-openclaw/releases/download/v" .. version .. "/luci-app-openclaw_" .. version .. ".run"
|
||
-- 使用 curl 下载 (-L 跟随重定向), 然后 sh 执行
|
||
sys.exec(string.format(
|
||
"( echo '正在下载插件 v%s ...' > /tmp/openclaw-plugin-upgrade.log; " ..
|
||
"curl -sL --connect-timeout 15 --max-time 120 -o /tmp/luci-app-openclaw-update.run '%s' >> /tmp/openclaw-plugin-upgrade.log 2>&1; " ..
|
||
"RC=$?; " ..
|
||
"if [ $RC -ne 0 ]; then " ..
|
||
" echo '下载失败 (curl exit: '$RC')' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" echo '如果无法访问 GitHub,请手动下载: %s' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" echo $RC > /tmp/openclaw-plugin-upgrade.exit; " ..
|
||
"else " ..
|
||
" FSIZE=$(wc -c < /tmp/luci-app-openclaw-update.run 2>/dev/null | tr -d ' '); " ..
|
||
" echo \"下载完成 (${FSIZE} bytes)\" >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" if [ \"$FSIZE\" -lt 10000 ] 2>/dev/null; then " ..
|
||
" echo '文件过小,可能下载失败或链接无效' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" echo 1 > /tmp/openclaw-plugin-upgrade.exit; " ..
|
||
" else " ..
|
||
" echo '' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" echo '正在安装...' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" sh /tmp/luci-app-openclaw-update.run >> /tmp/openclaw-plugin-upgrade.log 2>&1; " ..
|
||
" RC2=$?; echo $RC2 > /tmp/openclaw-plugin-upgrade.exit; " ..
|
||
" if [ $RC2 -eq 0 ]; then " ..
|
||
" echo '' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" echo '✅ 插件升级完成!请刷新浏览器页面。' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" else " ..
|
||
" echo '安装执行失败 (exit: '$RC2')' >> /tmp/openclaw-plugin-upgrade.log; " ..
|
||
" fi; " ..
|
||
" fi; " ..
|
||
" rm -f /tmp/luci-app-openclaw-update.run; " ..
|
||
"fi " ..
|
||
") & echo $! > /tmp/openclaw-plugin-upgrade.pid",
|
||
version, run_url, run_url
|
||
))
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({
|
||
status = "ok",
|
||
message = "插件升级已在后台启动..."
|
||
})
|
||
end
|
||
|
||
-- ═══════════════════════════════════════════
|
||
-- 插件升级日志轮询 API
|
||
-- ═══════════════════════════════════════════
|
||
function action_plugin_upgrade_log()
|
||
local http = require "luci.http"
|
||
local sys = require "luci.sys"
|
||
|
||
local log = ""
|
||
local f = io.open("/tmp/openclaw-plugin-upgrade.log", "r")
|
||
if f then
|
||
log = f:read("*a") or ""
|
||
f:close()
|
||
end
|
||
|
||
local running = false
|
||
local pid_file = io.open("/tmp/openclaw-plugin-upgrade.pid", "r")
|
||
if pid_file then
|
||
local pid = pid_file:read("*a"):gsub("%s+", "")
|
||
pid_file:close()
|
||
if pid ~= "" then
|
||
local check = sys.exec("kill -0 " .. pid .. " 2>/dev/null && echo yes || echo no"):gsub("%s+", "")
|
||
running = (check == "yes")
|
||
end
|
||
end
|
||
|
||
local exit_code = -1
|
||
if not running then
|
||
local exit_file = io.open("/tmp/openclaw-plugin-upgrade.exit", "r")
|
||
if exit_file then
|
||
local code = exit_file:read("*a"):gsub("%s+", "")
|
||
exit_file:close()
|
||
exit_code = tonumber(code) or -1
|
||
end
|
||
end
|
||
|
||
local state = "idle"
|
||
if running then
|
||
state = "running"
|
||
elseif exit_code == 0 then
|
||
state = "success"
|
||
elseif exit_code > 0 then
|
||
state = "failed"
|
||
end
|
||
|
||
http.prepare_content("application/json")
|
||
http.write_json({
|
||
status = "ok",
|
||
log = log,
|
||
state = state,
|
||
running = running,
|
||
exit_code = exit_code
|
||
})
|
||
end
|