diff --git a/luasrc/controller/openclaw.lua b/luasrc/controller/openclaw.lua index 40e4c97..76cf6ba 100644 --- a/luasrc/controller/openclaw.lua +++ b/luasrc/controller/openclaw.lua @@ -4,6 +4,9 @@ module("luci.controller.openclaw", package.seeall) local nixio_fs = require "nixio.fs" local util = require "luci.util" local oc_paths = require "openclaw.paths" +local GITHUB_REPO = "hotwa/luci-app-openclaw" +local GITHUB_RELEASES_URL = "https://github.com/" .. GITHUB_REPO .. "/releases" +local GITHUB_API_RELEASES_URL = "https://api.github.com/repos/" .. GITHUB_REPO .. "/releases" local function get_install_root_from_uci() return require("luci.model.uci").cursor():get("openclaw", "main", "install_root") @@ -448,7 +451,7 @@ function action_check_update() local plugin_has_update = false -- 使用 GitHub API 获取最新 release (tag + body) - local gh_json = sys.exec("curl -sf --connect-timeout 5 --max-time 10 'https://api.github.com/repos/10000ge10000/luci-app-openclaw/releases/latest' 2>/dev/null") + local gh_json = sys.exec("curl -sf --connect-timeout 5 --max-time 10 '" .. GITHUB_API_RELEASES_URL .. "/latest' 2>/dev/null") if gh_json and gh_json ~= "" then -- 提取 tag_name local tag = gh_json:match('"tag_name"%s*:%s*"([^"]+)"') @@ -551,7 +554,7 @@ function action_plugin_upgrade() 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" + local run_url = GITHUB_RELEASES_URL .. "/download/v" .. version .. "/luci-app-openclaw_" .. version .. ".run" -- 使用 curl 下载 (-L 跟随重定向), 然后 sh 执行 sys.exec(string.format( "( echo '正在下载插件 v%s ...' > /tmp/openclaw-plugin-upgrade.log; " .. diff --git a/luasrc/model/cbi/openclaw/basic.lua b/luasrc/model/cbi/openclaw/basic.lua index 71191ca..b203995 100644 --- a/luasrc/model/cbi/openclaw/basic.lua +++ b/luasrc/model/cbi/openclaw/basic.lua @@ -251,9 +251,13 @@ act.cfgvalue = function(self, section) html[#html+1] = 'if(log.indexOf("当前已安装在")>=0||log.indexOf("检测目录不存在")>=0||log.indexOf("安装根目录")>=0){' html[#html+1] = 'reasons.push("📁 安装目录配置有误 — 请选择已挂载的绝对路径;如果当前环境已经安装在其他目录,请先卸载后再切换。");' html[#html+1] = '}' - -- 网络问题 - html[#html+1] = 'if(ll.indexOf("could not resolve")>=0||ll.indexOf("connection timed out")>=0||ll.indexOf("curl")>=0&&ll.indexOf("fail")>=0||ll.indexOf("wget")>=0&&ll.indexOf("fail")>=0||ll.indexOf("所有镜像均下载失败")>=0){' - html[#html+1] = 'reasons.push("🌐 网络连接失败 — 无法下载 Node.js。请检查路由器是否能访问外网。
  💡 解决: 检查 DNS 设置和网络连接,或手动指定镜像: NODE_MIRROR=https://npmmirror.com/mirrors/node openclaw-env setup");' + -- ARM64 musl 专属下载问题 + html[#html+1] = 'if(ll.indexOf("arm64 musl")>=0&&(ll.indexOf("release api")>=0||ll.indexOf("node-bins")>=0||ll.indexOf("未找到兼容的 arm64 musl node.js 资产")>=0||ll.indexOf("无法获取 arm64 musl node.js 发布元数据")>=0)){' + html[#html+1] = 'reasons.push("🧩 ARM64 musl Node.js 资产不可用 — 当前设备依赖仓库发布的 node-bins 资产,而不是通用 Node 镜像。
  💡 解决: 检查 hotwa/luci-app-openclawnode-bins release 是否存在满足 >=22.16.0linux-arm64-musl 资产,并确认路由器可访问 GitHub API 与 release 页面。");' + html[#html+1] = '}' + -- 通用网络问题 + html[#html+1] = 'if((ll.indexOf("could not resolve")>=0||ll.indexOf("connection timed out")>=0||ll.indexOf("curl")>=0&&ll.indexOf("fail")>=0||ll.indexOf("wget")>=0&&ll.indexOf("fail")>=0||ll.indexOf("所有镜像均下载失败")>=0)&&!(ll.indexOf("arm64 musl")>=0&&(ll.indexOf("release api")>=0||ll.indexOf("node-bins")>=0||ll.indexOf("未找到兼容的 arm64 musl node.js 资产")>=0||ll.indexOf("无法获取 arm64 musl node.js 发布元数据")>=0))){' + html[#html+1] = 'reasons.push("🌐 网络连接失败 — 无法下载 Node.js。请检查路由器是否能访问外网。
  💡 解决: 检查 DNS 设置、网络连通性以及当前默认下载源是否可访问;如有需要,可通过环境变量覆盖下载源后重试。");' html[#html+1] = '}' -- 磁盘空间 html[#html+1] = 'if(ll.indexOf("no space")>=0||ll.indexOf("disk full")>=0||ll.indexOf("enospc")>=0){' @@ -331,7 +335,7 @@ act.cfgvalue = function(self, section) html[#html+1] = '}' html[#html+1] = 'act.innerHTML=notesHtml' html[#html+1] = '+\'\'' - html[#html+1] = '+\' 📥 手动下载\';' + html[#html+1] = '+\' 📥 手动下载\';' html[#html+1] = '}' html[#html+1] = '}catch(e){el.innerHTML="❌ 检测失败";}' html[#html+1] = '});}' @@ -614,8 +618,8 @@ guide.cfgvalue = function() html[#html+1] = '' html[#html+1] = '🔗 space.bilibili.com/59438380' html[#html+1] = 'GitHub 项目:' - html[#html+1] = '' - html[#html+1] = '🐙 10000ge10000/luci-app-openclaw' + html[#html+1] = '' + html[#html+1] = '🐙 hotwa/luci-app-openclaw' return table.concat(html, "\n") end diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env index c1f776d..aa688b9 100755 --- a/root/usr/bin/openclaw-env +++ b/root/usr/bin/openclaw-env @@ -53,13 +53,14 @@ NODE_BIN="${NODE_BASE}/bin/node" NPM_BIN="${NODE_BASE}/bin/npm" PNPM_BIN="${OC_GLOBAL}/bin/pnpm" -# Node.js 官方镜像 + musl 非官方构建 -# Node.js 官方镜像 + musl 非官方构建 +# Node.js 下载源 +OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}" NODE_MIRROR="${NODE_MIRROR:-https://nodejs.org/dist}" NODE_MIRROR_CN="https://npmmirror.com/mirrors/node" NODE_MUSL_MIRROR="https://unofficial-builds.nodejs.org/download/release" -# 项目自托管 ARM64 musl Node.js (unofficial-builds 仅提供 x64 musl) -NODE_SELF_HOST="https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins" +NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_GITHUB_REPO}/releases/download/node-bins}" +NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_GITHUB_REPO}/releases/tags/node-bins}" +NODE_RELEASE_PAGE="${NODE_RELEASE_PAGE:-https://github.com/${OPENCLAW_GITHUB_REPO}/releases/tag/node-bins}" export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH" export NODE_ICU_DATA="${NODE_BASE}/share/icu" @@ -68,6 +69,48 @@ log_info() { echo " [✓] $1"; } log_warn() { echo " [!] $1"; } log_error() { echo " [✗] $1"; } +download_url_to_file() { + local url="$1" + local output="$2" + + curl -fSL --connect-timeout 15 --max-time 300 -o "$output" "$url" 2>/dev/null || \ + wget -q --timeout=15 -O "$output" "$url" 2>/dev/null +} + +resolve_arm64_musl_node_url() { + local node_ver="$1" + local release_json="/tmp/openclaw-node-bins-release.json" + local asset_url="" + + echo " 正在从 ${NODE_RELEASE_API} 获取 ARM64 musl 版本列表..." >&2 + if ! download_url_to_file "$NODE_RELEASE_API" "$release_json"; then + rm -f "$release_json" + log_error "无法获取 ARM64 musl Node.js 发布元数据" >&2 + echo " 仓库: ${OPENCLAW_GITHUB_REPO}" >&2 + echo " 最低要求: v${node_ver}" >&2 + echo " Release API: ${NODE_RELEASE_API}" >&2 + echo " Release 页面: ${NODE_RELEASE_PAGE}" >&2 + echo " 请先发布 node-bins 中满足要求的 ARM64 musl 资产,或通过环境变量覆盖下载源" >&2 + return 1 + fi + + asset_url=$(oc_select_node_release_asset_url "$release_json" "linux-arm64" "$node_ver" || true) + rm -f "$release_json" + + if [ -n "$asset_url" ]; then + printf '%s\n' "$asset_url" + return 0 + fi + + log_error "未找到兼容的 ARM64 musl Node.js 资产" >&2 + echo " 仓库: ${OPENCLAW_GITHUB_REPO}" >&2 + echo " 最低要求: v${node_ver}" >&2 + echo " Release API: ${NODE_RELEASE_API}" >&2 + echo " Release 页面: ${NODE_RELEASE_PAGE}" >&2 + echo " 请先发布 node-bins 中满足要求的 ARM64 musl 资产,或通过环境变量覆盖下载源" >&2 + return 1 +} + # 安全创建目录 (会在 _oc_fix_opt 修复 /opt 后使用标准路径) ensure_mkdir() { local target="$1" @@ -169,17 +212,16 @@ download_node() { local mirror_list="" local musl_tarball="node-v${node_ver}-${node_arch}-musl.tar.xz" local glibc_tarball="node-v${node_ver}-${node_arch}.tar.xz" + local arm64_musl_url="" if [ "$libc_type" = "musl" ]; then echo "" echo "=== 下载 Node.js v${node_ver} (${node_arch}, musl libc) ===" if [ "$node_arch" = "linux-arm64" ]; then - # ARM64 musl: unofficial-builds 不提供,从项目自托管下载 - # 1) 项目自托管 ARM64 musl 构建 (当前版本) - mirror_list="${NODE_SELF_HOST}/${musl_tarball}" - # 2) unofficial-builds (留作将来可能支持) - mirror_list="$mirror_list ${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}" + # ARM64 musl: 从当前维护仓库的 node-bins release 动态选择兼容资产 + arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver") || exit 1 + mirror_list="${arm64_musl_url}" else # x64 musl: unofficial-builds 提供 # 1) unofficial-builds diff --git a/root/usr/libexec/openclaw-node.sh b/root/usr/libexec/openclaw-node.sh index 3354240..bc0b862 100644 --- a/root/usr/libexec/openclaw-node.sh +++ b/root/usr/libexec/openclaw-node.sh @@ -66,3 +66,53 @@ oc_read_node_version() { version=$("$node_bin" --version 2>/dev/null) || return 1 oc_normalize_node_version "$version" } + +oc_select_node_release_asset_url() { + local json_file="${1:-}" + local node_arch="${2:-}" + local min_version="${3:-}" + local asset_regex="" + local urls="" + local best_url="" + local best_version="" + local url="" + local asset_name="" + local asset_version="" + + [ -n "$json_file" ] || return 1 + [ -f "$json_file" ] || return 1 + oc_normalize_node_version "$min_version" >/dev/null || return 1 + + case "$node_arch" in + linux-arm64|linux-x64) ;; + *) return 1 ;; + esac + + asset_regex="node-v[0-9][0-9.]*-${node_arch}-musl\\.tar\\.xz" + urls=$( + tr -d '\r' < "$json_file" | \ + grep -o "\"browser_download_url\"[[:space:]]*:[[:space:]]*\"[^\"]*${asset_regex}\"" | \ + sed 's/^"browser_download_url"[[:space:]]*:[[:space:]]*"//; s/"$//' + ) || true + + [ -n "$urls" ] || return 1 + + while IFS= read -r url; do + [ -n "$url" ] || continue + asset_name="${url##*/}" + asset_version=$(printf '%s\n' "$asset_name" | sed -n "s/^node-v\\([0-9][0-9.]*\\)-${node_arch}-musl\\.tar\\.xz$/\\1/p") + asset_version=$(oc_normalize_node_version "$asset_version" 2>/dev/null || true) + [ -n "$asset_version" ] || continue + if oc_node_version_ge "$asset_version" "$min_version"; then + if [ -z "$best_version" ] || oc_node_version_ge "$asset_version" "$best_version"; then + best_version="$asset_version" + best_url="$url" + fi + fi + done <&2 @@ -29,6 +31,14 @@ grep -Fq 'oc_node_version_ge "$installed_ver" "$node_ver"' "$ENV_SCRIPT" || fail if grep -Fq 'mirror_list="$mirror_list ${NODE_SELF_HOST}/${v1_tarball}"' "$ENV_SCRIPT"; then fail "installer should not auto-fallback from V2 to V1 tarball" fi +grep -Fq 'OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should default to hotwa repo" +grep -Fq 'NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_GITHUB_REPO}/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release URL from hotwa repo" +grep -Fq 'NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_GITHUB_REPO}/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release API from hotwa repo" +grep -Fq 'oc_select_node_release_asset_url' "$ENV_SCRIPT" || fail "installer should dynamically select ARM64 musl asset" +grep -Fq 'arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver") || exit 1' "$ENV_SCRIPT" || fail "installer should resolve ARM64 musl asset dynamically" +if grep -Fq 'mirror_list="${NODE_SELF_HOST}/${musl_tarball}"' "$ENV_SCRIPT"; then + fail "installer should not hardcode exact ARM64 musl asset path" +fi grep -Fq 'openclaw-paths.sh' "$MAKEFILE" || fail "package makefile should install path helper" grep -Fq 'openclaw-node.sh' "$MAKEFILE" || fail "package makefile should install node helper" @@ -43,5 +53,13 @@ grep -Fq 'openclaw/paths.lua' "$BUILD_IPK" || fail "ipk builder should package L grep -Fq 'openclaw-paths.sh' "$BUILD_RUN" || fail "run builder should package path helper" grep -Fq 'openclaw-node.sh' "$BUILD_RUN" || fail "run builder should package node helper" grep -Fq 'openclaw/paths.lua' "$BUILD_RUN" || fail "run builder should package Lua path helper" +grep -Fq 'local GITHUB_REPO = "hotwa/luci-app-openclaw"' "$CONTROLLER_SCRIPT" || fail "controller should default to hotwa repo" +grep -Fq 'local GITHUB_RELEASES_URL = "https://github.com/" .. GITHUB_REPO .. "/releases"' "$CONTROLLER_SCRIPT" || fail "controller should derive release URLs from hotwa repo" +grep -Fq 'local GITHUB_API_RELEASES_URL = "https://api.github.com/repos/" .. GITHUB_REPO .. "/releases"' "$CONTROLLER_SCRIPT" || fail "controller should derive API URLs from hotwa repo" +grep -Fq "https://github.com/hotwa/luci-app-openclaw/releases/latest" "$BASIC_LUA" || fail "UI should link manual download to hotwa repo" +grep -Fq "ARM64 musl" "$BASIC_LUA" || fail "UI should mention ARM64 musl specific guidance" +if grep -Fq 'NODE_MIRROR=https://npmmirror.com/mirrors/node openclaw-env setup' "$BASIC_LUA"; then + fail "UI should not recommend NODE_MIRROR for ARM64 musl node download failures" +fi echo "ok" diff --git a/tests/test_openclaw_node.sh b/tests/test_openclaw_node.sh index 855145e..0c4f8c9 100644 --- a/tests/test_openclaw_node.sh +++ b/tests/test_openclaw_node.sh @@ -51,4 +51,31 @@ if oc_read_node_version "$tmpdir/node-bad" >/dev/null 2>&1; then fail "broken node binary should not be accepted" fi +cat > "$tmpdir/node-bins-release.json" <<'EOF' +{ + "tag_name": "node-bins", + "assets": [ + { + "name": "node-v22.15.1-linux-arm64-musl.tar.xz", + "browser_download_url": "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v22.15.1-linux-arm64-musl.tar.xz" + }, + { + "name": "node-v23.2.0-linux-arm64-musl.tar.xz", + "browser_download_url": "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v23.2.0-linux-arm64-musl.tar.xz" + }, + { + "name": "node-v22.16.0-linux-x64-musl.tar.xz", + "browser_download_url": "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v22.16.0-linux-x64-musl.tar.xz" + } + ] +} +EOF + +selected_url=$(oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "22.16.0") || fail "select compatible ARM64 musl asset" +[ "$selected_url" = "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v23.2.0-linux-arm64-musl.tar.xz" ] || fail "selected asset should be newest compatible ARM64 musl release" + +if oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "24.0.0" >/dev/null 2>&1; then + fail "asset selection should fail when no compatible version exists" +fi + echo "ok"