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-openclaw 的 node-bins release 是否存在满足 >=22.16.0 的 linux-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"