release: v1.0.15 — QQBot 死锁修复、离线安装包、musl 架构支持

This commit is contained in:
10000ge10000
2026-03-13 00:20:08 +08:00
parent a99283aee2
commit 3aa39512c0
18 changed files with 1870 additions and 170 deletions

304
scripts/download_deps.sh Executable file
View File

@@ -0,0 +1,304 @@
#!/bin/sh
# ============================================================================
# 离线依赖预下载脚本 (在有网络的构建机上运行)
# 为所有支持的架构下载 Node.js + OpenClaw + pnpm
#
# 用法:
# sh scripts/download_deps.sh [cache_dir]
#
# 产出目录结构:
# cache_dir/
# node/
# node-v22.15.1-linux-x64-musl.tar.xz
# node-v22.15.1-linux-arm64-musl.tar.xz
# openclaw/
# openclaw-deps-v<version>.tar.gz (完整 node_modules, 跨架构通用, ~150MB)
# ============================================================================
set -e
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
CACHE_DIR="${1:-$PKG_DIR/.offline-cache}"
# 确保 CACHE_DIR 是绝对路径
case "$CACHE_DIR" in
/*) ;;
*) CACHE_DIR="$PKG_DIR/$CACHE_DIR" ;;
esac
# ── 版本配置 (与 openclaw-env 保持一致) ──
NODE_VERSION="${NODE_VERSION:-22.15.1}"
OC_VERSION="${OC_VERSION:-2026.3.8}"
# ── 下载镜像 ──
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="https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins"
NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}"
log_info() { echo " [✓] $1"; }
log_warn() { echo " [!] $1"; }
log_error() { echo " [✗] $1"; }
# 自动检测 Node.js / npm (兼容 OpenWrt 上已安装的 openclaw 环境)
if ! command -v node >/dev/null 2>&1; then
for try_path in /opt/openclaw/node/bin /usr/local/bin; do
if [ -x "$try_path/node" ]; then
export PATH="$try_path:$PATH"
log_info "检测到 Node.js: $try_path/node"
break
fi
done
fi
# 下载文件 (支持 curl 和 wget)
download_file() {
local url="$1" dest="$2"
if [ -f "$dest" ]; then
local fsize=$(wc -c < "$dest" 2>/dev/null || echo 0)
if [ "$fsize" -gt 1000000 ] 2>/dev/null; then
log_info "已缓存: $(basename "$dest") ($(du -h "$dest" | cut -f1))"
return 0
fi
fi
echo " 下载: $url"
if curl -fSL --connect-timeout 30 --max-time 600 -o "$dest" "$url" 2>/dev/null; then
return 0
elif wget -q --timeout=30 -O "$dest" "$url" 2>/dev/null; then
return 0
fi
rm -f "$dest"
return 1
}
# ============================================================================
# Phase 1: 下载 Node.js (全架构)
# ============================================================================
download_all_node() {
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
printf "║ [1/3] 下载 Node.js v%-8s (musl 架构) ║\n" "$NODE_VERSION"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
local node_dir="$CACHE_DIR/node"
mkdir -p "$node_dir"
# x86_64 musl
echo "=== x86_64 musl ==="
local x64_musl="node-v${NODE_VERSION}-linux-x64-musl.tar.xz"
download_file "${NODE_MUSL_MIRROR}/v${NODE_VERSION}/${x64_musl}" "$node_dir/$x64_musl" || \
download_file "${NODE_MIRROR_CN}/v${NODE_VERSION}/${x64_musl}" "$node_dir/$x64_musl" || \
log_error "x86_64 musl 下载失败"
# aarch64 musl (项目自托管)
echo "=== aarch64 musl ==="
local arm64_musl="node-v${NODE_VERSION}-linux-arm64-musl.tar.xz"
download_file "${NODE_SELF_HOST}/${arm64_musl}" "$node_dir/$arm64_musl" || \
download_file "${NODE_MUSL_MIRROR}/v${NODE_VERSION}/${arm64_musl}" "$node_dir/$arm64_musl" || \
log_error "aarch64 musl 下载失败"
echo ""
echo "Node.js 下载完成:"
ls -lh "$node_dir/"*.tar.xz 2>/dev/null || echo " (无文件)"
}
# ============================================================================
# Phase 2: 下载并预装 OpenClaw + 依赖
# ============================================================================
download_openclaw_deps() {
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
printf "║ [2/3] 下载 OpenClaw v%-8s + 全部依赖 ║\n" "$OC_VERSION"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
local oc_dir="$CACHE_DIR/openclaw"
mkdir -p "$oc_dir"
# 检查 npm 是否可用 (构建机上需要 node + npm)
local NPM_CMD=""
if command -v npm >/dev/null 2>&1; then
NPM_CMD="npm"
elif [ -x /opt/openclaw/node/bin/npm ]; then
# OpenWrt 上 npm wrapper 可能需要显式 node 调用
NPM_CMD="/opt/openclaw/node/bin/node /opt/openclaw/node/bin/npm"
else
log_error "构建机上需要 npm"
log_error "请执行: apt install -y nodejs npm 或 apk add nodejs npm"
log_error "或者确保 /opt/openclaw/node 中有 Node.js"
exit 1
fi
log_info "使用 npm: $NPM_CMD"
# 方案: 使用 npm install 到临时目录,然后打包整个 node_modules
# 这是最可靠的方式,确保所有依赖树完整
local tmp_install="/tmp/openclaw-offline-$$"
trap "rm -rf '$tmp_install'" EXIT
# ── 为每种架构生成预安装包 ──
# 注意: openclaw 的依赖树中可能包含平台特定的 optional dependencies
# musl 环境下用 --ignore-scripts 跳过原生编译
# 对于离线包,我们在构建机上安装后直接打包 node_modules
# 通用安装 (忽略平台特定编译脚本)
echo "=== 安装 OpenClaw 依赖 (通用包) ==="
mkdir -p "$tmp_install/global"
# ── 强制 npm 使用 musl 平台检测 ──
# 目标系统是 OpenWrt/iStoreOS (musl libc),无论构建机是什么系统
# 在 glibc 系统 (如 GitHub Actions ubuntu-latest) 上npm 默认安装 *-gnu 变体
# 的原生可选依赖 (如 @napi-rs/canvas-linux-x64-gnu),比 *-musl 变体大得多
# 通过设置 npm_config_libc=musl 强制安装 musl 变体,确保跨平台一致的产物大小
export npm_config_os=linux
export npm_config_libc=musl
echo " 正在用 npm 安装 openclaw@${OC_VERSION}..."
echo " npm 平台覆盖: os=${npm_config_os}, libc=${npm_config_libc}"
$NPM_CMD install -g "openclaw@${OC_VERSION}" \
--prefix="$tmp_install/global" \
--ignore-scripts \
--omit=dev \
--omit=optional \
--no-optional \
--registry="$NPM_REGISTRY" 2>&1 | tail -20
# 验证安装
local oc_entry=""
for d in "$tmp_install/global/lib/node_modules/openclaw" "$tmp_install/global/node_modules/openclaw"; do
if [ -f "${d}/openclaw.mjs" ]; then
oc_entry="${d}/openclaw.mjs"
break
elif [ -f "${d}/dist/cli.js" ]; then
oc_entry="${d}/dist/cli.js"
break
fi
done
if [ -z "$oc_entry" ]; then
log_error "OpenClaw 安装验证失败"
echo "目录内容:"
find "$tmp_install/global" -maxdepth 4 -type d 2>/dev/null | head -30
exit 1
fi
log_info "OpenClaw 安装验证通过: $oc_entry"
# 获取实际安装的版本号
local actual_ver=""
local oc_pkg_dir="$(dirname "$oc_entry")"
if [ -f "$oc_pkg_dir/package.json" ]; then
actual_ver=$(node -e "console.log(require('$oc_pkg_dir/package.json').version)" 2>/dev/null || echo "$OC_VERSION")
fi
echo " 实际版本: v${actual_ver:-$OC_VERSION}"
# ── 打包为通用 tarball ──
# 不精简 node_modules保留全部功能飞书/Slack/Discord 等平台 SDK
# 依靠 gzip 压缩控制包大小: ~670MB → ~150MB
# 因为 openclaw 是纯 JS 包 (使用 --ignore-scripts)node_modules 跨架构通用
echo ""
echo "=== 打包 OpenClaw 依赖 ==="
local install_size=$(du -sm "$tmp_install/global" 2>/dev/null | awk '{print $1}')
local tarball="$oc_dir/openclaw-deps-v${actual_ver:-$OC_VERSION}.tar.gz"
echo " 正在压缩 ${install_size}MB 数据到 tar.gz (可能需要数分钟)..."
# 注意: 不在子 shell 中用 set -e, 避免 tar 在处理损坏的符号链接时意外退出
# --warning=no-file-changed: 忽略打包过程中文件被修改的警告
if ! tar czf "$tarball" -C "$tmp_install/global" . 2>&1; then
log_error "tar 打包失败"
rm -f "$tarball"
exit 1
fi
# 验证 gzip 完整性
if ! gzip -t "$tarball" 2>/dev/null; then
log_error "tar.gz 文件完整性检查失败!"
rm -f "$tarball"
exit 1
fi
local tgz_size=$(du -h "$tarball" | cut -f1)
log_info "依赖包: $tarball ($tgz_size) [完整性已验证]"
# 保存版本号
echo "${actual_ver:-$OC_VERSION}" > "$oc_dir/VERSION"
rm -rf "$tmp_install"
# 清除之前的 trap
trap - EXIT
}
# ============================================================================
# Phase 3: 生成清单
# ============================================================================
generate_manifest() {
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ [3/3] 生成构建清单 ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
local manifest="$CACHE_DIR/manifest.txt"
local oc_ver=$(cat "$CACHE_DIR/openclaw/VERSION" 2>/dev/null || echo "$OC_VERSION")
cat > "$manifest" << EOF
# OpenClaw Offline Bundle - 依赖清单
# 生成时间: $(date -Iseconds 2>/dev/null || date)
# Node.js: v${NODE_VERSION}
# OpenClaw: v${oc_ver}
[node]
EOF
# 列出 Node.js 包
for f in "$CACHE_DIR/node/"*.tar.xz; do
[ -f "$f" ] || continue
local fname=$(basename "$f")
local fsize=$(du -h "$f" | cut -f1)
local sha256=$(sha256sum "$f" 2>/dev/null | awk '{print $1}' || echo "N/A")
echo "${fname} size=${fsize} sha256=${sha256}" >> "$manifest"
done
echo "" >> "$manifest"
echo "[openclaw]" >> "$manifest"
# 列出 OpenClaw 包
for f in "$CACHE_DIR/openclaw/"*.tar.gz; do
[ -f "$f" ] || continue
local fname=$(basename "$f")
local fsize=$(du -h "$f" | cut -f1)
local sha256=$(sha256sum "$f" 2>/dev/null | awk '{print $1}' || echo "N/A")
echo "${fname} size=${fsize} sha256=${sha256}" >> "$manifest"
done
echo ""
echo "=== 依赖清单 ==="
cat "$manifest"
echo ""
# 统计总大小
local total_size=$(du -sh "$CACHE_DIR" 2>/dev/null | awk '{print $1}')
echo "缓存目录: $CACHE_DIR"
echo "总大小: $total_size"
}
# ── 主入口 ──
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ OpenClaw 离线依赖下载器 ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
echo " Node.js: v${NODE_VERSION}"
echo " OpenClaw: v${OC_VERSION}"
echo " 缓存目录: ${CACHE_DIR}"
echo " npm 源: ${NPM_REGISTRY}"
echo ""
mkdir -p "$CACHE_DIR"
download_all_node
download_openclaw_deps
generate_manifest
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ ✅ 依赖下载完成!现在可以运行 build_offline_run.sh ║"
echo "╚══════════════════════════════════════════════════════════════╝"