Files
luci-app-openclaw/luasrc/controller/openclaw.lua

385 lines
14 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- luci-app-openclaw — LuCI Controller
module("luci.controller.openclaw", package.seeall)
-- 公共辅助: 获取 OpenClaw 版本号
local function get_openclaw_version()
local sys = require "luci.sys"
local ver = sys.exec("[ -x /opt/openclaw/node/bin/node ] && for d in /opt/openclaw/global/lib/node_modules/openclaw /opt/openclaw/global/node_modules/openclaw /opt/openclaw/global/*/node_modules/openclaw; do [ -f \"$d/openclaw.mjs\" ] && /opt/openclaw/node/bin/node \"$d/openclaw.mjs\" --version 2>/dev/null && break; [ -f \"$d/dist/cli.js\" ] && /opt/openclaw/node/bin/node \"$d/dist/cli.js\" --version 2>/dev/null && break; done"):gsub("%s+", "")
return ver
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
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,
pty_running = false,
pid = "",
memory_kb = 0,
uptime = "",
node_version = "",
openclaw_version = "",
}
-- 检查 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
-- 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
-- PID 和内存
if result.gateway_running then
local pid = sys.exec("netstat -tlnp 2>/dev/null | grep ':" .. port .. " ' | head -1 | sed 's|.* \\([0-9]*\\)/.*|\\1|'"):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")
elseif action == "restart" then
sys.exec("/etc/init.d/openclaw restart >/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 -einit_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"
-- 当前版本
local current = get_openclaw_version()
-- 最新版本 (从 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
http.prepare_content("application/json")
http.write_json({
status = "ok",
current = current,
latest = latest,
has_update = 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