From 823a693761f17647d34591420d517a84b5d63a49 Mon Sep 17 00:00:00 2001 From: 10000ge10000 <10000ge10000@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:12:57 +0800 Subject: [PATCH] =?UTF-8?q?release:=20v1.0.2=20=E2=80=94=20ARM64=20musl=20?= =?UTF-8?q?patchelf=20=E4=BF=AE=E5=A4=8D=E3=80=81ICU=20=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 12 +++ README.md | 132 +++++++++++---------------- VERSION | 2 +- root/etc/init.d/openclaw | 2 + root/usr/bin/openclaw-env | 1 + root/usr/share/openclaw/oc-config.sh | 1 + scripts/build-node-musl.sh | 90 +++++++++++++++--- 7 files changed, 151 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d874fcd..3ac31b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 +## [1.0.2] - 2026-03-02 + +### 修复 +- **P0** ARM64 musl: Gateway 崩溃循环 — `process.execPath` 返回 musl 链接器路径导致 `child_process.fork()` 失败 + - 使用 `patchelf` 直接修改 node ELF 二进制的 interpreter 和 rpath,替代 ld-musl wrapper 方案 + - 子进程通过 `process.execPath` fork 时可正确找到 node 二进制 +- **P0** ARM64 musl: Unicode property escapes 正则失败 (`\p{Emoji_Presentation}`) — 缺少 `NODE_ICU_DATA` 环境变量 + - init.d、openclaw-env、oc-config.sh 所有入口均添加 `NODE_ICU_DATA` 环境变量 + +### 改进 +- `build-node-musl.sh` 构建验证阶段新增 `process.execPath` 输出检查 + ## [1.0.1] - 2026-03-02 ### 修复 diff --git a/README.md b/README.md index 9c07a62..3ac4a9e 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,60 @@ | 项目 | 要求 | |------|------| -| 架构 | x86_64 或 aarch64 | +| 架构 | x86_64 或 aarch64 (ARM64) | | C 库 | glibc 或 musl(自动检测) | | 依赖 | luci-compat, luci-base, curl, openssl-util | | 存储 | 1.5GB 以上可用空间 | | 内存 | 推荐 2GB 及以上 | +### 🖥️ 兼容性矩阵 + +#### 支持的架构 × C 库组合 + +| 架构 | C 库 | Node.js 来源 | 状态 | +|------|------|-------------|------| +| x86_64 | musl | [unofficial-builds.nodejs.org](https://unofficial-builds.nodejs.org/) | ✅ 已验证 | +| x86_64 | glibc | [nodejs.org](https://nodejs.org/) 官方 | ✅ 支持 | +| aarch64 | musl | 项目自托管(Alpine 打包,含完整依赖) | ✅ 已验证 | +| aarch64 | glibc | [nodejs.org](https://nodejs.org/) 官方 | ✅ 支持 | +| armv7l / armv6l | - | — | ❌ 不支持 | + +> **说明**:Node.js 22+ 不再提供 32 位 ARM 预编译包,因此不支持 armv7l/armv6l(如 MT7621 路由器)。 + +#### 支持的 OpenWrt 版本 + +| OpenWrt 版本 | LuCI 版本 | 验证状态 | 说明 | +|-------------|-----------|---------|------| +| 24.x (iStoreOS 24.10) | LuCI 24.x | ✅ 已验证 | 推荐版本 | +| 23.05 | LuCI openwrt-23.05 | ✅ 支持 | | +| 22.03 (iStoreOS 22.03) | LuCI openwrt-22.03 | ✅ 已验证 | 需自托管 Node.js(ARM64 musl) | +| 21.02 | LuCI openwrt-21.02 | ⚠️ 应兼容 | 未测试,procd / LuCI API 兼容 | +| 19.07 | LuCI openwrt-19.07 | ⚠️ 应兼容 | 未测试 | +| 18.06 及更早 | LuCI 旧版 | ❌ 不保证 | procd API 可能不兼容 | + +> 插件使用标准 procd init 和 LuCI CBI (luci-compat) 接口,理论上兼容 OpenWrt 19.07+。 + +#### 已验证的典型设备 + +| 设备 / 平台 | 架构 | 系统 | 验证结果 | +|------------|------|------|---------| +| N100 / N5105 软路由 | x86_64 musl | iStoreOS 24.10.5 | ✅ 通过 | +| 晶晨 S905 系列 (Cortex-A53) | aarch64 musl | iStoreOS 22.03.7 | ✅ 通过 | +| Raspberry Pi 4/5 | aarch64 | OpenWrt 23.05+ | ✅ 应支持 | +| FriendlyElec R4S/R5S | aarch64 | OpenWrt / FriendlyWrt | ✅ 应支持 | +| 通用 x86 虚拟机 (PVE/ESXi) | x86_64 | OpenWrt 22.03+ | ✅ 应支持 | +| MT7621 路由器 (如 Redmi AC2100) | mipsel / armv7l | — | ❌ 不支持 | + +#### ARM64 musl 特别说明 + +ARM64 + musl 的 OpenWrt 设备(绝大多数 ARM64 路由器)使用**项目自托管的 Node.js 包**: + +- 基于 Alpine Linux 3.21 ARM64 环境打包 +- 包含完整的共享库(libstdc++、libssl、libicu 等)和 musl 动态链接器 +- 包含完整 ICU 国际化数据(`icudt74l.dat`) +- 通过 wrapper 脚本使用打包的 musl 链接器启动,**不依赖系统库版本** +- 因此即使系统是 OpenWrt 22.03(musl 1.2.3)也能正常运行 Alpine 3.21 编译的 Node.js + ## 📦 安装 ### 方式一:.run 自解压包(推荐) @@ -85,23 +133,7 @@ sh /etc/uci-defaults/99-openclaw rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* ``` -## � 一键部署下载 - -OpenClaw 支持全平台一键部署,请根据你的设备选择对应方式。 - -> **💡 提示**:OpenWrt 路由器请直接使用下方 [📦 安装](#-安装) 章节的命令,无需使用本节下载包。 - -| 平台 | 下载链接 | 说明 | -|------|----------|------| -| 🐧 Linux (Ubuntu/Debian) | [夸克网盘](https://pan.quark.cn/s/c25bb5c20db1) | 或直接 `curl -fsSL "https://alist.910501.xyz/d/openclaw/install.sh?sign=RUSBfm1vy35Z-2S86e-Hr0s1bR2u_rATHXEpY888zi8=:0" \| bash` | -| 🪟 Windows (Win10/Win11) | [夸克网盘](https://pan.quark.cn/s/af01a152dad7) | 解压后右键「一键安装.bat」以管理员身份运行 | -| 🍎 macOS (Intel & Apple Silicon) | [夸克网盘](https://pan.quark.cn/s/99bad2c03e5c) | 解压后 `bash setup.sh` 授权,再双击「一键安装.command」 | -| 🐂 飞牛 NAS (FnOS) | [夸克网盘](https://pan.quark.cn/s/fea2552a1b73) | 离线 FPK 包,在应用商店「手动安装」 | -| 📡 OpenWrt 路由器 | [GitHub Releases](https://github.com/10000ge10000/luci-app-openclaw/releases/latest) | 见下方安装章节 | - ---- - -## �🔰 首次使用 +## 🔰 首次使用 1. 打开 LuCI → 服务 → OpenClaw,点击「安装运行环境」 2. 安装完成后服务会自动启动,点击「刷新页面」查看状态 @@ -134,64 +166,6 @@ luci-app-openclaw/ └── .github/workflows/build.yml # GitHub Actions ``` -## 📡 OpenWrt 路由器专属说明 - -### 为什么选择路由器部署? - -路由器 24 小时在线,天然适合作为 AI 网关的宿主——家里所有设备共享同一个 AI 服务,Telegram / Discord 消息也能全天候响应,无需常开电脑。 - -### 支持的设备 - -| 架构 | 典型设备 | 支持状态 | -|------|----------|----------| -| x86_64 | N100 / N5105 软路由、iStoreOS 小主机 | ✅ 完全支持 | -| aarch64 | Raspberry Pi 4/5、R4S、部分 ARM64 路由器 | ✅ 完全支持 | -| 32 位 ARM | 老款 MT7620 / MT7621 路由器 | ❌ 不支持(Node.js 22 无 32 位包) | - -### 安装步骤(OpenWrt / iStoreOS) - -**第一步:安装 LuCI 插件** - -```bash -# 推荐:.run 自解压包,一行搞定 -wget https://github.com/10000ge10000/luci-app-openclaw/releases/latest/download/luci-app-openclaw.run -sh luci-app-openclaw.run -``` - -**第二步:安装 OpenClaw 运行环境** - -打开 LuCI → **服务** → **OpenClaw** → 点击「📦 安装运行环境」,脚本会自动完成: -- 检测 CPU 架构(x86_64 / aarch64) -- 检测 C 库类型(glibc / musl,绝大多数 OpenWrt 为 musl) -- 下载对应 Node.js 22 预编译包 -- 安装 pnpm 和 OpenClaw 本体 - -> **网络慢?** 可在路由器 SSH 中指定国内镜像加速 Node.js 下载: -> ```bash -> NODE_MIRROR=https://npmmirror.com/mirrors/node openclaw-env setup -> ``` - -**第三步:配置 AI 模型和消息渠道** - -进入「**配置管理**」页面,在内嵌 Web 终端中使用交互式向导,选数字即可完成配置,支持: -- OpenAI / Anthropic Claude / Google Gemini / DeepSeek / GitHub Copilot / OpenRouter / 通义千问 / Grok / Groq / 硅基流动 等 12+ 家提供商 -- Telegram / Discord / 飞书 / Slack 消息渠道 - -**第四步:用 Telegram 与 AI 对话** - -配置完 Telegram Bot Token 后,重启网关即可在 Telegram 直接给 Bot 发消息,路由器全天候在线响应。 - -### 与其他平台脚本的区别 - -| 对比项 | Linux/Mac/Win 脚本 | OpenWrt 插件 | -|--------|-------------------|--------------| -| 管理界面 | 命令行菜单 | LuCI 可视化界面 | -| 开机自启 | 系统服务 / 守护进程 | procd 托管,崩溃自动重启 | -| 安装包格式 | .sh / .bat / .command | .run / .ipk | -| Node.js 来源 | 官方 + npm 镜像 | 自动检测 musl/glibc,按需拉取 | - ---- - ## ❓ 常见问题 **安装后 LuCI 菜单没有出现** @@ -218,7 +192,11 @@ NODE_MIRROR=https://npmmirror.com/mirrors/node openclaw-env setup **是否支持 ARM 路由器** -支持 aarch64(ARM64)。不支持 32 位 ARM,Node.js 22 没有 32 位预编译包。 +支持 aarch64(ARM64),包括晶晨 S905 系列、Raspberry Pi 4/5、R4S/R5S 等。ARM64 musl 设备使用项目自托管的 Node.js 包,自带完整依赖库,不依赖系统库版本。**不支持** 32 位 ARM(armv7l/armv6l),Node.js 22 没有 32 位预编译包。 + +**ARM64 设备安装后 Node.js 显示 Segmentation fault** + +旧版 OpenWrt(如 22.03)的系统 musl 版本较低,与新版 Node.js 不兼容。请确保使用最新版本的 `openclaw-env`(v1.0.1+),它会自动下载包含独立 musl 链接器的自托管 Node.js 包。 ## 🤝 贡献 diff --git a/VERSION b/VERSION index 7dea76e..6d7de6e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.1 +1.0.2 diff --git a/root/etc/init.d/openclaw b/root/etc/init.d/openclaw index 74be67b..c556e68 100755 --- a/root/etc/init.d/openclaw +++ b/root/etc/init.d/openclaw @@ -176,6 +176,7 @@ HOME="$OC_DATA" \ OPENCLAW_HOME="$OC_DATA" \ OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \ OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \ +NODE_ICU_DATA="${NODE_BASE}/share/icu" \ NODE_BASE="$NODE_BASE" \ OC_GLOBAL="$OC_GLOBAL" \ OC_DATA="$OC_DATA" \ @@ -204,6 +205,7 @@ procd_set_param env \ OC_CONFIG_PORT="$pty_port" \ OC_PTY_TOKEN="$pty_token" \ OC_CONFIG_SCRIPT="/usr/share/openclaw/oc-config.sh" \ +NODE_ICU_DATA="${NODE_BASE}/share/icu" \ NODE_BASE="$NODE_BASE" \ OC_GLOBAL="$OC_GLOBAL" \ OC_DATA="$OC_DATA" \ diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env index a6fdbf5..f2913df 100755 --- a/root/usr/bin/openclaw-env +++ b/root/usr/bin/openclaw-env @@ -32,6 +32,7 @@ NODE_MUSL_MIRROR="https://unofficial-builds.nodejs.org/download/release" NODE_SELF_HOST="https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins" export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH" +export NODE_ICU_DATA="${NODE_BASE}/share/icu" log_info() { echo " [✓] $1"; } log_warn() { echo " [!] $1"; } diff --git a/root/usr/share/openclaw/oc-config.sh b/root/usr/share/openclaw/oc-config.sh index 61d9086..b5fcd61 100755 --- a/root/usr/share/openclaw/oc-config.sh +++ b/root/usr/share/openclaw/oc-config.sh @@ -40,6 +40,7 @@ export HOME="$OC_DATA" export OPENCLAW_HOME="$OC_DATA" export OPENCLAW_STATE_DIR="$OC_STATE_DIR" export OPENCLAW_CONFIG_PATH="$CONFIG_FILE" +export NODE_ICU_DATA="${NODE_BASE}/share/icu" export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" # ── 查找 openclaw 入口 ── diff --git a/scripts/build-node-musl.sh b/scripts/build-node-musl.sh index 31158c0..d3cd442 100755 --- a/scripts/build-node-musl.sh +++ b/scripts/build-node-musl.sh @@ -1,9 +1,17 @@ #!/bin/sh # 在 Alpine ARM64 Docker 容器内运行 # 环境变量: NODE_VER (目标版本号), /output (输出目录) +# +# 打包策略: +# 使用 patchelf 修改 node 二进制的 ELF interpreter 和 rpath, +# 使其直接使用打包的 musl 链接器和共享库,无需 LD_LIBRARY_PATH。 +# 这样 process.execPath 返回正确的 node 路径,子进程 fork 也能正常工作。 +# 安装路径固定为 /opt/openclaw/node (与 openclaw-env 一致)。 set -e -apk add --no-cache nodejs npm xz +INSTALL_PREFIX="/opt/openclaw/node" + +apk add --no-cache nodejs npm xz icu-data-full patchelf ACTUAL_VER=$(node --version | sed 's/^v//') echo "Alpine Node.js version: v${ACTUAL_VER} (requested: v${NODE_VER})" @@ -13,27 +21,87 @@ PKG_NAME="node-v${NODE_VER}-linux-arm64-musl" PKG_DIR="/tmp/${PKG_NAME}" mkdir -p "${PKG_DIR}/bin" "${PKG_DIR}/lib/node_modules" "${PKG_DIR}/include/node" -cp "$(which node)" "${PKG_DIR}/bin/" +# 复制 node 二进制 +cp "$(which node)" "${PKG_DIR}/bin/node" chmod +x "${PKG_DIR}/bin/node" +# 收集 node 依赖的所有共享库 (Alpine node 是动态链接的) +echo "=== Collecting shared libraries ===" +LIB_DIR="${PKG_DIR}/lib" +ldd "$(which node)" 2>/dev/null | while read -r line; do + # 解析 ldd 输出: libxxx.so => /usr/lib/libxxx.so (0x...) + lib_path=$(echo "$line" | grep -oE '/[^ ]+\.so[^ ]*' | head -1) + if [ -n "$lib_path" ] && [ -f "$lib_path" ]; then + cp -L "$lib_path" "$LIB_DIR/" 2>/dev/null || true + echo " + $(basename "$lib_path")" + fi +done +# 确保 musl 动态链接器也在 +if [ -f /lib/ld-musl-aarch64.so.1 ]; then + cp -L /lib/ld-musl-aarch64.so.1 "$LIB_DIR/" 2>/dev/null || true + echo " + ld-musl-aarch64.so.1" +fi +echo "Libraries collected: $(ls "$LIB_DIR"/*.so* 2>/dev/null | wc -l) files" + +# 用 patchelf 修改 node 二进制: +# - interpreter 指向打包的 musl 链接器 (绝对路径,对应安装后的位置) +# - rpath 指向打包的 lib 目录 +echo "=== Patching ELF binary ===" +patchelf --set-interpreter "${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1" "${PKG_DIR}/bin/node" +patchelf --set-rpath "${INSTALL_PREFIX}/lib" "${PKG_DIR}/bin/node" +echo " interpreter: ${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1" +echo " rpath: ${INSTALL_PREFIX}/lib" + +# 复制 ICU 完整数据 (npm 的 Intl.Collator 需要) +echo "=== Copying ICU data ===" +ICU_DAT=$(find /usr/share/icu -name "icudt*.dat" 2>/dev/null | head -1) +if [ -n "$ICU_DAT" ] && [ -f "$ICU_DAT" ]; then + mkdir -p "${PKG_DIR}/share/icu" + cp "$ICU_DAT" "${PKG_DIR}/share/icu/" + echo " + $(basename "$ICU_DAT") ($(du -h "$ICU_DAT" | cut -f1))" +else + echo " WARNING: ICU data file not found" +fi + +# 创建 node wrapper 脚本 (只设置 NODE_ICU_DATA,ELF 层面已解决链接器和库路径) +cat > "${PKG_DIR}/bin/node-wrapper" << 'NODEWRAPPER' +#!/bin/sh +SELF_DIR="$(cd "$(dirname "$0")" && pwd)" +export NODE_ICU_DATA="${SELF_DIR}/../share/icu" +exec "${SELF_DIR}/node" "$@" +NODEWRAPPER +chmod +x "${PKG_DIR}/bin/node-wrapper" + # 复制 npm if [ -d /usr/lib/node_modules/npm ]; then cp -r /usr/lib/node_modules/npm "${PKG_DIR}/lib/node_modules/" fi -# 创建 npm wrapper -printf '#!/bin/sh\nexec "$(dirname "$0")/node" "$(dirname "$0")/../lib/node_modules/npm/bin/npm-cli.js" "$@"\n' \ - > "${PKG_DIR}/bin/npm" +# 创建 npm wrapper (直接调用 patchelf 后的 node,只需设置 ICU) +cat > "${PKG_DIR}/bin/npm" << 'NPMWRAPPER' +#!/bin/sh +SELF_DIR="$(cd "$(dirname "$0")" && pwd)" +export NODE_ICU_DATA="${SELF_DIR}/../share/icu" +exec "${SELF_DIR}/node" "${SELF_DIR}/../lib/node_modules/npm/bin/npm-cli.js" "$@" +NPMWRAPPER # 创建 npx wrapper -printf '#!/bin/sh\nexec "$(dirname "$0")/node" "$(dirname "$0")/../lib/node_modules/npm/bin/npx-cli.js" "$@"\n' \ - > "${PKG_DIR}/bin/npx" +cat > "${PKG_DIR}/bin/npx" << 'NPXWRAPPER' +#!/bin/sh +SELF_DIR="$(cd "$(dirname "$0")" && pwd)" +export NODE_ICU_DATA="${SELF_DIR}/../share/icu" +exec "${SELF_DIR}/node" "${SELF_DIR}/../lib/node_modules/npm/bin/npx-cli.js" "$@" +NPXWRAPPER chmod +x "${PKG_DIR}/bin/npm" "${PKG_DIR}/bin/npx" -# 验证 +# 验证 (需要将打包内容放到目标路径来测试 patchelf 结果) echo "=== Verification ===" -"${PKG_DIR}/bin/node" --version -"${PKG_DIR}/bin/node" -e "console.log(process.arch, process.platform, process.versions.modules)" -"${PKG_DIR}/bin/npm" --version 2>/dev/null || echo "npm wrapper created" +mkdir -p "${INSTALL_PREFIX}" +cp -a "${PKG_DIR}"/* "${INSTALL_PREFIX}/" +"${INSTALL_PREFIX}/bin/node" --version +"${INSTALL_PREFIX}/bin/node" -e "console.log('execPath:', process.execPath)" +"${INSTALL_PREFIX}/bin/node" -e "console.log(process.arch, process.platform, process.versions.modules)" +NODE_ICU_DATA="${INSTALL_PREFIX}/share/icu" "${INSTALL_PREFIX}/bin/npm" --version 2>/dev/null || echo "npm needs ICU data" +rm -rf "${INSTALL_PREFIX}" # 打包 cd /tmp