Files
luci-app-openclaw/scripts/download_deps.sh

305 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "╚══════════════════════════════════════════════════════════════╝"