7 Commits

Author SHA1 Message Date
mm644706215
156837877d fix: open console in a new window 2026-03-26 14:08:52 +08:00
mm644706215
2816ba19c3 fix: normalize shell scripts to lf 2026-03-26 11:42:45 +08:00
mm644706215
5e7d0e4b95 chore: bump plugin version to 2.0.1 2026-03-26 11:29:40 +08:00
mm644706215
5c0a368604 feat: upgrade arm64 musl node runtime to 24.14.1 2026-03-25 22:19:24 +08:00
mm644706215
e73a574db0 fix: add gitea fallback for arm64 musl node downloads 2026-03-22 23:30:57 +08:00
mm644706215
182623f67c Auto-link legacy ARM64 musl node assets 2026-03-21 15:38:26 +08:00
mm644706215
40e0704dc9 Use hotwa node-bins release by default 2026-03-21 15:30:55 +08:00
13 changed files with 338 additions and 82 deletions

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
*.sh text eol=lf
*.bash text eol=lf
Makefile text eol=lf
VERSION text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.lua text eol=lf
*.htm text eol=lf
*.md text eol=lf

View File

@@ -9,7 +9,7 @@ on:
default: true
type: boolean
build_v2:
description: 'Build V2 (22.16.0) - Current version'
description: 'Build V2 (24.14.1) - Current LTS version'
required: false
default: true
type: boolean
@@ -89,7 +89,7 @@ jobs:
name: node-arm64-musl-v1
path: dist/*.tar.xz
# ── V2 构建: 使用交叉编译模式 (22.16.0) ──
# ── V2 构建: 使用 Alpine edge apk 模式 (24.14.1 LTS) ──
build-v2:
name: Build Node.js V2 (Current)
if: ${{ inputs.build_v2 }}
@@ -108,23 +108,26 @@ jobs:
with:
platforms: linux/arm64
- name: Build Node.js V2 ARM64 musl (apk current mode)
- name: Build Node.js V2 ARM64 musl (apk lts mode)
run: |
NODE_VER="22.16.0"
NODE_VER="24.14.1"
mkdir -p dist
echo "=== Building Node.js v${NODE_VER}+ ARM64 musl (apk current mode) ==="
echo "=== Building Node.js v${NODE_VER} ARM64 musl (apk lts mode) ==="
docker run --rm --platform linux/arm64 \
-v "$PWD/dist:/output" \
-v "$PWD/scripts/build-node-musl.sh:/build-node-musl.sh:ro" \
-e "NODE_VER=${NODE_VER}" \
-e "BUILD_MODE=apk" \
-e "PKG_TYPE=current" \
alpine:3.21 sh /build-node-musl.sh
-e "PKG_TYPE=lts" \
alpine:edge sh /build-node-musl.sh
- name: Build info
id: build_info
run: |
EXPECTED_ARTIFACT="node-v24.14.1-linux-arm64-musl.tar.xz"
test -f "dist/${EXPECTED_ARTIFACT}"
ARTIFACT_NAME=$(ls dist/*.tar.xz | head -1 | xargs basename)
test "${ARTIFACT_NAME}" = "${EXPECTED_ARTIFACT}"
echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
echo "Artifact: ${ARTIFACT_NAME}"
@@ -211,13 +214,13 @@ jobs:
| 文件 | 版本 | 用途 |
|------|------|------|
| `node-v23.x.x-linux-arm64-musl.tar.xz` | V2 (当前) | OpenClaw v2026.3.11+ (要求 >= 22.16.0) |
| `node-v24.14.1-linux-arm64-musl.tar.xz` | V2 (当前默认) | OpenClaw v2026.3.11+ (默认下载,满足 >= 22.16.0) |
| `node-v22.x.x-linux-arm64-musl.tar.xz` | V1 (旧版) | OpenClaw v2026.3.8 及更早版本 |
## 构建模式
两种版本均使用 **Alpine apk 模式** 构建:
- **V2**: 使用 Alpine `nodejs-current` 包 (v23.x)满足 OpenClaw >= 22.16.0 的要求
- **V2**: 使用 Alpine `edge` 的 `nodejs` LTS 包 (v24.x)当前默认发布 `node-v24.14.1-linux-arm64-musl.tar.xz`
- **V1**: 使用 Alpine `nodejs` LTS 包 (v22.x),兼容旧版 OpenClaw
`openclaw-env setup` 会在 ARM64 musl 设备上自动从此处下载合适的版本
`openclaw-env setup` 会在 ARM64 musl 设备上默认直连下载 V2 当前版本,必要时再回退到 Release 元数据解析

View File

@@ -4,6 +4,21 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
## [2.0.1] - 2026-03-26
### 新增
- **ARM64 musl Node.js 双源下载**: `openclaw-env` 现在默认优先从 GitHub `node-bins` release 下载 Node.js失败后自动回退到 Gitea 镜像 release
- **Node.js 24 预编译资产**: 新增 `node-v24.14.1-linux-arm64-musl.tar.xz`,供 ARM64 OpenWrt/iStoreOS 设备直接使用
### 变更
- **插件版本升级**: LuCI 插件包版本提升到 `2.0.1`,便于 iStore / opkg 识别升级
- **默认 Node.js 版本升级**: ARM64 musl 默认下载版本从 `23.2.0` 升级到 `24.14.1`
- **构建链路更新**: V2 Node.js musl 资产改为使用 Alpine `edge``nodejs` LTS 包构建,并校验产物必须精确命中 `24.14.1`
### 适配
- **适配 OpenClaw 3.22-2**: 插件侧安装脚本与默认运行时已按 OpenClaw `2026.3.22` 版本链路完成同步LuCI 页面中的已验证版本同步更新
- **Gitea 镜像同步**: `node-bins` release 的 GitHub / Gitea 两侧资产与说明已保持一致,确保 ARM64 musl 设备在主源异常时仍可回退下载
## [2.0.0] - 2026-03-16
### 重大变更

View File

@@ -1 +1 @@
2.0.0
2.0.1

View File

@@ -253,7 +253,7 @@ act.cfgvalue = function(self, section)
html[#html+1] = '}'
-- 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("🧩 <b>ARM64 musl Node.js 资产不可用</b> — 当前设备依赖仓库发布的 <code>node-bins</code> 资产,而不是通用 Node 镜像。<br/>&nbsp;&nbsp;💡 解决: 检查 <code>10000ge10000/luci-app-openclaw</code> 的 <code>node-bins</code> release 是否存在满足 <code>>=22.16.0</code> 的 <code>linux-arm64-musl</code> 资产,并确认路由器可访问 GitHub API 与 release 页面。");'
html[#html+1] = 'reasons.push("🧩 <b>ARM64 musl Node.js 资产不可用</b> — 当前设备依赖仓库发布的 <code>node-bins</code> 资产,而不是通用 Node 镜像。<br/>&nbsp;&nbsp;💡 解决: 检查 <code>hotwa/luci-app-openclaw</code> 的 <code>node-bins</code> release 是否存在满足 <code>>=22.16.0</code> 的 <code>linux-arm64-musl</code> 资产,并确认路由器可访问 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))){'

View File

@@ -51,17 +51,53 @@ local port = uci:get("openclaw", "main", "port") or "18789"
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
#oc-console-iframe { width: 100%; height: 700px; border: none; display: block; }
.oc-console-loading {
display: flex; flex-direction: column; align-items: center; justify-content: center;
height: 300px; color: #666; font-size: 14px; background: #fafafa;
min-height: 320px; color: #666; font-size: 14px; background: #fafafa;
padding: 32px 24px;
text-align: center;
}
.oc-console-loading .spinner {
width: 32px; height: 32px; border: 3px solid #e0e0e0; border-top: 3px solid #4a90d9;
border-radius: 50%; animation: oc-spin .8s linear infinite; margin-bottom: 12px;
}
@keyframes oc-spin { to { transform: rotate(360deg); } }
.oc-console-note {
max-width: 640px;
line-height: 1.8;
}
.oc-console-note strong {
color: #333;
}
.oc-console-note code {
padding: 2px 6px;
background: #f0f3f6;
border-radius: 4px;
font-family: monospace;
}
.oc-console-actions {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
margin-top: 16px;
}
.oc-console-link {
font-size: 12px;
color: #666;
word-break: break-all;
margin-top: 14px;
}
.oc-console-link a {
color: #4a90d9;
text-decoration: none;
}
</style>
<div class="oc-page-header">
@@ -107,13 +143,28 @@ local port = uci:get("openclaw", "main", "port") or "18789"
var openBtn = document.getElementById('oc-console-open-btn');
function getConsoleUrl() {
var proto = window.location.protocol;
var host = window.location.hostname;
var url = proto + '//' + host + ':' + gwPort + '/';
var url = 'http://' + host + ':' + gwPort + '/';
if (gwToken) url += '#token=' + encodeURIComponent(gwToken);
return url;
}
function showOpenMessage(url) {
loading.innerHTML =
'<div class="oc-console-note">' +
'<div style="font-size:42px;margin-bottom:12px;">🪟</div>' +
'<div style="font-size:16px;margin-bottom:8px;"><strong>请在新窗口中打开 OpenClaw 控制台</strong></div>' +
'<div style="font-size:13px;color:#666;">' +
'OpenClaw 控制台会返回浏览器安全头,拒绝被 LuCI 页面内嵌。' +
'请点击上方「新窗口打开」访问控制台。' +
'</div>' +
'<div class="oc-console-actions">' +
'<a class="btn cbi-button cbi-button-action" href="' + url + '" target="_blank" rel="noopener">↗ 新窗口打开</a>' +
'</div>' +
'<div class="oc-console-link">直接地址:<a href="' + url + '" target="_blank" rel="noopener">' + url + '</a></div>' +
'</div>';
}
function checkAndLoad() {
addrEl.textContent = window.location.hostname + ':' + gwPort;
@@ -135,7 +186,7 @@ local port = uci:get("openclaw", "main", "port") or "18789"
statusTextEl.innerHTML = '<span style="color:#1a7f37;">● 网关运行中</span>';
openBtn.href = url;
openBtn.style.display = '';
showIframe(url);
showOpenMessage(url);
} else if (d.gateway_starting) {
statusTextEl.innerHTML = '<span style="color:#9a6700;">⏳ 网关正在启动</span>';
openBtn.style.display = 'none';
@@ -160,21 +211,6 @@ local port = uci:get("openclaw", "main", "port") or "18789"
});
}
function showIframe(url) {
loading.style.display = 'none';
var existing = document.getElementById('oc-console-iframe');
if (existing) return;
var iframe = document.createElement('iframe');
iframe.id = 'oc-console-iframe';
iframe.src = url;
iframe.style.width = '100%';
iframe.style.height = '700px';
iframe.style.border = 'none';
iframe.setAttribute('allowfullscreen', 'true');
container.appendChild(iframe);
}
checkAndLoad();
})();
//]]>

View File

@@ -15,14 +15,14 @@ set -e
. /usr/libexec/openclaw-node.sh
# ── Node.js 版本策略 ──
# V2: 当前推荐版本,用于 OpenClaw v2026.3.11+ (要求 >= 22.16.0)
# V2: 当前推荐版本,用于 OpenClaw v2026.3.11+ (默认下载 24.14.1,满足 >= 22.16.0)
# V1: 保留给显式指定旧版环境时使用,不再作为 V2 的自动回退
NODE_VERSION_V2="22.16.0"
NODE_VERSION_V2="24.14.1"
NODE_VERSION_V1="22.15.1"
# 默认使用 V2 版本 (可通过 NODE_VERSION 环境变量覆盖)
NODE_VERSION="${NODE_VERSION:-${NODE_VERSION_V2}}"
# 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
OC_TESTED_VERSION="2026.3.13"
OC_TESTED_VERSION="2026.3.22"
# 用户可通过 OC_VERSION 环境变量覆盖安装版本
OC_VERSION="${OC_VERSION:-}"
oc_load_paths "$OPENCLAW_INSTALL_ROOT"
@@ -55,13 +55,16 @@ PNPM_BIN="${OC_GLOBAL}/bin/pnpm"
# Node.js 下载源
OPENCLAW_GITHUB_REPO="${OPENCLAW_GITHUB_REPO:-hotwa/luci-app-openclaw}"
OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-10000ge10000/luci-app-openclaw}"
OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_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"
NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}"
NODE_SELF_HOST_FALLBACK="${NODE_SELF_HOST_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/download/node-bins}"
NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}"
NODE_RELEASE_API_FALLBACK="${NODE_RELEASE_API_FALLBACK:-https://gitea.jmsu.top/api/v1/repos/lingyuzeng/luci-app-openclaw/releases/tags/node-bins}"
NODE_RELEASE_PAGE="${NODE_RELEASE_PAGE:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/tag/node-bins}"
NODE_RELEASE_PAGE_FALLBACK="${NODE_RELEASE_PAGE_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/tag/node-bins}"
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH"
export NODE_ICU_DATA="${NODE_BASE}/share/icu"
@@ -82,32 +85,36 @@ resolve_arm64_musl_node_url() {
local node_ver="$1"
local release_json="/tmp/openclaw-node-bins-release.json"
local asset_url=""
local release_api=""
echo " 正在从 ${NODE_RELEASE_API} 获取 ARM64 musl 版本列表..." >&2
if ! download_url_to_file "$NODE_RELEASE_API" "$release_json"; then
for release_api in "$NODE_RELEASE_API" "$NODE_RELEASE_API_FALLBACK"; do
[ -n "$release_api" ] || continue
echo " 正在从 ${release_api} 获取 ARM64 musl 版本列表..." >&2
if ! download_url_to_file "$release_api" "$release_json"; then
rm -f "$release_json"
continue
fi
asset_url=$(oc_select_node_release_asset_url "$release_json" "linux-arm64" "$node_ver" || true)
rm -f "$release_json"
log_error "无法获取 ARM64 musl Node.js 发布元数据" >&2
echo " 仓库: ${OPENCLAW_NODE_BINS_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
if [ -n "$asset_url" ]; then
printf '%s\n' "$asset_url"
return 0
fi
done
log_error "未找到兼容的 ARM64 musl Node.js 资产" >&2
echo " 仓库: ${OPENCLAW_NODE_BINS_REPO}" >&2
echo " 最低要求: v${node_ver}" >&2
echo " Release API: ${NODE_RELEASE_API}" >&2
if [ -n "$NODE_RELEASE_API_FALLBACK" ] && [ "$NODE_RELEASE_API_FALLBACK" != "$NODE_RELEASE_API" ]; then
echo " Fallback API: ${NODE_RELEASE_API_FALLBACK}" >&2
fi
echo " Release 页面: ${NODE_RELEASE_PAGE}" >&2
if [ -n "$NODE_RELEASE_PAGE_FALLBACK" ] && [ "$NODE_RELEASE_PAGE_FALLBACK" != "$NODE_RELEASE_PAGE" ]; then
echo " Fallback 页面: ${NODE_RELEASE_PAGE_FALLBACK}" >&2
fi
echo " 请先发布 node-bins 中满足要求的 ARM64 musl 资产,或通过环境变量覆盖下载源" >&2
return 1
}
@@ -152,16 +159,26 @@ $ver_dir"
done
local d
echo "$search_dirs" | while read -r d; do
local found=""
while IFS= read -r d; do
[ -z "$d" ] && continue
if [ -f "${d}/openclaw.mjs" ]; then
echo "${d}/openclaw.mjs"
return
found="${d}/openclaw.mjs"
break
elif [ -f "${d}/dist/cli.js" ]; then
echo "${d}/dist/cli.js"
return
found="${d}/dist/cli.js"
break
fi
done
done <<EOF
$search_dirs
EOF
if [ -n "$found" ]; then
printf '%s\n' "$found"
return 0
fi
return 1
}
detect_arch() {
@@ -219,11 +236,20 @@ download_node() {
echo ""
echo "=== 下载 Node.js v${node_ver} (${node_arch}, musl libc) ==="
if [ "$node_arch" = "linux-arm64" ]; then
# ARM64 musl: 从当前维护仓库的 node-bins release 动态选择兼容资产
arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver") || exit 1
mirror_list="${arm64_musl_url}"
else
if [ "$node_arch" = "linux-arm64" ]; then
# ARM64 musl: GitHub 直链优先Gitea 镜像兜底API 解析作为兼容补充
mirror_list="${NODE_SELF_HOST}/${musl_tarball}"
if [ -n "$NODE_SELF_HOST_FALLBACK" ] && [ "$NODE_SELF_HOST_FALLBACK" != "$NODE_SELF_HOST" ]; then
mirror_list="$mirror_list ${NODE_SELF_HOST_FALLBACK}/${musl_tarball}"
fi
arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver" 2>/dev/null || true)
if [ -n "$arm64_musl_url" ] && [ "$arm64_musl_url" != "${NODE_SELF_HOST}/${musl_tarball}" ]; then
case " $mirror_list " in
*" ${arm64_musl_url} "*) ;;
*) mirror_list="$mirror_list ${arm64_musl_url}" ;;
esac
fi
else
# x64 musl: unofficial-builds 提供
# 1) unofficial-builds
mirror_list="${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}"
@@ -307,6 +333,22 @@ download_node() {
log_info "Node.js ${installed_ver} 安装成功"
return 0
fi
if [ "$libc_type" = "musl" ] && [ "$node_arch" = "linux-arm64" ] && \
[ "$OPENCLAW_INSTALL_ROOT" != "/opt" ] && oc_node_requires_opt_compat "$NODE_BIN"; then
log_warn "检测到旧版 ARM64 musl Node.js 资产,尝试创建 /opt 兼容链接"
if oc_ensure_opt_compat_link "$OC_ROOT"; then
installed_ver=$(oc_read_node_version "$NODE_BIN" || true)
if [ -n "$installed_ver" ] && oc_node_version_ge "$installed_ver" "$node_ver"; then
log_warn "已启用兼容链接: /opt/openclaw -> ${OC_ROOT}"
log_info "Node.js ${installed_ver} 安装成功"
return 0
fi
else
log_warn "无法创建 /opt/openclaw 兼容链接,请检查该路径是否已被其他安装占用"
fi
fi
if [ -n "$installed_ver" ]; then
log_error "Node.js 版本过低: v${installed_ver} < v${node_ver}"
else

View File

@@ -1,6 +1,8 @@
#!/bin/sh
# Shared OpenClaw Node.js runtime/version helpers.
OPENCLAW_LEGACY_ARM64_MUSL_INTERPRETER="${OPENCLAW_LEGACY_ARM64_MUSL_INTERPRETER:-/opt/openclaw/node/lib/ld-musl-aarch64.so.1}"
oc_normalize_node_version() {
local version="${1:-}"
local old_ifs
@@ -67,6 +69,15 @@ oc_read_node_version() {
oc_normalize_node_version "$version"
}
oc_node_requires_opt_compat() {
local node_bin="${1:-}"
[ -n "$node_bin" ] || return 1
[ -f "$node_bin" ] || return 1
grep -aqF "$OPENCLAW_LEGACY_ARM64_MUSL_INTERPRETER" "$node_bin"
}
oc_select_node_release_asset_url() {
local json_file="${1:-}"
local node_arch="${2:-}"

View File

@@ -2,6 +2,7 @@
# Shared OpenClaw install-root and derived-path helpers.
OPENCLAW_DEFAULT_INSTALL_ROOT="${OPENCLAW_DEFAULT_INSTALL_ROOT:-/opt}"
OPENCLAW_OPT_COMPAT_ROOT="${OPENCLAW_OPT_COMPAT_ROOT:-/opt}"
oc_normalize_install_root() {
local path="$1"
@@ -66,6 +67,30 @@ oc_install_root_uses_opt_workaround() {
[ "$(oc_normalize_install_root "${1:-$OPENCLAW_INSTALL_ROOT}")" = "/opt" ]
}
oc_ensure_opt_compat_link() {
local target_root="$1"
local compat_root compat_link current_target
target_root="$(oc_normalize_install_root "$target_root")"
[ "$target_root" = "/opt" ] && return 0
compat_root="$(oc_normalize_install_root "$OPENCLAW_OPT_COMPAT_ROOT")"
compat_link="${compat_root}/openclaw"
if [ -L "$compat_link" ]; then
current_target="$(readlink "$compat_link" 2>/dev/null || true)"
[ "$current_target" = "$target_root" ] && return 0
return 1
fi
if [ -e "$compat_link" ]; then
return 1
fi
mkdir -p "$compat_root"
ln -s "$target_root" "$compat_link"
}
oc_print_env() {
oc_load_paths "$1"
cat <<EOF
@@ -74,5 +99,6 @@ OC_ROOT=$OC_ROOT
NODE_BASE=$NODE_BASE
OC_GLOBAL=$OC_GLOBAL
OC_DATA=$OC_DATA
OPENCLAW_OPT_COMPAT_ROOT=$OPENCLAW_OPT_COMPAT_ROOT
EOF
}

View File

@@ -0,0 +1,30 @@
#!/bin/sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
CONSOLE_VIEW="$REPO_ROOT/luasrc/view/openclaw/console.htm"
fail() {
echo "FAIL: $1" >&2
exit 1
}
grep -Fq "http://' + host + ':' + gwPort + '/'" "$CONSOLE_VIEW" || fail "console view should force http for the gateway URL"
grep -Fq '请点击上方「新窗口打开」访问控制台。' "$CONSOLE_VIEW" || fail "console view should explain that the console must open in a new window"
if grep -Fq "document.createElement('iframe')" "$CONSOLE_VIEW"; then
fail "console view should not embed the OpenClaw UI in an iframe"
fi
if grep -Fq 'window.location.protocol' "$CONSOLE_VIEW"; then
fail "console view should not reuse the LuCI page protocol for the gateway URL"
fi
cr=$(printf '\r')
if LC_ALL=C grep -q "$cr" "$CONSOLE_VIEW"; then
fail "console view should use LF line endings"
fi
echo "ok"

View File

@@ -12,6 +12,11 @@ BUILD_RUN="$REPO_ROOT/scripts/build_run.sh"
ENV_SCRIPT="$REPO_ROOT/root/usr/bin/openclaw-env"
CONTROLLER_SCRIPT="$REPO_ROOT/luasrc/controller/openclaw.lua"
BASIC_LUA="$REPO_ROOT/luasrc/model/cbi/openclaw/basic.lua"
PROFILE_SCRIPT="$REPO_ROOT/root/etc/profile.d/openclaw.sh"
UCI_DEFAULTS_SCRIPT="$REPO_ROOT/root/etc/uci-defaults/99-openclaw"
INIT_SCRIPT="$REPO_ROOT/root/etc/init.d/openclaw"
PATHS_HELPER="$REPO_ROOT/root/usr/libexec/openclaw-paths.sh"
NODE_HELPER="$REPO_ROOT/root/usr/libexec/openclaw-node.sh"
fail() {
echo "FAIL: $1" >&2
@@ -28,17 +33,34 @@ grep -Fq 'verify_prefix /opt/openclaw/node' "$WORKFLOW" || fail "workflow should
grep -Fq 'verify_prefix /tmp/custom-openclaw-root/openclaw/node' "$WORKFLOW" || fail "workflow should verify custom install path"
grep -Fq 'oc_node_version_ge "$installed_ver" "$node_ver"' "$ENV_SCRIPT" || fail "installer should enforce minimum node version after extraction"
grep -Fq 'NODE_VERSION_V2="24.14.1"' "$ENV_SCRIPT" || fail "installer should default V2 to Node.js 24.14.1"
grep -Fq "description: 'Build V2 (24.14.1) - Current LTS version'" "$WORKFLOW" || fail "workflow should describe V2 as Node.js 24.14.1 LTS"
grep -Fq 'NODE_VER="24.14.1"' "$WORKFLOW" || fail "workflow should request Node.js 24.14.1 for V2"
grep -Fq 'Build Node.js V2 ARM64 musl (apk lts mode)' "$WORKFLOW" || fail "workflow should build V2 in apk lts mode"
grep -Fq 'PKG_TYPE=lts' "$WORKFLOW" || fail "workflow should use Alpine nodejs LTS package for V2"
grep -Fq 'alpine:edge sh /build-node-musl.sh' "$WORKFLOW" || fail "workflow should build V2 from Alpine edge to get the latest Node.js 24 LTS package"
grep -Fq 'EXPECTED_ARTIFACT="node-v24.14.1-linux-arm64-musl.tar.xz"' "$WORKFLOW" || fail "workflow should require the exact Node.js 24.14.1 V2 artifact name"
grep -Fq '`node-v24.14.1-linux-arm64-musl.tar.xz`' "$WORKFLOW" || fail "release notes should mention the Node.js 24.14.1 ARM64 musl tarball"
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 keep hotwa as primary app repo"
grep -Fq 'OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-10000ge10000/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should default ARM64 musl node-bins to 10000ge10000 repo"
grep -Fq 'NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release URL from 10000ge10000 repo"
grep -Fq 'NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release API from 10000ge10000 repo"
grep -Fq 'OPENCLAW_NODE_BINS_REPO="${OPENCLAW_NODE_BINS_REPO:-hotwa/luci-app-openclaw}"' "$ENV_SCRIPT" || fail "installer should default ARM64 musl node-bins to hotwa repo"
grep -Fq 'NODE_SELF_HOST="${NODE_SELF_HOST:-https://github.com/${OPENCLAW_NODE_BINS_REPO}/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release URL from hotwa repo"
grep -Fq 'NODE_SELF_HOST_FALLBACK="${NODE_SELF_HOST_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/download/node-bins}"' "$ENV_SCRIPT" || fail "installer should define a Gitea mirror fallback for ARM64 musl downloads"
grep -Fq 'NODE_RELEASE_API="${NODE_RELEASE_API:-https://api.github.com/repos/${OPENCLAW_NODE_BINS_REPO}/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should derive node-bins release API from hotwa repo"
grep -Fq 'NODE_RELEASE_API_FALLBACK="${NODE_RELEASE_API_FALLBACK:-https://gitea.jmsu.top/api/v1/repos/lingyuzeng/luci-app-openclaw/releases/tags/node-bins}"' "$ENV_SCRIPT" || fail "installer should define a Gitea release API fallback"
grep -Fq 'NODE_RELEASE_PAGE_FALLBACK="${NODE_RELEASE_PAGE_FALLBACK:-https://gitea.jmsu.top/lingyuzeng/luci-app-openclaw/releases/tag/node-bins}"' "$ENV_SCRIPT" || fail "installer should define a Gitea release page fallback"
grep -Fq 'for release_api in "$NODE_RELEASE_API" "$NODE_RELEASE_API_FALLBACK"; do' "$ENV_SCRIPT" || fail "installer should retry ARM64 musl release API using the Gitea mirror"
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"
grep -Fq 'oc_node_requires_opt_compat "$NODE_BIN"' "$ENV_SCRIPT" || fail "installer should detect legacy opt-bound ARM64 musl node assets"
grep -Fq 'oc_ensure_opt_compat_link "$OC_ROOT"' "$ENV_SCRIPT" || fail "installer should create /opt compatibility symlink for legacy assets"
grep -Fq 'mirror_list="${NODE_SELF_HOST}/${musl_tarball}"' "$ENV_SCRIPT" || fail "installer should default ARM64 musl downloads to direct release asset URL"
grep -Fq 'mirror_list="$mirror_list ${NODE_SELF_HOST_FALLBACK}/${musl_tarball}"' "$ENV_SCRIPT" || fail "installer should try the Gitea mirror after GitHub"
grep -Fq 'arm64_musl_url=$(resolve_arm64_musl_node_url "$node_ver" 2>/dev/null || true)' "$ENV_SCRIPT" || fail "installer should keep API-based ARM64 musl asset discovery as fallback"
grep -Fq 'while IFS= read -r d; do' "$ENV_SCRIPT" || fail "installer should traverse OpenClaw entry candidates without a pipeline subshell"
if grep -Fq 'echo "$search_dirs" | while read -r d; do' "$ENV_SCRIPT"; then
fail "installer should not rely on a pipeline subshell for OpenClaw entry lookup"
fi
grep -Fq 'openclaw-paths.sh' "$MAKEFILE" || fail "package makefile should install path helper"
@@ -54,12 +76,30 @@ 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"
python - "$ENV_SCRIPT" "$PROFILE_SCRIPT" "$UCI_DEFAULTS_SCRIPT" "$INIT_SCRIPT" "$PATHS_HELPER" "$NODE_HELPER" "$BUILD_IPK" "$BUILD_RUN" "$BUILD_SCRIPT" <<'PY' || fail "shell-oriented source files must use LF line endings"
from pathlib import Path
import sys
bad = []
for arg in sys.argv[1:]:
data = Path(arg).read_bytes()
if b"\r\n" in data:
bad.append(arg)
if bad:
print("CRLF detected in:", file=sys.stderr)
for path in bad:
print(path, file=sys.stderr)
raise SystemExit(1)
PY
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"
grep -Fq "10000ge10000/luci-app-openclaw" "$BASIC_LUA" || fail "UI should point ARM64 musl guidance at the live node-bins repo"
grep -Fq "hotwa/luci-app-openclaw" "$BASIC_LUA" || fail "UI should point ARM64 musl guidance at hotwa repo"
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

View File

@@ -18,10 +18,10 @@ if oc_normalize_node_version "broken-version" >/dev/null 2>&1; then
fail "invalid version should not normalize"
fi
oc_node_version_ge "22.16.0" "22.16.0" || fail "exact version should satisfy requirement"
oc_node_version_ge "22.16.1" "22.16.0" || fail "newer patch should satisfy requirement"
oc_node_version_ge "23.0.0" "22.16.0" || fail "newer major should satisfy requirement"
if oc_node_version_ge "22.15.1" "22.16.0"; then
oc_node_version_ge "24.14.1" "24.14.1" || fail "exact version should satisfy requirement"
oc_node_version_ge "24.14.2" "24.14.1" || fail "newer patch should satisfy requirement"
oc_node_version_ge "25.0.0" "24.14.1" || fail "newer major should satisfy requirement"
if oc_node_version_ge "24.14.0" "24.14.1"; then
fail "older minor version should not satisfy requirement"
fi
@@ -51,17 +51,28 @@ 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-legacy-opt" <<'EOF'
#!/bin/sh
/opt/openclaw/node/lib/ld-musl-aarch64.so.1
EOF
chmod +x "$tmpdir/node-legacy-opt"
oc_node_requires_opt_compat "$tmpdir/node-legacy-opt" || fail "legacy opt-bound node binary should be detected"
if oc_node_requires_opt_compat "$tmpdir/node-ok" >/dev/null 2>&1; then
fail "modern runnable node helper should not require opt compatibility"
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/10000ge10000/luci-app-openclaw/releases/download/node-bins/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/10000ge10000/luci-app-openclaw/releases/download/node-bins/node-v23.2.0-linux-arm64-musl.tar.xz"
"name": "node-v24.14.1-linux-arm64-musl.tar.xz",
"browser_download_url": "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz"
},
{
"name": "node-v22.16.0-linux-x64-musl.tar.xz",
@@ -71,10 +82,25 @@ cat > "$tmpdir/node-bins-release.json" <<'EOF'
}
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/10000ge10000/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"
selected_url=$(oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "24.14.1") || fail "select compatible ARM64 musl asset"
[ "$selected_url" = "https://github.com/hotwa/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-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
cat > "$tmpdir/gitea-node-bins-release.json" <<'EOF'
{
"tag_name": "node-bins",
"assets": [
{
"name": "node-v24.14.1-linux-arm64-musl.tar.xz",
"browser_download_url": "http://100.64.0.27:8418/lingyuzeng/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz"
}
]
}
EOF
gitea_selected_url=$(oc_select_node_release_asset_url "$tmpdir/gitea-node-bins-release.json" "linux-arm64" "24.14.1") || fail "select compatible ARM64 musl asset from Gitea release JSON"
[ "$gitea_selected_url" = "http://100.64.0.27:8418/lingyuzeng/luci-app-openclaw/releases/download/node-bins/node-v24.14.1-linux-arm64-musl.tar.xz" ] || fail "selected Gitea asset should preserve browser_download_url"
if oc_select_node_release_asset_url "$tmpdir/node-bins-release.json" "linux-arm64" "24.14.2" >/dev/null 2>&1; then
fail "asset selection should fail when no compatible version exists"
fi

View File

@@ -27,4 +27,22 @@ trap 'rm -rf "$tmpdir"' EXIT INT TERM
existing=$(oc_find_existing_path "$tmpdir/missing/nested")
[ "$existing" = "$tmpdir" ] || fail "nearest existing path"
export OPENCLAW_OPT_COMPAT_ROOT="$tmpdir/compat-opt"
target_root="$tmpdir/install-root/openclaw"
mkdir -p "$target_root"
oc_ensure_opt_compat_link "$target_root" || fail "compat symlink should be created for custom install root"
[ -L "$OPENCLAW_OPT_COMPAT_ROOT/openclaw" ] || fail "compat symlink should exist"
[ "$(readlink "$OPENCLAW_OPT_COMPAT_ROOT/openclaw")" = "$target_root" ] || fail "compat symlink should point to install root"
oc_ensure_opt_compat_link "$target_root" || fail "compat symlink should be idempotent"
conflict_root="$tmpdir/conflict-openclaw"
mkdir -p "$conflict_root"
rm -f "$OPENCLAW_OPT_COMPAT_ROOT/openclaw"
ln -s "$conflict_root" "$OPENCLAW_OPT_COMPAT_ROOT/openclaw"
if oc_ensure_opt_compat_link "$target_root" >/dev/null 2>&1; then
fail "compat symlink should fail when pointing at another install root"
fi
echo "ok"