From d1bb7ce8e935e1200de1da6ee4543fe27dbd75e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 18:41:20 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=88=20Sync=202026-03-21=2018:41:20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-openclawmgr/Makefile | 4 +- .../luci-static/resources/openclawmgr/app.js | 2 +- .../luasrc/controller/openclawmgr.lua | 4 +- .../luasrc/model/cbi/openclawmgr/cli.lua | 43 ++-- .../luasrc/view/openclawmgr/cli.htm | 21 +- .../root/etc/init.d/openclawmgr | 11 +- .../root/usr/libexec/istorec/openclawmgr.sh | 56 ++++- .../root/usr/share/openclawmgr/oc-config.sh | 217 ------------------ .../usr/share/openclawmgr/openclawmgr-cli.sh | 160 +++++++++++++ 9 files changed, 271 insertions(+), 247 deletions(-) delete mode 100755 luci-app-openclawmgr/root/usr/share/openclawmgr/oc-config.sh create mode 100755 luci-app-openclawmgr/root/usr/share/openclawmgr/openclawmgr-cli.sh diff --git a/luci-app-openclawmgr/Makefile b/luci-app-openclawmgr/Makefile index a1746a93..3a62fc5f 100644 --- a/luci-app-openclawmgr/Makefile +++ b/luci-app-openclawmgr/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk -PKG_VERSION:=0.1.3 -PKG_RELEASE:=5 +PKG_VERSION:=0.1.5 +PKG_RELEASE:=6 LUCI_TITLE:=LuCI support for OpenClaw Launcher LUCI_PKGARCH:=all diff --git a/luci-app-openclawmgr/htdocs/luci-static/resources/openclawmgr/app.js b/luci-app-openclawmgr/htdocs/luci-static/resources/openclawmgr/app.js index 029e94c1..9c88740f 100644 --- a/luci-app-openclawmgr/htdocs/luci-static/resources/openclawmgr/app.js +++ b/luci-app-openclawmgr/htdocs/luci-static/resources/openclawmgr/app.js @@ -431,7 +431,7 @@ '
' + '

基础配置

' + '
' + - fieldInput("监听端口", '') + + fieldInput("监听端口", '') + fieldInput("监听范围", selectHtml("oclm-bind", form.bind, [ ["lan", "所有地址"], ["loopback", "仅本机"], diff --git a/luci-app-openclawmgr/luasrc/controller/openclawmgr.lua b/luci-app-openclawmgr/luasrc/controller/openclawmgr.lua index ce858259..f6598e6d 100644 --- a/luci-app-openclawmgr/luasrc/controller/openclawmgr.lua +++ b/luci-app-openclawmgr/luasrc/controller/openclawmgr.lua @@ -506,8 +506,8 @@ function action_config_data() return end local port_num = tonumber(port) or 0 - if port_num < 1 or port_num > 65535 then - write_json({ ok = false, error = "invalid port" }) + if port_num < 1025 or port_num > 65535 then + write_json({ ok = false, error = "invalid port (must be 1025-65535)" }) return end uci:set("openclawmgr", section, "port", port) diff --git a/luci-app-openclawmgr/luasrc/model/cbi/openclawmgr/cli.lua b/luci-app-openclawmgr/luasrc/model/cbi/openclawmgr/cli.lua index 1e711c88..c1a2d64e 100644 --- a/luci-app-openclawmgr/luasrc/model/cbi/openclawmgr/cli.lua +++ b/luci-app-openclawmgr/luasrc/model/cbi/openclawmgr/cli.lua @@ -29,42 +29,29 @@ local node_dir = base_dir .. "/node" local global_dir = base_dir .. "/global" local data_dir = base_dir .. "/data" -local cmdline = "sh /usr/share/openclawmgr/oc-config.sh" - s = m:section(SimpleSection) o = s:option(Value, "mode", translate("Mode")) -o:value("menu", translate("Interactive menu")) -o.default = "menu" +o:value("configure", translate("官方配置向导 (openclaw configure)")) +o:value("backup", translate("备份配置")) +o:value("restore", translate("恢复配置")) +o.default = "configure" o.forcewrite = true -o.write = function(self, section, value) - if value == "menu" then - cmdline = "sh /usr/share/openclawmgr/oc-config.sh" - end -end o = s:option(DummyValue, "_tip", translate("CLI Config")) o.rawhtml = true o.cfgvalue = function() - return translate("Starts an interactive OpenClaw configuration menu in a web terminal (LAN only).") + return translate("Starts a web terminal (LAN only) for common OpenClaw maintenance tasks.") end -o = s:option(Button, "connect") -o.render = function(self, section, scope) - self.inputstyle = "add" - self.title = " " - self.inputtitle = translate("Connect") - Button.render(self, section, scope) -end - -o.write = function(self, section) +local function start_ttyd(action) local cmd_ttyd = luci.util.exec("command -v ttyd"):match("^.+ttyd") or nil if not cmd_ttyd or cmd_ttyd:match("^%s*$") then return end local pid = luci.util.trim(luci.util.exec("netstat -lnpt | grep :7682 | grep ttyd | tr -s ' ' | cut -d ' ' -f7 | cut -d'/' -f1")) - if pid and pid ~= "" then + if pid and pid:match("^%d+$") then luci.util.exec("kill -9 " .. pid) end @@ -82,6 +69,7 @@ o.write = function(self, section) "PATH=" .. luci.util.shellquote(env_path), }, " ") + local cmdline = "sh /usr/share/openclawmgr/openclawmgr-cli.sh " .. luci.util.shellquote(action) local start_cmd = string.format( "%s %s -d 2 --once -p 7682 sh -lc %s &", env_prefix, @@ -96,4 +84,19 @@ o.write = function(self, section) o.template = "openclawmgr/cli" end +o = s:option(Button, "connect") +o.render = function(self, section, scope) + self.inputstyle = "add" + self.title = " " + self.inputtitle = translate("Connect") + Button.render(self, section, scope) +end +o.write = function(self, section) + local mode = m:formvalue("mode") or m:formvalue("cbid.OpenClawCLI.1.mode") or "configure" + if mode ~= "configure" and mode ~= "backup" and mode ~= "restore" then + mode = "configure" + end + start_ttyd(mode) +end + return m diff --git a/luci-app-openclawmgr/luasrc/view/openclawmgr/cli.htm b/luci-app-openclawmgr/luasrc/view/openclawmgr/cli.htm index 69d5493b..16dbb2e6 100644 --- a/luci-app-openclawmgr/luasrc/view/openclawmgr/cli.htm +++ b/luci-app-openclawmgr/luasrc/view/openclawmgr/cli.htm @@ -1,4 +1,5 @@
+
+ function ttydHost() { + const h = window.location.hostname || "127.0.0.1"; + return (h.indexOf(":") >= 0 && h[0] !== "[") ? ("[" + h + "]") : h; + } + + const url = "http://" + ttydHost() + ":7682"; + const proto = (window.location.protocol || "http:").toLowerCase(); + const tip = document.getElementById("oclm-cli-tip"); + const iframe = document.getElementById("terminal"); + + if (proto === "https:") { + tip.innerHTML = '当前 LuCI 使用 HTTPS,浏览器通常会拦截 iframe 加载 HTTP 终端(混合内容)。请点击打开:' + url + ''; + iframe.style.display = "none"; + } else { + tip.innerHTML = '终端地址:' + url + ''; + iframe.src = url; + } + diff --git a/luci-app-openclawmgr/root/etc/init.d/openclawmgr b/luci-app-openclawmgr/root/etc/init.d/openclawmgr index 5b1f36a9..e83b099d 100755 --- a/luci-app-openclawmgr/root/etc/init.d/openclawmgr +++ b/luci-app-openclawmgr/root/etc/init.d/openclawmgr @@ -24,6 +24,14 @@ start_service() { [ "$enabled" -eq 1 ] || return 0 [ -n "$base_dir" ] || return 1 + if echo "$port" | grep -Eq '^[0-9]+$'; then + if [ "$port" -le 1024 ] 2>/dev/null; then + logger -t openclawmgr "Refusing to start: unsafe port ${port} (must be >1024)" + return 1 + fi + else + return 1 + fi local node_bin="${base_dir}/node/bin/node" local global_dir="${base_dir}/global" @@ -52,7 +60,8 @@ start_service() { # OpenClaw uses --force to kill conflicting processes, but requires either `lsof` # or `fuser`. iStoreOS images may not include them by default. local force_opt="" - if command -v lsof >/dev/null 2>&1 || command -v fuser >/dev/null 2>&1; then + # Safety: never allow --force on privileged ports (it could kill SSH/LuCI when ports collide). + if [ "$port" -gt 1024 ] 2>/dev/null && (command -v lsof >/dev/null 2>&1 || command -v fuser >/dev/null 2>&1); then force_opt="--force" fi diff --git a/luci-app-openclawmgr/root/usr/libexec/istorec/openclawmgr.sh b/luci-app-openclawmgr/root/usr/libexec/istorec/openclawmgr.sh index 164c897b..d8cc1edb 100755 --- a/luci-app-openclawmgr/root/usr/libexec/istorec/openclawmgr.sh +++ b/luci-app-openclawmgr/root/usr/libexec/istorec/openclawmgr.sh @@ -11,6 +11,27 @@ log_ts() { date "+%Y-%m-%d %H:%M:%S"; } uci_get() { uci -q get "${UCI_NS}.main.$1" 2>/dev/null || true; } +validate_base_dir() { + local dir="$1" + dir="$(printf "%s" "$dir" | sed 's/[[:space:]]*$//')" + dir="${dir%/}" + [ -n "$dir" ] || return 1 + case "$dir" in + /*) ;; + *) return 1 ;; + esac + [ "$dir" != "/" ] || return 1 + case "$dir" in + *"/../"*|*"/.."|*"/./"*|*"/.") return 1 ;; + esac + case "$dir" in + /bin|/bin/*|/sbin|/sbin/*|/lib|/lib/*|/usr|/usr/*|/etc|/etc/*|/proc|/proc/*|/sys|/sys/*|/dev|/dev/*|/run|/run/*|/tmp|/tmp/*|/var|/var/*|/overlay|/overlay/*|/rom|/rom/*) + return 1 + ;; + esac + return 0 +} + uci_get_list() { local key="$1" uci -q show "${UCI_NS}.main.${key}" 2>/dev/null | \ @@ -120,6 +141,7 @@ default_gateway() { ensure_dirs() { mkdir -p "$BASE_DIR" "$NODE_DIR" "$GLOBAL_DIR" "$DATA_DIR/.openclaw/workspace" "$BASE_DIR/npm-cache" 2>/dev/null || true + printf "%s\n" "managed-by=openclawmgr" >"${BASE_DIR}/.openclawmgr.managed" 2>/dev/null || true fix_data_permissions || true } @@ -917,6 +939,7 @@ install_openclaw() { fix_data_permissions || true # Do not auto-enable/start on first install; let user Save & Apply after choosing base_dir and settings. if [ "${ENABLED:-0}" = "1" ]; then + ensure_safe_port_for_start || return 1 /etc/init.d/openclawmgr enable >/dev/null 2>&1 || true /etc/init.d/openclawmgr restart >/dev/null 2>&1 || true fi @@ -955,8 +978,15 @@ do_purge() { /etc/init.d/openclawmgr stop >/dev/null 2>&1 || true /etc/init.d/openclawmgr disable >/dev/null 2>&1 || true uci -q set "${UCI_NS}.main.enabled=0" && uci -q commit "$UCI_NS" || true - rm -rf "$BASE_DIR" 2>/dev/null || true - write_installer_log "Base dir removed: $BASE_DIR" + # Safety: only remove the whole base_dir when it looks like a dedicated OpenClawMgr directory. + # Otherwise, only remove managed subdirectories to avoid accidental system wipe. + if [ -f "${BASE_DIR}/.openclawmgr.managed" ] || [ "$(basename "$BASE_DIR" 2>/dev/null || echo "")" = "OpenClawMgr" ]; then + rm -rf "$BASE_DIR" 2>/dev/null || true + write_installer_log "Base dir removed: $BASE_DIR" + else + rm -rf "$NODE_DIR" "$GLOBAL_DIR" "$DATA_DIR" "$BASE_DIR/npm-cache" "${BASE_DIR}/.openclawmgr.managed" 2>/dev/null || true + write_installer_log "Base dir kept (safety). Removed: node/global/data/npm-cache under $BASE_DIR" + fi write_installer_log "== purge done ==" } @@ -965,6 +995,7 @@ do_start() { write_installer_log "== start begin ==" ensure_dirs fix_data_permissions || true + ensure_safe_port_for_start || exit 1 /etc/init.d/openclawmgr enable >/dev/null 2>&1 || true uci -q set "${UCI_NS}.main.enabled=1" && uci -q commit "$UCI_NS" || true ensure_gateway_config || true @@ -986,6 +1017,7 @@ do_restart() { write_installer_log "== restart begin ==" ensure_dirs fix_data_permissions || true + ensure_safe_port_for_start || exit 1 ensure_gateway_config || true /etc/init.d/openclawmgr restart >/dev/null 2>&1 || true write_installer_log "== restart done ==" @@ -1001,6 +1033,7 @@ do_restart() { local pid="" pid="$(ubus call service list "{\"name\":\"openclawmgr\"}" 2>/dev/null | jsonfilter -e '$.openclawmgr.instances.gateway.pid' 2>/dev/null || true)" if [ "${ENABLED:-0}" = "1" ]; then + ensure_safe_port_for_start || exit 1 /etc/init.d/openclawmgr enable >/dev/null 2>&1 || true if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then /etc/init.d/openclawmgr restart >/dev/null 2>&1 || true @@ -1165,6 +1198,9 @@ NODE_VERSION="24.14.0" case "$PORT" in ''|*[!0-9]*) PORT="18789" ;; esac +if [ "$PORT" -lt 1 ] 2>/dev/null || [ "$PORT" -gt 65535 ] 2>/dev/null; then + PORT="18789" +fi case "$BIND" in loopback|lan|auto|tailnet|custom) ;; *) BIND="lan" ;; @@ -1195,6 +1231,22 @@ require_base_dir() { write_installer_log "base_dir is not configured; please choose a data directory in LuCI and Save & Apply, then retry." exit 2 fi + if ! validate_base_dir "$BASE_DIR"; then + write_installer_log "Refusing to operate on unsafe base_dir: $BASE_DIR" + write_installer_log "Fix: choose a dedicated directory like /root/Configs/OpenClawMgr or /mnt/.../OpenClawMgr" + exit 2 + fi +} + +ensure_safe_port_for_start() { + case "$PORT" in + ''|*[!0-9]*) return 1 ;; + esac + if [ "$PORT" -le 1024 ] 2>/dev/null; then + write_installer_log "Refusing to start with unsafe port $PORT (must be >1024)." + return 1 + fi + return 0 } NODE_DIR="${BASE_DIR}/node" diff --git a/luci-app-openclawmgr/root/usr/share/openclawmgr/oc-config.sh b/luci-app-openclawmgr/root/usr/share/openclawmgr/oc-config.sh deleted file mode 100755 index cde10c38..00000000 --- a/luci-app-openclawmgr/root/usr/share/openclawmgr/oc-config.sh +++ /dev/null @@ -1,217 +0,0 @@ -#!/bin/sh -# ============================================================================ -# OpenClawMgr CLI Config Helper (LAN ttyd) -# - Provides an interactive menu to run OpenClaw official configure wizard, -# manage config file, and restart gateway. -# ============================================================================ - -set -e - -# Ensure a sensible TERM for ttyd/xterm.js. -export TERM="${TERM:-xterm-256color}" - -ESC="$(printf '\033')" -RED="${ESC}[0;31m"; GREEN="${ESC}[0;32m"; YELLOW="${ESC}[1;33m" -CYAN="${ESC}[0;36m"; BOLD="${ESC}[1m"; DIM="${ESC}[2m"; NC="${ESC}[0m" - -prompt_with_default() { - local prompt="$1" def="$2" var="$3" v="" - if [ -n "$def" ]; then - printf "%s%s[%s]%s: " "$prompt" "$DIM" "$def" "$NC" - else - printf "%s: " "$prompt" - fi - # shellcheck disable=SC2162 - read v - [ -z "$v" ] && v="$def" - eval "$var=\$v" -} - -pause_enter() { - printf "\n%s按回车继续...%s" "$DIM" "$NC" - # shellcheck disable=SC2162 - read _ -} - -uci_get() { uci -q get "openclawmgr.main.$1" 2>/dev/null || true; } - -BASE_DIR="${BASE_DIR:-$(uci_get base_dir)}" -if [ -z "$BASE_DIR" ]; then - printf '%s\n' "${RED}ERROR: base_dir 未配置。请先在 OpenClaw 启动器页面设置数据目录并保存应用。${NC}" - exit 1 -fi - -NODE_DIR="${BASE_DIR}/node" -GLOBAL_DIR="${BASE_DIR}/global" -DATA_DIR="${BASE_DIR}/data" - -NODE_BIN="${NODE_DIR}/bin/node" -OPENCLAW_BIN="${GLOBAL_DIR}/bin/openclaw" -CONFIG_FILE="${DATA_DIR}/.openclaw/openclaw.json" -BACKUP_DIR="${DATA_DIR}/.openclaw/backups" - -export HOME="$DATA_DIR" -export OPENCLAW_HOME="$DATA_DIR" -export OPENCLAW_STATE_DIR="${DATA_DIR}/.openclaw" -export OPENCLAW_CONFIG_PATH="$CONFIG_FILE" -export PATH="${NODE_DIR}/bin:${GLOBAL_DIR}/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -find_entry() { - local d="${GLOBAL_DIR}/lib/node_modules/openclaw" - [ -f "${d}/openclaw.mjs" ] && { echo "${d}/openclaw.mjs"; return 0; } - [ -f "${d}/dist/cli.js" ] && { echo "${d}/dist/cli.js"; return 0; } - return 1 -} - -OPENCLAW_ENTRY="$(find_entry 2>/dev/null || true)" - -oc_cmd() { - if [ -x "$OPENCLAW_BIN" ]; then - "$OPENCLAW_BIN" "$@" - return $? - fi - if [ -x "$NODE_BIN" ] && [ -n "$OPENCLAW_ENTRY" ]; then - "$NODE_BIN" "$OPENCLAW_ENTRY" "$@" - return $? - fi - printf '%s\n' "${RED}ERROR: OpenClaw 未安装或运行时不完整。${NC}" - printf '%s\n' "HINT: 在 OpenClaw 启动器页面执行安装/升级。" - return 127 -} - -restart_gateway() { - printf '\n' - printf '%s\n' "${CYAN}=== 重启 Gateway ===${NC}" - /usr/libexec/istorec/openclawmgr.sh restart 2>&1 || true -} - -apply_config() { - printf '\n' - printf '%s\n' "${CYAN}=== 重新生成/应用配置(以 OpenClawMgr 配置为准)===${NC}" - /usr/libexec/istorec/openclawmgr.sh apply_config 2>&1 || true -} - -show_config() { - printf '\n' - printf '%s\n' "${CYAN}=== 配置文件 ===${NC}" - printf '%s\n' "路径: ${DIM}${CONFIG_FILE}${NC}" - printf '\n' - if [ ! -f "$CONFIG_FILE" ]; then - printf '%s\n' "${YELLOW}(配置文件不存在)${NC}" - return 0 - fi - if [ -x "$NODE_BIN" ]; then - "$NODE_BIN" -e "console.log(JSON.stringify(JSON.parse(require('fs').readFileSync(process.env.OPENCLAW_CONFIG_PATH,'utf8')),null,2))" 2>/dev/null \ - || cat "$CONFIG_FILE" - else - cat "$CONFIG_FILE" - fi -} - -edit_config() { - if [ ! -f "$CONFIG_FILE" ]; then - printf '%s\n' "${YELLOW}配置文件不存在:${CONFIG_FILE}${NC}" - return 1 - fi - if command -v vi >/dev/null 2>&1; then - vi "$CONFIG_FILE" - elif command -v nano >/dev/null 2>&1; then - nano "$CONFIG_FILE" - else - printf '%s\n' "${YELLOW}未找到编辑器(vi/nano)。${NC}" - return 1 - fi -} - -tail_logs() { - printf '\n' - printf '%s\n' "${CYAN}=== 最近日志(logread -e openclaw)===${NC}" - printf '\n' - logread -e openclaw 2>/dev/null | tail -200 || printf '%s\n' "${YELLOW}(无法读取日志)${NC}" -} - -backup_config() { - mkdir -p "$BACKUP_DIR" 2>/dev/null || true - if [ ! -f "$CONFIG_FILE" ]; then - printf '%s\n' "${YELLOW}配置文件不存在,无法备份。${NC}" - return 1 - fi - local ts dst - ts="$(date -u +%Y-%m-%dT%H-%M-%SZ 2>/dev/null || date +%s)" - dst="${BACKUP_DIR}/${ts}-openclaw.json" - cp -f "$CONFIG_FILE" "$dst" - printf '%s\n' "${GREEN}✅ 已备份到: ${dst}${NC}" -} - -restore_config() { - mkdir -p "$BACKUP_DIR" 2>/dev/null || true - printf '\n' - printf '%s\n' "${CYAN}=== 可用备份 ===${NC}" - ls -lt "$BACKUP_DIR"/*.json 2>/dev/null | head -10 | awk '{print " " $NF}' || printf '%s\n' "${YELLOW}(无备份)${NC}" - printf '\n' - local fp="" - prompt_with_default "请输入要恢复的备份文件路径" "" fp - [ -n "$fp" ] || return 0 - if [ ! -f "$fp" ]; then - printf '%s\n' "${YELLOW}文件不存在:${fp}${NC}" - return 1 - fi - cp -f "$fp" "$CONFIG_FILE" - printf '%s\n' "${GREEN}✅ 已恢复配置到: ${CONFIG_FILE}${NC}" -} - -main_menu() { - while true; do - printf '\n' - printf '%s\n' "${BOLD}OpenClaw AI Gateway — CLI 配置入口(OpenClawMgr)${NC}" - printf '%s\n' "${DIM}base_dir: ${BASE_DIR}${NC}" - printf '\n' - printf '%s\n' " ${CYAN}1)${NC} 🧭 官方配置向导 ${DIM}(openclaw configure)${NC}" - printf '%s\n' " ${CYAN}2)${NC} ℹ️ OpenClaw 版本/帮助 ${DIM}(openclaw --version / --help)${NC}" - printf '%s\n' " ${CYAN}3)${NC} 📄 查看配置文件 ${DIM}(openclaw.json)${NC}" - printf '%s\n' " ${CYAN}4)${NC} ✍️ 编辑配置文件 ${DIM}(vi/nano)${NC}" - printf '%s\n' " ${CYAN}5)${NC} ♻️ 应用/重建配置 ${DIM}(openclawmgr.sh apply_config)${NC}" - printf '%s\n' " ${CYAN}6)${NC} 🔄 重启 Gateway ${DIM}(openclawmgr.sh restart)${NC}" - printf '%s\n' " ${CYAN}7)${NC} 📋 查看日志 ${DIM}(logread -e openclaw)${NC}" - printf '%s\n' " ${CYAN}8)${NC} 💾 备份配置" - printf '%s\n' " ${CYAN}9)${NC} 📥 恢复配置" - printf '\n' - printf '%s\n' " ${CYAN}0)${NC} 退出" - printf '\n' - local c="" - prompt_with_default "请选择" "1" c - case "$c" in - 1) - printf '\n' - printf '%s\n' "${CYAN}=== openclaw configure ===${NC}" - oc_cmd configure || true - pause_enter - ;; - 2) - printf '\n' - oc_cmd --version 2>/dev/null || true - printf '\n' - oc_cmd --help 2>/dev/null | sed -n '1,120p' || true - pause_enter - ;; - 3) show_config; pause_enter ;; - 4) edit_config || true; pause_enter ;; - 5) apply_config; pause_enter ;; - 6) restart_gateway; pause_enter ;; - 7) tail_logs; pause_enter ;; - 8) backup_config || true; pause_enter ;; - 9) restore_config || true; pause_enter ;; - 0) printf '%s\n' "${GREEN}再见!${NC}"; exit 0 ;; - *) printf '%s\n' "${YELLOW}无效选择${NC}" ;; - esac - done -} - -case "${1:-}" in - --help|-h) - echo "Usage: oc-config.sh [--help]" - ;; - *) - main_menu - ;; -esac diff --git a/luci-app-openclawmgr/root/usr/share/openclawmgr/openclawmgr-cli.sh b/luci-app-openclawmgr/root/usr/share/openclawmgr/openclawmgr-cli.sh new file mode 100755 index 00000000..dc61921a --- /dev/null +++ b/luci-app-openclawmgr/root/usr/share/openclawmgr/openclawmgr-cli.sh @@ -0,0 +1,160 @@ +#!/bin/sh +# OpenClawMgr CLI Config Helper (for ttyd) + +set -eu + +export TERM="${TERM:-xterm-256color}" + +ESC="$(printf '\033')" +RED="${ESC}[0;31m"; GREEN="${ESC}[0;32m"; YELLOW="${ESC}[1;33m" +CYAN="${ESC}[0;36m"; BOLD="${ESC}[1m"; DIM="${ESC}[2m"; NC="${ESC}[0m" + +err() { printf '%s\n' "${RED}ERROR: $*${NC}"; } +warn() { printf '%s\n' "${YELLOW}$*${NC}"; } +info() { printf '%s\n' "${CYAN}$*${NC}"; } +ok() { printf '%s\n' "${GREEN}$*${NC}"; } + +pause_enter() { + printf "\n%s按回车继续...%s" "$DIM" "$NC" + IFS= read -r _ || true +} + +prompt_default() { + local prompt="$1" def="${2:-}" v="" + if [ -n "$def" ]; then + printf "%s%s[%s]%s: " "$prompt" "$DIM" "$def" "$NC" >&2 + else + printf "%s: " "$prompt" >&2 + fi + IFS= read -r v || v="" + [ -z "$v" ] && v="$def" + printf "%s" "$v" +} + +uci_get() { uci -q get "openclawmgr.main.$1" 2>/dev/null || true; } + +init_paths() { + BASE_DIR="${BASE_DIR:-$(uci_get base_dir)}" + if [ -z "$BASE_DIR" ]; then + err "base_dir 未配置。请先在 OpenClaw 启动器页面设置数据目录并保存应用。" + exit 1 + fi + + NODE_DIR="${BASE_DIR}/node" + GLOBAL_DIR="${BASE_DIR}/global" + DATA_DIR="${BASE_DIR}/data" + + NODE_BIN="${NODE_DIR}/bin/node" + OPENCLAW_BIN="${GLOBAL_DIR}/bin/openclaw" + CONFIG_FILE="${DATA_DIR}/.openclaw/openclaw.json" + BACKUP_DIR="${DATA_DIR}/.openclaw/backups" + + export HOME="$DATA_DIR" + export OPENCLAW_HOME="$DATA_DIR" + export OPENCLAW_STATE_DIR="${DATA_DIR}/.openclaw" + export OPENCLAW_CONFIG_PATH="$CONFIG_FILE" + export PATH="${NODE_DIR}/bin:${GLOBAL_DIR}/bin:/usr/sbin:/usr/bin:/sbin:/bin" +} + +find_openclaw_entry() { + local d="${GLOBAL_DIR}/lib/node_modules/openclaw" + [ -f "${d}/openclaw.mjs" ] && { echo "${d}/openclaw.mjs"; return 0; } + [ -f "${d}/dist/cli.js" ] && { echo "${d}/dist/cli.js"; return 0; } + return 1 +} + +openclaw_cmd() { + if [ -x "$OPENCLAW_BIN" ]; then + "$OPENCLAW_BIN" "$@" + return $? + fi + + local entry="" + entry="$(find_openclaw_entry 2>/dev/null || true)" + if [ -x "$NODE_BIN" ] && [ -n "$entry" ]; then + "$NODE_BIN" "$entry" "$@" + return $? + fi + + err "OpenClaw 未安装或运行时不完整。" + printf '%s\n' "HINT: 在 OpenClaw 启动器页面执行安装/升级。" + return 127 +} + +action_backup_config() { + mkdir -p "$BACKUP_DIR" 2>/dev/null || true + if [ ! -f "$CONFIG_FILE" ]; then + warn "配置文件不存在,无法备份。" + return 1 + fi + local ts dst + ts="$(date -u +%Y-%m-%dT%H-%M-%SZ 2>/dev/null || date +%s)" + dst="${BACKUP_DIR}/${ts}-openclaw.json" + cp -f "$CONFIG_FILE" "$dst" + ok "已备份到: ${dst}" +} + +action_restore_config() { + mkdir -p "$BACKUP_DIR" 2>/dev/null || true + printf '\n' + info "=== 可用备份 ===" + ls -lt "$BACKUP_DIR"/*.json 2>/dev/null | head -10 | awk '{print " " $NF}' || warn "(无备份)" + printf '\n' + local fp="" + fp="$(prompt_default "请输入要恢复的备份文件路径" "")" + [ -n "$fp" ] || return 0 + if [ ! -f "$fp" ]; then + warn "文件不存在:${fp}" + return 1 + fi + cp -f "$fp" "$CONFIG_FILE" + ok "已恢复配置到: ${CONFIG_FILE}" +} + +action_configure() { + printf '\n' + info "=== openclaw configure ===" + openclaw_cmd configure || true +} + +main_menu() { + while true; do + printf '\n' + printf '%s\n' "${BOLD}OpenClaw AI Gateway — CLI 配置入口(OpenClawMgr)${NC}" + printf '%s\n' "${DIM}base_dir: ${BASE_DIR}${NC}" + printf '\n' + printf '%s\n' " ${CYAN}1)${NC} 🧭 官方配置向导 ${DIM}(openclaw configure)${NC}" + printf '%s\n' " ${CYAN}2)${NC} 💾 备份配置" + printf '%s\n' " ${CYAN}3)${NC} 📥 恢复配置" + printf '\n' + printf '%s\n' " ${CYAN}0)${NC} 退出" + printf '\n' + + local c="" + c="$(prompt_default "请选择" "1")" + case "$c" in + 1) action_configure; pause_enter ;; + 2) action_backup_config || true; pause_enter ;; + 3) action_restore_config || true; pause_enter ;; + 0) ok "再见!"; exit 0 ;; + *) warn "无效选择" ;; + esac + done +} + +init_paths + +case "${1:-}" in + --help|-h) + echo "Usage: openclawmgr-cli.sh [configure|backup|restore]" + ;; + *) + case "${1:-menu}" in + menu) main_menu ;; + configure) action_configure; pause_enter ;; + backup) action_backup_config || true; pause_enter ;; + restore) action_restore_config || true; pause_enter ;; + *) echo "Unknown command: ${1:-}" >&2; exit 2 ;; + esac + ;; +esac