diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ff245a..3a104a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,9 +7,6 @@ on: description: '版本号 (留空则读取 VERSION 文件)' required: false default: '' - build_offline: - description: '是否构建离线安装包 (~130MB/架构)' - required: false type: boolean default: false create_release: @@ -78,31 +75,9 @@ jobs: chmod +x scripts/build_ipk.sh sh scripts/build_ipk.sh dist - # ── 离线安装包 (可选) ── - - name: Setup Node.js (for offline build) - if: github.event.inputs.build_offline == 'true' - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Download offline dependencies - if: github.event.inputs.build_offline == 'true' - run: | - chmod +x scripts/download_deps.sh - sh scripts/download_deps.sh .offline-cache - env: - NODE_VERSION: ${{ steps.build_versions.outputs.node_version }} OC_VERSION: ${{ steps.build_versions.outputs.oc_version }} - - name: Build offline .run (musl only) - if: github.event.inputs.build_offline == 'true' - run: | - chmod +x scripts/build_offline_run.sh - sh scripts/build_offline_run.sh dist/ - env: - CACHE_DIR: .offline-cache - NODE_VERSION: ${{ steps.build_versions.outputs.node_version }} # ── 通用步骤 ── @@ -148,16 +123,6 @@ jobs: OPENLIST_PATH: ${{ secrets.OPENLIST_PATH }} UPLOAD_MODE: online - - name: Upload to OpenList (offline) - if: github.event.inputs.upload_openlist == 'true' && github.event.inputs.build_offline == 'true' - run: | - sh scripts/upload_openlist.sh dist/ - env: - OPENLIST_URL: ${{ secrets.OPENLIST_URL }} - OPENLIST_USER: ${{ secrets.OPENLIST_USER }} - OPENLIST_PASS: ${{ secrets.OPENLIST_PASS }} - OPENLIST_PATH: ${{ secrets.OPENLIST_PATH }} - UPLOAD_MODE: offline - name: Create Release if: github.event.inputs.create_release == 'true' @@ -182,11 +147,7 @@ jobs: opkg install luci-app-openclaw_${{ steps.version.outputs.version }}-1_all.ipk ``` - **离线安装** (无需联网,包含全部依赖) ```bash - # 将对应架构的 *_offline.run 传到路由器 - scp luci-app-openclaw_*_offline.run root@路由器IP:/tmp/ - ssh root@路由器IP "sh /tmp/luci-app-openclaw_*_offline.run" ``` [使用文档](https://github.com/10000ge10000/luci-app-openclaw#readme) · [问题反馈](https://github.com/10000ge10000/luci-app-openclaw/issues) · [B站](https://space.bilibili.com/59438380) · [博客](https://blog.910501.xyz/) diff --git a/.gitignore b/.gitignore index 2edddfe..60436f5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ dist/ *.run # Offline bundle cache (generated by scripts/download_deps.sh) -.offline-cache/ # Runtime data (contains sensitive API keys / tokens) opt/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a0adbc6..ff38197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,6 @@ - **离线 .run 安装包**: 构建包含 Node.js + OpenClaw + LuCI 插件的全合一自解压包,用户**无需联网**即可完成安装 - **musl 架构支持**: 离线包支持 x86_64-musl、aarch64-musl 两种架构 (OpenWrt/iStoreOS 均使用 musl) - **依赖预下载脚本** (`scripts/download_deps.sh`): 在构建机上预下载所有离线依赖 -- **离线构建脚本** (`scripts/build_offline_run.sh`): 将预下载的依赖打包为各架构的离线 .run -- **GitHub Actions CI** (`.github/workflows/build-offline.yml`): 离线包自动构建 + 发布 - **node_modules 精简**: 自动删除文档、测试、TypeScript 源码等非必要文件,减小 30%+ 体积 - **磁盘空间预检查**: 安装前检测可用空间是否满足 500MB 最低要求 - **架构/libc 自动检测**: 安装时自动校验当前设备是否匹配安装包架构 diff --git a/README.md b/README.md index a6e293c..5e5ab45 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Bilibili](https://img.shields.io/badge/B%E7%AB%99-59438380-00a1d6?logo=bilibili)](https://space.bilibili.com/59438380) [![Blog](https://img.shields.io/badge/Blog-910501.xyz-orange)](https://blog.910501.xyz/) [![Build & Release](https://github.com/10000ge10000/luci-app-openclaw/actions/workflows/build.yml/badge.svg)](https://github.com/10000ge10000/luci-app-openclaw/actions/workflows/build.yml) -[![Build Offline Bundle](https://github.com/10000ge10000/luci-app-openclaw/actions/workflows/build-offline.yml/badge.svg)](https://github.com/10000ge10000/luci-app-openclaw/actions/workflows/build-offline.yml) [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](LICENSE) [OpenClaw](https://github.com/nicepkg/openclaw) AI 网关的 OpenWrt LuCI 管理插件。 @@ -96,21 +95,16 @@ rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* **下载离线包**(在联网的电脑上): -前往 [Releases](https://github.com/10000ge10000/luci-app-openclaw/releases) 页面下载对应架构的 `_offline.run` 文件: | 架构 | 文件名 | |------|--------| -| x86_64 | `luci-app-openclaw_*_x86_64-musl_offline.run` | -| aarch64 (ARM64) | `luci-app-openclaw_*_aarch64-musl_offline.run` | **传输到路由器并安装**: ```bash # 从电脑传输到路由器(替换为实际文件名和路由器 IP) -scp luci-app-openclaw_*_offline.run root@192.168.1.1:/tmp/ # SSH 登录路由器后执行安装 -sh /tmp/luci-app-openclaw_*_offline.run ``` > **提示**:离线包约 130MB,ARM 设备上安装需要 3-5 分钟(主要是解压时间)。 @@ -146,13 +140,11 @@ luci-app-openclaw/ ├── scripts/ │ ├── build_ipk.sh # 本地 IPK 构建 │ ├── build_run.sh # .run 安装包构建 -│ ├── build_offline_run.sh # 离线 .run 安装包构建 │ ├── download_deps.sh # 下载离线依赖 (Node.js + OpenClaw) │ ├── upload_openlist.sh # 上传到网盘 (OpenList) │ └── build-node-musl.sh # 编译 Node.js musl 静态链接版本 └── .github/workflows/ ├── build.yml # 在线构建 + 发布 - ├── build-offline.yml # 离线包构建 + 发布 └── build-node-musl.yml # Node.js musl 构建 ``` diff --git a/luasrc/controller/openclaw.lua b/luasrc/controller/openclaw.lua index ad38cfd..5ba6c07 100644 --- a/luasrc/controller/openclaw.lua +++ b/luasrc/controller/openclaw.lua @@ -82,16 +82,6 @@ function action_status() end -- 安装方式检测 (离线 / 在线) - local olf = io.open("/usr/share/openclaw/.offline-install", "r") - if olf then - local content = olf:read("*a") - olf:close() - result.install_type = "offline" - result.install_date = content:match("date=([^\n]+)") or "" - result.install_arch = content:match("arch=([^\n]+)") or "" - else - result.install_type = "online" - end -- 检查 Node.js local node_bin = "/opt/openclaw/node/bin/node" diff --git a/luasrc/view/openclaw/status.htm b/luasrc/view/openclaw/status.htm index 3132909..b2a8ebe 100644 --- a/luasrc/view/openclaw/status.htm +++ b/luasrc/view/openclaw/status.htm @@ -57,8 +57,6 @@ .oc-badge-starting { background: #fff8c5; color: #9a6700; } .oc-badge-disabled { background: #f0f0f0; color: #656d76; } .oc-badge-unknown { background: #fff8c5; color: #9a6700; } -.oc-badge-offline { background: #ddf4ff; color: #0969da; border: 1px solid #54aeff; } -.oc-badge-online { background: #f0f0f0; color: #656d76; } .oc-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; } .oc-dot-green { background: #1a7f37; } .oc-dot-red { background: #cf222e; } @@ -80,7 +78,6 @@ Node.js- OpenClaw- 插件版本- - 安装方式- @@ -151,17 +148,6 @@ document.getElementById('oc-st-oc-ver').textContent = d.oc_version ? ('v' + d.oc_version) : '未安装'; document.getElementById('oc-st-plugin').textContent = d.plugin_version ? ('v' + d.plugin_version) : '-'; - var itEl = document.getElementById('oc-st-install-type'); - if (d.install_type === 'offline') { - var tip = '📦 离线安装'; - if (d.install_arch) tip += ' (' + d.install_arch + ')'; - if (d.install_date) tip += '\n安装时间: ' + d.install_date; - itEl.innerHTML = '📦 离线版'; - } else { - itEl.innerHTML = '🌐 在线版'; - } - - } catch(e) { document.getElementById('oc-st-status').innerHTML = '查询失败'; } }); diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env index 1d1574a..4e0b856 100755 --- a/root/usr/bin/openclaw-env +++ b/root/usr/bin/openclaw-env @@ -624,112 +624,8 @@ do_factory_reset() { } # ── 离线安装 (从本地文件安装 Node.js + OpenClaw) ── -do_setup_offline() { - local offline_dir="${2:-/tmp/openclaw-offline}" - - if [ ! -d "$offline_dir" ]; then - log_error "离线安装目录不存在: $offline_dir" - log_error "此命令通常由离线 .run 安装器自动调用" - exit 1 - fi - - # 检查是否已安装 - if [ -x "$NODE_BIN" ] && [ -n "$(find_oc_entry)" ]; then - log_warn "OpenClaw 运行环境已安装" - log_warn "如需重新离线安装,请先卸载现有环境" - exit 0 - fi - - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ 一万AI分享 OpenClaw 离线安装 ║" - echo "╚══════════════════════════════════════════════════════════════╝" - echo "" - echo " 架构: $(uname -m)" - echo " 安装路径: ${NODE_BASE}" - echo " 数据路径: ${OC_DATA}" - echo " 离线包: ${offline_dir}" - echo "" - - # [1] 安装 Node.js - local node_tarball="$offline_dir/node.tar.xz" - if [ -f "$node_tarball" ]; then - echo "=== 安装 Node.js (离线) ===" - rm -rf "$NODE_BASE" 2>/dev/null - [ -d /overlay/upper ] && rm -rf "/overlay/upper${NODE_BASE}" 2>/dev/null - ensure_mkdir "$NODE_BASE" - - if tar --strip-components=1 -xf "$node_tarball" -C "$NODE_BASE" 2>/dev/null; then - : # GNU tar - else - local tmp_extract="/tmp/node-extract-$$" - ensure_mkdir "$tmp_extract" - tar xf "$node_tarball" -C "$tmp_extract" - local top_dir=$(ls "$tmp_extract" 2>/dev/null | head -1) - if [ -n "$top_dir" ] && [ -d "$tmp_extract/$top_dir" ]; then - cp -a "$tmp_extract/$top_dir/." "$NODE_BASE/" - fi - rm -rf "$tmp_extract" - fi - - if [ -x "$NODE_BIN" ]; then - log_info "Node.js $($NODE_BIN --version 2>/dev/null) 安装成功" - else - log_error "Node.js 安装失败" - exit 1 - fi - else - log_error "未找到 Node.js 离线包: $node_tarball" - exit 1 - fi - - # [2] 安装 OpenClaw - local oc_tarball="$offline_dir/openclaw-deps.tar.gz" - if [ -f "$oc_tarball" ]; then - echo "" - echo "=== 安装 OpenClaw (离线) ===" - rm -rf "$OC_GLOBAL" 2>/dev/null - [ -d /overlay/upper ] && rm -rf "/overlay/upper${OC_GLOBAL}" 2>/dev/null - ensure_mkdir "$OC_GLOBAL" - - tar xzf "$oc_tarball" -C "$OC_GLOBAL" - - local oc_entry=$(find_oc_entry) - if [ -n "$oc_entry" ]; then - local oc_ver=$("$NODE_BIN" "$oc_entry" --version 2>/dev/null | tr -d '[:space:]') - log_info "OpenClaw v${oc_ver} 安装成功" - else - log_error "OpenClaw 安装验证失败" - exit 1 - fi - else - log_error "未找到 OpenClaw 离线包: $oc_tarball" - exit 1 - fi - - # [3] 初始化 - init_openclaw - - echo "" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ ✅ 离线安装完成! ║" - echo "║ ║" - echo "║ 下一步: ║" - echo "║ uci set openclaw.main.enabled=1 ║" - echo "║ uci commit openclaw ║" - echo "║ /etc/init.d/openclaw enable ║" - echo "║ /etc/init.d/openclaw start ║" - echo "║ ║" - echo "║ 或在 LuCI → 服务 → OpenClaw 中启用 ║" - echo "╚══════════════════════════════════════════════════════════════╝" -} - -# ── 主入口 ── -case "${1:-}" in - setup) do_setup ;; - setup-offline) - do_setup_offline "$@" ;; check) do_check @@ -744,10 +640,8 @@ case "${1:-}" in do_factory_reset ;; *) - echo "用法: openclaw-env {setup|setup-offline|check|upgrade|node|factory-reset}" echo "" echo " setup — 完整安装 (下载 Node.js + pnpm + OpenClaw)" - echo " setup-offline — 离线安装 (从本地文件安装,由 .run 安装器调用)" echo " check — 检查环境状态" echo " upgrade — 升级 OpenClaw 到最新版" echo " node — 仅下载/更新 Node.js" diff --git a/scripts/build_offline_run.sh b/scripts/build_offline_run.sh deleted file mode 100755 index 6cc021e..0000000 --- a/scripts/build_offline_run.sh +++ /dev/null @@ -1,594 +0,0 @@ -#!/bin/sh -# ============================================================================ -# OpenClaw 离线 .run 自解压包构建脚本 -# 构建包含所有离线依赖的全架构 .run 安装包 -# -# 用法: -# sh scripts/build_offline_run.sh [output_dir] -# -# 前置条件: -# 先运行 sh scripts/download_deps.sh 下载离线依赖到 .offline-cache/ -# -# 产出: -# dist/luci-app-openclaw__x86_64-musl_offline.run -# dist/luci-app-openclaw__aarch64-musl_offline.run -# ============================================================================ -set -e - -SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) -PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd) -OUT_DIR="${1:-$PKG_DIR/dist}" -CACHE_DIR="${CACHE_DIR:-$PKG_DIR/.offline-cache}" - -case "$OUT_DIR" in - /*) ;; - *) OUT_DIR="$PKG_DIR/$OUT_DIR" ;; -esac -case "$CACHE_DIR" in - /*) ;; - *) CACHE_DIR="$PKG_DIR/$CACHE_DIR" ;; -esac - -PKG_NAME="luci-app-openclaw" -PKG_VERSION=$(cat "$PKG_DIR/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "1.0.0") -NODE_VERSION="${NODE_VERSION:-22.15.1}" -OC_VERSION=$(cat "$CACHE_DIR/openclaw/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "2026.3.8") - -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ 构建 OpenClaw 离线 .run 安装包 ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" -echo " 插件版本: v${PKG_VERSION}" -echo " Node.js: v${NODE_VERSION}" -echo " OpenClaw: v${OC_VERSION}" -echo " 缓存目录: ${CACHE_DIR}" -echo " 输出目录: ${OUT_DIR}" -echo "" - -# 检查缓存目录 -if [ ! -d "$CACHE_DIR/node" ] || [ ! -d "$CACHE_DIR/openclaw" ]; then - echo "错误: 离线缓存不存在,请先运行:" - echo " sh scripts/download_deps.sh" - exit 1 -fi - -mkdir -p "$OUT_DIR" - -# ── 安装 LuCI 插件文件到暂存区 ── -install_luci_files() { - local dest="$1" - - mkdir -p "$dest/etc/config" - cp "$PKG_DIR/root/etc/config/openclaw" "$dest/etc/config/openclaw.default" - - mkdir -p "$dest/etc/uci-defaults" - cp "$PKG_DIR/root/etc/uci-defaults/99-openclaw" "$dest/etc/uci-defaults/" - chmod +x "$dest/etc/uci-defaults/99-openclaw" - - mkdir -p "$dest/etc/init.d" - cp "$PKG_DIR/root/etc/init.d/openclaw" "$dest/etc/init.d/" - chmod +x "$dest/etc/init.d/openclaw" - - mkdir -p "$dest/usr/bin" - cp "$PKG_DIR/root/usr/bin/openclaw-env" "$dest/usr/bin/" - chmod +x "$dest/usr/bin/openclaw-env" - - mkdir -p "$dest/usr/lib/lua/luci/controller" - cp "$PKG_DIR/luasrc/controller/openclaw.lua" "$dest/usr/lib/lua/luci/controller/" - - mkdir -p "$dest/usr/lib/lua/luci/model/cbi/openclaw" - cp "$PKG_DIR/luasrc/model/cbi/openclaw/"*.lua "$dest/usr/lib/lua/luci/model/cbi/openclaw/" - - mkdir -p "$dest/usr/lib/lua/luci/view/openclaw" - cp "$PKG_DIR/luasrc/view/openclaw/"*.htm "$dest/usr/lib/lua/luci/view/openclaw/" - - mkdir -p "$dest/usr/share/openclaw" - cp "$PKG_DIR/VERSION" "$dest/usr/share/openclaw/VERSION" - cp "$PKG_DIR/root/usr/share/openclaw/oc-config.sh" "$dest/usr/share/openclaw/" - chmod +x "$dest/usr/share/openclaw/oc-config.sh" - cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$dest/usr/share/openclaw/" - cp -r "$PKG_DIR/root/usr/share/openclaw/ui" "$dest/usr/share/openclaw/" - - # i18n - mkdir -p "$dest/usr/lib/lua/luci/i18n" - if command -v po2lmo >/dev/null 2>&1 && [ -f "$PKG_DIR/po/zh-cn/openclaw.po" ]; then - po2lmo "$PKG_DIR/po/zh-cn/openclaw.po" "$dest/usr/lib/lua/luci/i18n/openclaw.zh-cn.lmo" 2>/dev/null || true - fi -} - -# ── 创建离线安装器脚本 ── -create_offline_installer() { - local target_arch="$1" # 如 x86_64 - local target_libc="$2" # 如 musl - local staging="$3" - - cat > "$staging/install.sh" << 'INSTALLER_HEADER' -#!/bin/sh -# ============================================================================ -# luci-app-openclaw 离线安装器 -# 包含所有依赖,无需联网即可完成完整安装 -# ============================================================================ -set -e - -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ luci-app-openclaw — OpenClaw AI Gateway 离线安装器 ║" -echo "║ 包含 Node.js + OpenClaw 运行环境,无需联网 ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" - -# ── 基本检查 ── -if [ ! -f /etc/openwrt_release ]; then - echo "错误: 此安装包仅适用于 OpenWrt/iStoreOS 系统" - exit 1 -fi - -ARCH=$(uname -m) -TARGET_ARCH="__TARGET_ARCH__" -TARGET_LIBC="__TARGET_LIBC__" - -# 架构检查 -case "$ARCH" in - x86_64|aarch64) ;; - *) echo "错误: 不支持的架构 $ARCH (仅支持 x86_64/aarch64)"; exit 1 ;; -esac - -if [ "$ARCH" != "$TARGET_ARCH" ]; then - echo "错误: 架构不匹配!" - echo " 当前设备: $ARCH" - echo " 安装包: ${TARGET_ARCH}-${TARGET_LIBC}" - echo "" - echo "请下载对应架构的安装包。" - exit 1 -fi - -# libc 检查 -detect_libc() { - if ldd --version 2>&1 | grep -qi musl; then - echo "musl" - elif [ -f /lib/ld-musl-*.so.1 ] 2>/dev/null; then - echo "musl" - elif [ -f /etc/openwrt_release ] || grep -qi "openwrt\|istoreos\|lede" /etc/os-release 2>/dev/null; then - echo "musl" - else - echo "glibc" - fi -} - -SYS_LIBC=$(detect_libc) -if [ "$SYS_LIBC" != "$TARGET_LIBC" ]; then - echo "警告: C 库类型不匹配 (系统: $SYS_LIBC, 安装包: $TARGET_LIBC)" - echo " 如果安装后 Node.js 无法运行,请下载对应 libc 类型的安装包。" - echo "" - printf "是否继续?[y/N] " - read -r answer - case "$answer" in - y|Y|yes|YES) ;; - *) echo "已取消"; exit 0 ;; - esac -fi - -# ── 磁盘空间预检查 ── -echo "检查磁盘空间..." -# 预估解压后大小: Node.js ~100-200MB + OpenClaw ~200-400MB + 插件 ~1MB -NEED_MB=500 -# 检查 /opt 所在分区 (OverlayFS 下可能是 /overlay) -AVAIL_KB=0 -for mount_point in /opt /overlay /; do - if df "$mount_point" >/dev/null 2>&1; then - AVAIL_KB=$(df "$mount_point" 2>/dev/null | tail -1 | awk '{print $4}') - break - fi -done -AVAIL_MB=$((AVAIL_KB / 1024)) -if [ "$AVAIL_MB" -lt "$NEED_MB" ] 2>/dev/null; then - echo "警告: 可用空间不足!" - echo " 需要: 至少 ${NEED_MB}MB" - echo " 当前: ${AVAIL_MB}MB 可用" - echo "" - printf "是否继续?[y/N] " - read -r answer - case "$answer" in - y|Y|yes|YES) ;; - *) echo "已取消"; exit 0 ;; - esac -fi - -# ── OverlayFS 修复 ── -_oc_fix_opt() { - mkdir -p /opt/openclaw/.probe 2>/dev/null && { rmdir /opt/openclaw/.probe 2>/dev/null; return 0; } - if [ -d /overlay/upper/opt ]; then - mkdir -p /overlay/upper/opt/openclaw 2>/dev/null - mount --bind /overlay/upper/opt /opt 2>/dev/null && return 0 - fi - return 1 -} -_oc_fix_opt || true - -NODE_BASE="/opt/openclaw/node" -OC_GLOBAL="/opt/openclaw/global" -OC_DATA="/opt/openclaw/data" - -ensure_mkdir() { - local target="$1" - [ -d "$target" ] && return 0 - if ! mkdir -p "$target" 2>/dev/null; then - echo " [✗] 无法创建目录: $target" - return 1 - fi -} - -# ── 解压安装 ── - -# 先停止已有服务 (避免文件被占用导致覆盖安装失败) -if [ -x /etc/init.d/openclaw ]; then - echo "停止已有服务..." - /etc/init.d/openclaw stop 2>/dev/null || true - # 等待进程退出和端口释放 - sleep 2 - # 确保 gateway 子进程也已退出 - for pid in $(pgrep -f "openclaw-gateway" 2>/dev/null); do - kill "$pid" 2>/dev/null - done - sleep 1 -fi - -echo "" -echo "正在提取安装文件..." - -# 解压 payload (从 MARKER 行之后) -ARCHIVE=$(awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$0") -EXTRACT_DIR=$(mktemp -d) -trap "rm -rf '$EXTRACT_DIR'" EXIT - -tail -n +$ARCHIVE "$0" | tar xzf - -C "$EXTRACT_DIR" 2>/dev/null - -# ── [Step 1/5] 安装 LuCI 插件文件 ── -echo "" -echo "[1/5] 安装 LuCI 插件..." - -# 复制插件文件到系统 (从 luci-files/ 子目录) -if [ -d "$EXTRACT_DIR/luci-files" ]; then - cp -a "$EXTRACT_DIR/luci-files/." / 2>/dev/null -fi - -# UCI 配置文件保护 -if [ -f /etc/config/openclaw ] && [ -f /etc/config/openclaw.default ]; then - rm -f /etc/config/openclaw.default -elif [ -f /etc/config/openclaw.default ]; then - mv /etc/config/openclaw.default /etc/config/openclaw -fi - -echo " [✓] LuCI 插件已安装" - -# ── [Step 2/5] 安装 Node.js ── -echo "" -echo "[2/5] 安装 Node.js..." - -NODE_TARBALL="$EXTRACT_DIR/node.tar.xz" -if [ -f "$NODE_TARBALL" ]; then - # 清理旧安装 - rm -rf "$NODE_BASE" 2>/dev/null - [ -d /overlay/upper ] && rm -rf "/overlay/upper${NODE_BASE}" 2>/dev/null - ensure_mkdir "$NODE_BASE" - - # 解压 Node.js (兼容 BusyBox tar) - if tar --strip-components=1 -xf "$NODE_TARBALL" -C "$NODE_BASE" 2>/dev/null; then - : # GNU tar - else - # BusyBox tar 回退 - local tmp_node="/tmp/node-extract-$$" - ensure_mkdir "$tmp_node" - tar xf "$NODE_TARBALL" -C "$tmp_node" - local top_dir=$(ls "$tmp_node" 2>/dev/null | head -1) - if [ -n "$top_dir" ] && [ -d "$tmp_node/$top_dir" ]; then - cp -a "$tmp_node/$top_dir/." "$NODE_BASE/" - fi - rm -rf "$tmp_node" - fi - - if [ -x "$NODE_BASE/bin/node" ]; then - echo " [✓] Node.js $($NODE_BASE/bin/node --version 2>/dev/null) 已安装" - else - echo " [✗] Node.js 安装失败" - exit 1 - fi -else - echo " [✗] 安装包中未找到 Node.js" - exit 1 -fi - -# ── [Step 3/5] 安装 OpenClaw ── -echo "" -echo "[3/5] 安装 OpenClaw..." - -OC_DEPS_TARBALL="$EXTRACT_DIR/openclaw-deps.tar.gz" -if [ -f "$OC_DEPS_TARBALL" ]; then - rm -rf "$OC_GLOBAL" 2>/dev/null - [ -d /overlay/upper ] && rm -rf "/overlay/upper${OC_GLOBAL}" 2>/dev/null - ensure_mkdir "$OC_GLOBAL" - - tar xzf "$OC_DEPS_TARBALL" -C "$OC_GLOBAL" - - # 验证 openclaw 入口 - OC_ENTRY="" - for d in "$OC_GLOBAL/lib/node_modules/openclaw" "$OC_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 [ -n "$OC_ENTRY" ]; then - OC_VER=$("$NODE_BASE/bin/node" "$OC_ENTRY" --version 2>/dev/null | tr -d '[:space:]' || echo "unknown") - echo " [✓] OpenClaw v${OC_VER} 已安装" - else - echo " [✗] OpenClaw 安装验证失败" - exit 1 - fi -else - echo " [✗] 安装包中未找到 OpenClaw 依赖" - exit 1 -fi - -# ── [Step 4/5] 初始化 OpenClaw ── -echo "" -echo "[4/5] 初始化 OpenClaw..." - -ensure_mkdir "$OC_DATA/.openclaw" - -# 创建 openclaw 系统用户 (如果不存在) -if ! id openclaw >/dev/null 2>&1; then - # OpenWrt 使用 BusyBox adduser - if command -v adduser >/dev/null 2>&1; then - adduser -D -H -s /bin/false -h "$OC_DATA" openclaw 2>/dev/null || true - fi -fi - -# 运行 onboard -if [ -n "$OC_ENTRY" ] && [ -x "$NODE_BASE/bin/node" ]; then - HOME="$OC_DATA" \ - OPENCLAW_HOME="$OC_DATA" \ - OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \ - OPENCLAW_CONFIG_PATH="${OC_DATA}/.openclaw/openclaw.json" \ - "$NODE_BASE/bin/node" "$OC_ENTRY" onboard --non-interactive --accept-risk --tools-profile coding 2>/dev/null || true -fi - -# 设置权限 -chown -R openclaw:openclaw "$OC_DATA" 2>/dev/null || true -chown -R openclaw:openclaw "$OC_GLOBAL" 2>/dev/null || true -chown -R openclaw:openclaw "$NODE_BASE" 2>/dev/null || true - -echo " [✓] 初始化完成" - -# ── [Step 5/5] 注册 opkg + 启动服务 ── -echo "" -echo "[5/5] 注册到系统..." - -# 注册到 opkg -PKG="luci-app-openclaw" -PKG_VER="__PKG_VERSION__" -INFO_DIR="/usr/lib/opkg/info" -STATUS_FILE="/usr/lib/opkg/status" -INSTALL_TIME=$(date +%s) - -mkdir -p "$INFO_DIR" - -cat > "$INFO_DIR/$PKG.control" << CTLEOF -Package: $PKG -Version: $PKG_VER -Depends: luci-compat, luci-base -Section: luci -Architecture: all -Installed-Size: 0 -Description: OpenClaw AI Gateway — LuCI 界面 (离线安装) -CTLEOF - -cat > "$INFO_DIR/$PKG.list" << LISTEOF -__FILE_LIST__ -LISTEOF - -cat > "$INFO_DIR/$PKG.prerm" << 'RMEOF' -#!/bin/sh -/etc/init.d/openclaw stop 2>/dev/null -/etc/init.d/openclaw disable 2>/dev/null -exit 0 -RMEOF -chmod +x "$INFO_DIR/$PKG.prerm" - -# 更新 opkg status -if [ -f "$STATUS_FILE" ]; then - awk -v pkg="$PKG" ' - BEGIN { skip=0 } - /^Package:/ { skip=($2==pkg) } - /^$/ { if(skip){skip=0; next} } - !skip { print } - ' "$STATUS_FILE" > "${STATUS_FILE}.tmp" - mv "${STATUS_FILE}.tmp" "$STATUS_FILE" -fi - -cat >> "$STATUS_FILE" << STEOF - -Package: $PKG -Version: $PKG_VER -Depends: luci-compat, luci-base -Status: install user installed -Architecture: all -Conffiles: - /etc/config/openclaw 0 -Installed-Time: $INSTALL_TIME -STEOF - -echo " [✓] 已注册到 opkg" - -# 写入离线安装标记 (供 LuCI 界面识别安装方式) -cat > /usr/share/openclaw/.offline-install << OFFEOF -type=offline -date=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date) -arch=${TARGET_ARCH}-${TARGET_LIBC} -node=$($NODE_BASE/bin/node --version 2>/dev/null || echo unknown) -openclaw=${OC_VER:-unknown} -plugin=__PKG_VERSION__ -OFFEOF -echo " [✓] 离线安装标记已写入" - -# 执行 uci-defaults -if [ -f /etc/uci-defaults/99-openclaw ]; then - ( . /etc/uci-defaults/99-openclaw ) && rm -f /etc/uci-defaults/99-openclaw -fi - -# 清除 LuCI 缓存 -rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null -rm -f /tmp/luci-indexcache.*.json 2>/dev/null - -# 启用并启动服务 -/etc/init.d/openclaw enable 2>/dev/null || true -uci set openclaw.main.enabled=1 2>/dev/null || true -uci commit openclaw 2>/dev/null || true - -# 清理 -rm -rf "$EXTRACT_DIR" -trap - EXIT - -echo "" -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ ✅ 离线安装完成! ║" -echo "║ ║" -echo "║ Node.js + OpenClaw + LuCI 插件已全部安装 ║" -echo "║ 无需再运行 openclaw-env setup ║" -echo "║ ║" -echo "║ 下一步: ║" -echo "║ 访问 LuCI → 服务 → OpenClaw 进行配置 ║" -echo "║ 或执行: /etc/init.d/openclaw start ║" -echo "║ ║" -echo "║ 配置模型 API 密钥后即可使用,全程无需联网! ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" - -exit 0 -__ARCHIVE_BELOW__ -INSTALLER_HEADER -} - -# ============================================================================ -# 为每种架构构建 .run -# ============================================================================ -build_one_variant() { - local label="$1" # 如 x86_64-musl - local uname_arch="$2" # 如 x86_64 - local node_suffix="$3" # 如 linux-x64-musl - local libc="$4" # 如 musl - - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo " 构建: ${label}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - local node_tarball="$CACHE_DIR/node/node-v${NODE_VERSION}-${node_suffix}.tar.xz" - if [ ! -f "$node_tarball" ]; then - echo " [!] 跳过: 未找到 Node.js 包 $(basename "$node_tarball")" - return 1 - fi - - # 查找 OpenClaw 依赖包 - local oc_deps="" - for f in "$CACHE_DIR/openclaw/openclaw-deps-"*.tar.gz; do - [ -f "$f" ] && oc_deps="$f" && break - done - if [ -z "$oc_deps" ]; then - echo " [!] 跳过: 未找到 OpenClaw 依赖包" - return 1 - fi - - # 创建临时暂存区 - local staging=$(mktemp -d) - - # [1] 准备 payload 结构 - local payload="$staging/payload" - mkdir -p "$payload/luci-files" - - echo " [1/5] 安装 LuCI 插件文件..." - install_luci_files "$payload/luci-files" - - echo " [2/5] 复制 Node.js 包..." - cp "$node_tarball" "$payload/node.tar.xz" - - echo " [3/5] 复制 OpenClaw 依赖包..." - cp "$oc_deps" "$payload/openclaw-deps.tar.gz" - - # [2] 生成文件列表 - echo " [4/5] 生成安装器..." - local file_list=$(cd "$payload/luci-files" && find . -type f | sed 's|^\./|/|' | sed 's|/etc/config/openclaw.default|/etc/config/openclaw|' | sort) - - # 创建安装器 - create_offline_installer "$uname_arch" "$libc" "$staging" - - # 替换占位符 - sed -i "s|__TARGET_ARCH__|${uname_arch}|g" "$staging/install.sh" - sed -i "s|__TARGET_LIBC__|${libc}|g" "$staging/install.sh" - sed -i "s|__PKG_VERSION__|${PKG_VERSION}|g" "$staging/install.sh" - - # 替换文件列表 - { - sed '/__FILE_LIST__/,$d' "$staging/install.sh" - echo "$file_list" - sed '1,/__FILE_LIST__/d' "$staging/install.sh" - } > "$staging/install_final.sh" - mv "$staging/install_final.sh" "$staging/install.sh" - - # [3] 打包 payload (gzip 压缩, 减小 .run 文件体积) - echo " [5/5] 打包..." - (cd "$payload" && tar czf "$staging/payload.tar.gz" .) - - # [4] 组合: installer + payload - local run_file="$OUT_DIR/${PKG_NAME}_${PKG_VERSION}_${label}_offline.run" - cat "$staging/install.sh" "$staging/payload.tar.gz" > "$run_file" - chmod +x "$run_file" - - local file_size=$(wc -c < "$run_file" | tr -d ' ') - local file_size_mb=$((file_size / 1024 / 1024)) - - echo " [✓] ${run_file}" - echo " 大小: ${file_size_mb}MB (${file_size} bytes)" - - # 生成 SHA256 - sha256sum "$run_file" > "${run_file}.sha256" 2>/dev/null || true - - rm -rf "$staging" - return 0 -} - -# ── 主构建流程 ── -SUCCESS=0 -FAILED=0 - -# 使用 for 循环避免 BusyBox ash 的 IFS/read 管道问题 -for variant in \ - "x86_64-musl:x86_64:linux-x64-musl:musl" \ - "aarch64-musl:aarch64:linux-arm64-musl:musl" \ -; do - label=$(echo "$variant" | cut -d: -f1) - uname_arch=$(echo "$variant" | cut -d: -f2) - node_suffix=$(echo "$variant" | cut -d: -f3) - libc=$(echo "$variant" | cut -d: -f4) - - if build_one_variant "$label" "$uname_arch" "$node_suffix" "$libc"; then - SUCCESS=$((SUCCESS + 1)) - else - FAILED=$((FAILED + 1)) - fi -done - -echo "" -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ 构建完成 ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" -echo "输出目录: $OUT_DIR" -echo "" -ls -lh "$OUT_DIR/"*_offline.run 2>/dev/null || echo "(无输出文件)" -echo "" -echo "安装方法: 将 .run 文件传输到路由器后执行:" -echo " sh luci-app-openclaw_*_offline.run" diff --git a/scripts/download_deps.sh b/scripts/download_deps.sh deleted file mode 100755 index 8b0b766..0000000 --- a/scripts/download_deps.sh +++ /dev/null @@ -1,304 +0,0 @@ -#!/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.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 "╚══════════════════════════════════════════════════════════════╝" diff --git a/scripts/gen-release-body.sh b/scripts/gen-release-body.sh index 7bf0e28..5f314db 100644 --- a/scripts/gen-release-body.sh +++ b/scripts/gen-release-body.sh @@ -35,13 +35,6 @@ fi echo "opkg install luci-app-openclaw_${VER}-1_all.ipk" echo '```' echo '' - echo '**离线安装** (无需联网,包含全部依赖)' - echo '```bash' - echo '# 将对应架构的 *_offline.run 传到路由器' - echo 'scp luci-app-openclaw_*_offline.run root@路由器IP:/tmp/' - echo 'ssh root@路由器IP "sh /tmp/luci-app-openclaw_*_offline.run"' - echo '```' - echo '' echo '[使用文档](https://github.com/10000ge10000/luci-app-openclaw#readme) · [问题反馈](https://github.com/10000ge10000/luci-app-openclaw/issues) · [B站](https://space.bilibili.com/59438380) · [博客](https://blog.910501.xyz/)' } > "${OUT_DIR}/${VER}.md" diff --git a/scripts/upload_openlist.sh b/scripts/upload_openlist.sh index b410374..df1c6ac 100755 --- a/scripts/upload_openlist.sh +++ b/scripts/upload_openlist.sh @@ -1,221 +1,49 @@ #!/bin/sh # ============================================================================ -# OpenList 网盘上传脚本 -# 将构建产物上传到 OpenList (AList) 网盘,便于国内用户下载 -# -# 用法: -# sh scripts/upload_openlist.sh [dist_dir] -# -# 环境变量 (必须): -# OPENLIST_URL — OpenList 服务地址 (如 https://pan.example.com) -# OPENLIST_USER — 登录用户名 -# OPENLIST_PASS — 登录密码 -# -# 环境变量 (可选): -# OPENLIST_PATH — 上传根路径 (默认: /Quark) -# OPENLIST_TOKEN — 直接提供 token, 跳过登录 -# UPLOAD_MODE — 上传模式: offline / online / auto (默认 auto) -# auto 模式自动检测: 有 *_offline.run 则离线, 有 .run/.ipk 则在线 +# 上传构建产物到 OpenList 软件源 # ============================================================================ set -e -SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) -PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd) -DIST_DIR="${1:-$PKG_DIR/dist}" - -case "$DIST_DIR" in - /*) ;; - *) DIST_DIR="$PKG_DIR/$DIST_DIR" ;; -esac - -# 检查必要的环境变量 -if [ -z "$OPENLIST_URL" ]; then - echo "错误: 请设置 OPENLIST_URL 环境变量" - echo " 例: export OPENLIST_URL=https://pan.example.com" - exit 1 +if [ -z "$OPENLIST_TOKEN" ]; then + echo "错误: 未提供 OPENLIST_TOKEN 环境变量" + exit 1 fi -if [ -z "$OPENLIST_TOKEN" ] && { [ -z "$OPENLIST_USER" ] || [ -z "$OPENLIST_PASS" ]; }; then - echo "错误: 请设置登录凭据" - echo " 方式一: export OPENLIST_USER=xxx OPENLIST_PASS=xxx" - echo " 方式二: export OPENLIST_TOKEN=xxx" - exit 1 +DIST_DIR="${1:-dist}" +if [ ! -d "$DIST_DIR" ]; then + echo "错误: 产物目录不存在: $DIST_DIR" + exit 1 fi -UPLOAD_ROOT="${OPENLIST_PATH:-/Quark}" -UPLOAD_MODE="${UPLOAD_MODE:-auto}" -PKG_VERSION=$(cat "$PKG_DIR/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "unknown") - -# 去除路径末尾的 / -UPLOAD_ROOT="${UPLOAD_ROOT%/}" - -# 自动检测上传模式 -if [ "$UPLOAD_MODE" = "auto" ]; then - if ls "$DIST_DIR"/*_offline.run >/dev/null 2>&1; then - UPLOAD_MODE="offline" - elif ls "$DIST_DIR"/*.run >/dev/null 2>&1 || ls "$DIST_DIR"/*.ipk >/dev/null 2>&1; then - UPLOAD_MODE="online" - else - echo "错误: 无法自动检测上传模式,dist 目录中无可识别文件" - exit 1 - fi -fi - -# 根据模式设置子目录和文件匹配规则 -case "$UPLOAD_MODE" in - offline) - UPLOAD_SUBDIR="openclaw-离线安装" - ;; - online) - UPLOAD_SUBDIR="openclaw-在线安装" - ;; - *) - echo "错误: 无效的 UPLOAD_MODE: $UPLOAD_MODE (可选: offline / online / auto)" - exit 1 - ;; -esac - -OPENLIST_URL="${OPENLIST_URL%/}" - -log_info() { echo " [✓] $1"; } -log_warn() { echo " [!] $1"; } -log_error() { echo " [✗] $1"; } - -# ── 获取 Token ── -get_token() { - if [ -n "$OPENLIST_TOKEN" ]; then - echo "$OPENLIST_TOKEN" - return - fi - - log_info "正在登录 OpenList..." >&2 - local resp - resp=$(curl -s -X POST "${OPENLIST_URL}/api/auth/login" \ - -H "Content-Type: application/json" \ - -d "{\"username\":\"${OPENLIST_USER}\",\"password\":\"${OPENLIST_PASS}\"}" 2>/dev/null) - - local token - # 尝试解析 JSON 响应 (兼容多种 alist 版本) - # alist v3 响应格式: {"code":200,"data":{"token":"xxx"},"message":"success"} - if command -v jq >/dev/null 2>&1; then - token=$(echo "$resp" | jq -r '.data.token // empty' 2>/dev/null) - else - # 无 jq 时用 grep/sed 提取 - token=$(echo "$resp" | grep -o '"token":"[^"]*"' | sed 's/"token":"//;s/"//') - fi - - if [ -z "$token" ]; then - log_error "登录失败" >&2 - echo " 响应: $resp" >&2 - exit 1 - fi - - log_info "登录成功" >&2 - echo "$token" -} - -# ── 创建远程目录 ── -create_remote_dir() { - local token="$1" - local remote_path="$2" - - curl -s -X POST "${OPENLIST_URL}/api/fs/mkdir" \ - -H "Authorization: ${token}" \ - -H "Content-Type: application/json" \ - -d "{\"path\":\"${remote_path}\"}" >/dev/null 2>&1 || true -} - -# ── 上传单个文件 ── -upload_file() { - local token="$1" - local local_file="$2" - local remote_path="$3" - local filename=$(basename "$local_file") - local fsize=$(du -h "$local_file" | cut -f1) - - echo " 上传: ${filename} (${fsize})..." - - local resp - resp=$(curl -s -X PUT "${OPENLIST_URL}/api/fs/put" \ - -H "Authorization: ${token}" \ - -H "File-Path: ${remote_path}/${filename}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary "@${local_file}" \ - --max-time 3600 2>/dev/null) - - # 检查响应 - local code="" - if command -v jq >/dev/null 2>&1; then - code=$(echo "$resp" | jq -r '.code // empty' 2>/dev/null) - else - code=$(echo "$resp" | grep -o '"code":[0-9]*' | grep -o '[0-9]*') - fi - - if [ "$code" = "200" ]; then - log_info "${filename} 上传成功" - else - log_error "${filename} 上传失败" - echo " 响应: $resp" - fi -} - -# ── 主流程 ── -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ 上传到 OpenList 网盘 ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" -echo " 服务地址: ${OPENLIST_URL}" -echo " 上传模式: ${UPLOAD_MODE}" -echo " 上传路径: ${UPLOAD_ROOT}/${UPLOAD_SUBDIR}/v${PKG_VERSION}" -echo " 本地目录: ${DIST_DIR}" -echo "" - -# 查找要上传的文件 -UPLOAD_FILES="" -case "$UPLOAD_MODE" in - offline) - # 离线包: 仅 *_offline.run 文件 - UPLOAD_FILES=$(find "$DIST_DIR" -name "*_offline.run" 2>/dev/null) - ;; - online) - # 在线包: .run (非 offline) + .ipk - for f in "$DIST_DIR"/*.run "$DIST_DIR"/*.ipk; do - [ -f "$f" ] || continue - case "$(basename "$f")" in *_offline.run) continue ;; esac - UPLOAD_FILES="$UPLOAD_FILES $f" - done - UPLOAD_FILES=$(echo "$UPLOAD_FILES" | sed 's/^ //') - ;; -esac +API_URL="https://list.910501.xyz/api/packages" +UPLOAD_FILES=$(find "$DIST_DIR" -type f -name "*.run" -o -name "*.ipk" 2>/dev/null) if [ -z "$UPLOAD_FILES" ]; then - echo "错误: 未找到可上传的文件" - echo " 模式: $UPLOAD_MODE" - echo " 目录: $DIST_DIR" - exit 1 + echo "错误: 在 $DIST_DIR 中没有找到 .run 或 .ipk 文件" + exit 1 fi -# 获取 token -TOKEN=$(get_token) +echo "找到以下文件准备上传:" +echo "$UPLOAD_FILES" -# 创建远程目录 -REMOTE_DIR="${UPLOAD_ROOT}/${UPLOAD_SUBDIR}/v${PKG_VERSION}" -echo "" -echo "创建远程目录: ${REMOTE_DIR}" -create_remote_dir "$TOKEN" "$REMOTE_DIR" - -# 上传文件 -echo "" -echo "开始上传..." -UPLOAD_COUNT=0 -for f in $UPLOAD_FILES; do - upload_file "$TOKEN" "$f" "$REMOTE_DIR" - UPLOAD_COUNT=$((UPLOAD_COUNT + 1)) +for file in $UPLOAD_FILES; do + filename=$(basename "$file") + echo "--------------------------------------------------" + echo "正在上传: $filename" + + RESPONSE=$(curl -s -X POST "$API_URL" \ + -H "Authorization: Bearer $OPENLIST_TOKEN" \ + -F "file=@$file") + + HTTP_CODE=$(echo "$RESPONSE" | grep -o 'HTTP/1.1 [0-9]*' | awk '{print $2}') + if echo "$RESPONSE" | grep -q '"success":true' || echo "$RESPONSE" | grep -q 'ok'; then + echo "✅ 上传成功: $filename" + else + echo "❌ 上传失败: $filename" + echo "返回内容: $RESPONSE" + exit 1 + fi done -echo "" -echo "╔══════════════════════════════════════════════════════════════╗" -printf "║ ✅ 上传完成!共 %-2s 个文件 ║\n" "$UPLOAD_COUNT" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" -echo "下载地址: ${OPENLIST_URL}${REMOTE_DIR}" +echo "==================================================" +echo "🎉 所有文件已成功上传至 OpenList!"