mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 06:45:13 +08:00
✨ Sync 2026-03-14 19:52:12
This commit is contained in:
1 parent
364f734d16
commit
2d75925beb
43 files changed
+7870
-35
No files matched your search
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/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
|
||||
|
||||
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})"
|
||||
|
||||
# 使用实际版本号作为文件名 (Alpine apk 的 nodejs 版本可能与请求版本不同)
|
||||
if [ "$ACTUAL_VER" != "$NODE_VER" ]; then
|
||||
echo "WARNING: Actual version (${ACTUAL_VER}) differs from requested (${NODE_VER})"
|
||||
echo " Using actual version for package name"
|
||||
fi
|
||||
PKG_NAME="node-v${ACTUAL_VER}-linux-arm64-musl"
|
||||
PKG_DIR="/tmp/${PKG_NAME}"
|
||||
mkdir -p "${PKG_DIR}/bin" "${PKG_DIR}/lib/node_modules" "${PKG_DIR}/include/node"
|
||||
|
||||
# 复制 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 (直接调用 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
|
||||
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 ==="
|
||||
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
|
||||
tar cJf "/output/${PKG_NAME}.tar.xz" "${PKG_NAME}"
|
||||
ls -lh "/output/${PKG_NAME}.tar.xz"
|
||||
echo "=== Done: ${PKG_NAME}.tar.xz ==="
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/bin/sh
|
||||
# ============================================================================
|
||||
# 本地构建 .ipk 包 (无需 OpenWrt SDK)
|
||||
# 用法: sh scripts/build_ipk.sh [output_dir]
|
||||
# ============================================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||
OUT_DIR="${1:-$PKG_DIR/dist}"
|
||||
# 确保 OUT_DIR 是绝对路径
|
||||
case "$OUT_DIR" in
|
||||
/*) ;;
|
||||
*) OUT_DIR="$PKG_DIR/$OUT_DIR" ;;
|
||||
esac
|
||||
mkdir -p "$OUT_DIR"
|
||||
PKG_NAME="luci-app-openclaw"
|
||||
PKG_VERSION=$(cat "$PKG_DIR/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "1.0.0")
|
||||
PKG_RELEASE="1"
|
||||
|
||||
echo "=== 构建 ${PKG_NAME} .ipk 包 ==="
|
||||
|
||||
STAGING=$(mktemp -d)
|
||||
trap "rm -rf '$STAGING'" EXIT
|
||||
|
||||
# ── 构建 data.tar.gz ──
|
||||
DATA_DIR="$STAGING/data"
|
||||
mkdir -p "$DATA_DIR"
|
||||
|
||||
# UCI config
|
||||
mkdir -p "$DATA_DIR/etc/config"
|
||||
cp "$PKG_DIR/root/etc/config/openclaw" "$DATA_DIR/etc/config/"
|
||||
|
||||
# UCI defaults
|
||||
mkdir -p "$DATA_DIR/etc/uci-defaults"
|
||||
cp "$PKG_DIR/root/etc/uci-defaults/99-openclaw" "$DATA_DIR/etc/uci-defaults/"
|
||||
chmod +x "$DATA_DIR/etc/uci-defaults/99-openclaw"
|
||||
|
||||
# init.d
|
||||
mkdir -p "$DATA_DIR/etc/init.d"
|
||||
cp "$PKG_DIR/root/etc/init.d/openclaw" "$DATA_DIR/etc/init.d/"
|
||||
chmod +x "$DATA_DIR/etc/init.d/openclaw"
|
||||
|
||||
# bin
|
||||
mkdir -p "$DATA_DIR/usr/bin"
|
||||
cp "$PKG_DIR/root/usr/bin/openclaw-env" "$DATA_DIR/usr/bin/"
|
||||
chmod +x "$DATA_DIR/usr/bin/openclaw-env"
|
||||
|
||||
# LuCI controller
|
||||
mkdir -p "$DATA_DIR/usr/lib/lua/luci/controller"
|
||||
cp "$PKG_DIR/luasrc/controller/openclaw.lua" "$DATA_DIR/usr/lib/lua/luci/controller/"
|
||||
|
||||
# LuCI CBI
|
||||
mkdir -p "$DATA_DIR/usr/lib/lua/luci/model/cbi/openclaw"
|
||||
cp "$PKG_DIR/luasrc/model/cbi/openclaw/"*.lua "$DATA_DIR/usr/lib/lua/luci/model/cbi/openclaw/"
|
||||
|
||||
# LuCI views
|
||||
mkdir -p "$DATA_DIR/usr/lib/lua/luci/view/openclaw"
|
||||
cp "$PKG_DIR/luasrc/view/openclaw/"*.htm "$DATA_DIR/usr/lib/lua/luci/view/openclaw/"
|
||||
|
||||
# oc-config assets
|
||||
mkdir -p "$DATA_DIR/usr/share/openclaw"
|
||||
cp "$PKG_DIR/VERSION" "$DATA_DIR/usr/share/openclaw/VERSION"
|
||||
cp "$PKG_DIR/root/usr/share/openclaw/oc-config.sh" "$DATA_DIR/usr/share/openclaw/"
|
||||
chmod +x "$DATA_DIR/usr/share/openclaw/oc-config.sh"
|
||||
cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$DATA_DIR/usr/share/openclaw/"
|
||||
|
||||
# Web PTY UI
|
||||
cp -r "$PKG_DIR/root/usr/share/openclaw/ui" "$DATA_DIR/usr/share/openclaw/"
|
||||
|
||||
# i18n (po2lmo 可选)
|
||||
mkdir -p "$DATA_DIR/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" "$DATA_DIR/usr/lib/lua/luci/i18n/openclaw.zh-cn.lmo" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 计算安装大小
|
||||
INSTALLED_SIZE=$(du -sk "$DATA_DIR" | awk '{print $1}')
|
||||
|
||||
(cd "$DATA_DIR" && tar czf "$STAGING/data.tar.gz" .)
|
||||
|
||||
# ── 构建 control.tar.gz ──
|
||||
CTRL_DIR="$STAGING/control"
|
||||
mkdir -p "$CTRL_DIR"
|
||||
|
||||
cat > "$CTRL_DIR/control" << EOF
|
||||
Package: ${PKG_NAME}
|
||||
Version: ${PKG_VERSION}-${PKG_RELEASE}
|
||||
Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
|
||||
Source: https://github.com/10000ge10000/luci-app-openclaw
|
||||
SourceName: ${PKG_NAME}
|
||||
License: GPL-3.0
|
||||
Section: luci
|
||||
SourceDateEpoch: $(date +%s)
|
||||
Maintainer: 10000ge10000 <10000ge10000@users.noreply.github.com>
|
||||
Architecture: all
|
||||
Installed-Size: ${INSTALLED_SIZE}
|
||||
Description: OpenClaw AI 网关 LuCI 管理插件
|
||||
EOF
|
||||
|
||||
cat > "$CTRL_DIR/postinst" << 'EOF'
|
||||
#!/bin/sh
|
||||
[ -n "${IPKG_INSTROOT}" ] || {
|
||||
( . /etc/uci-defaults/99-openclaw ) && rm -f /etc/uci-defaults/99-openclaw
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* /tmp/luci-indexcache.*.json 2>/dev/null
|
||||
# 重启 Web PTY (使其加载新文件和新 token)
|
||||
PTY_PID=$(pgrep -f 'web-pty.js' 2>/dev/null | head -1)
|
||||
[ -n "$PTY_PID" ] && kill "$PTY_PID" 2>/dev/null || true
|
||||
exit 0
|
||||
}
|
||||
EOF
|
||||
chmod +x "$CTRL_DIR/postinst"
|
||||
|
||||
cat > "$CTRL_DIR/postrm" << 'EOF'
|
||||
#!/bin/sh
|
||||
[ -n "${IPKG_INSTROOT}" ] || {
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null
|
||||
}
|
||||
EOF
|
||||
chmod +x "$CTRL_DIR/postrm"
|
||||
|
||||
cat > "$CTRL_DIR/conffiles" << 'EOF'
|
||||
/etc/config/openclaw
|
||||
EOF
|
||||
|
||||
(cd "$CTRL_DIR" && tar czf "$STAGING/control.tar.gz" .)
|
||||
|
||||
# ── 组装 .ipk (ar 格式) ──
|
||||
mkdir -p "$OUT_DIR"
|
||||
IPK_FILE="$OUT_DIR/${PKG_NAME}_${PKG_VERSION}-${PKG_RELEASE}_all.ipk"
|
||||
|
||||
echo "2.0" > "$STAGING/debian-binary"
|
||||
|
||||
# 清理旧文件
|
||||
rm -f "$IPK_FILE"
|
||||
|
||||
# 组装 .ipk — OpenWrt opkg 使用 tar.gz 格式 (非 Debian 的 ar 格式)
|
||||
(cd "$STAGING" && tar czf "$IPK_FILE" debian-binary control.tar.gz data.tar.gz)
|
||||
|
||||
IPK_SIZE=$(wc -c < "$IPK_FILE" | tr -d ' ')
|
||||
echo ""
|
||||
echo "=== 构建完成 ==="
|
||||
echo "输出文件: $IPK_FILE"
|
||||
echo "文件大小: ${IPK_SIZE} bytes"
|
||||
echo "安装大小: ${INSTALLED_SIZE} KB"
|
||||
echo ""
|
||||
echo "安装方法: opkg install ${PKG_NAME}_${PKG_VERSION}-${PKG_RELEASE}_all.ipk"
|
||||
Executable
+257
@@ -0,0 +1,257 @@
|
||||
#!/bin/sh
|
||||
# ============================================================================
|
||||
# iStoreOS .run 自解压包构建脚本
|
||||
# 用法: sh scripts/build_run.sh [output_dir]
|
||||
# ============================================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||
OUT_DIR="${1:-$PKG_DIR/dist}"
|
||||
# 确保 OUT_DIR 是绝对路径
|
||||
case "$OUT_DIR" in
|
||||
/*) ;;
|
||||
*) OUT_DIR="$PKG_DIR/$OUT_DIR" ;;
|
||||
esac
|
||||
mkdir -p "$OUT_DIR"
|
||||
PKG_NAME="luci-app-openclaw"
|
||||
PKG_VERSION=$(cat "$PKG_DIR/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "1.0.0")
|
||||
|
||||
echo "=== 构建 iStoreOS .run 安装包 ==="
|
||||
echo "源目录: $PKG_DIR"
|
||||
echo "输出到: $OUT_DIR"
|
||||
|
||||
# 创建临时打包目录
|
||||
STAGING=$(mktemp -d)
|
||||
trap "rm -rf '$STAGING'" EXIT
|
||||
|
||||
# 安装文件到暂存区
|
||||
install_files() {
|
||||
local dest="$1"
|
||||
|
||||
# UCI config (仅首次安装时部署默认配置; 升级时保留用户配置)
|
||||
# 安装器会在解压后检测并跳过已有配置, 见 install.sh 中的逻辑
|
||||
mkdir -p "$dest/etc/config"
|
||||
cp "$PKG_DIR/root/etc/config/openclaw" "$dest/etc/config/openclaw.default"
|
||||
|
||||
# UCI defaults
|
||||
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"
|
||||
|
||||
# init.d
|
||||
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"
|
||||
|
||||
# bin
|
||||
mkdir -p "$dest/usr/bin"
|
||||
cp "$PKG_DIR/root/usr/bin/openclaw-env" "$dest/usr/bin/"
|
||||
chmod +x "$dest/usr/bin/openclaw-env"
|
||||
|
||||
# LuCI controller
|
||||
mkdir -p "$dest/usr/lib/lua/luci/controller"
|
||||
cp "$PKG_DIR/luasrc/controller/openclaw.lua" "$dest/usr/lib/lua/luci/controller/"
|
||||
|
||||
# LuCI CBI
|
||||
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/"
|
||||
|
||||
# LuCI views
|
||||
mkdir -p "$dest/usr/lib/lua/luci/view/openclaw"
|
||||
cp "$PKG_DIR/luasrc/view/openclaw/"*.htm "$dest/usr/lib/lua/luci/view/openclaw/"
|
||||
|
||||
# oc-config assets
|
||||
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/"
|
||||
|
||||
# Web PTY UI (recursive copy)
|
||||
cp -r "$PKG_DIR/root/usr/share/openclaw/ui" "$dest/usr/share/openclaw/"
|
||||
}
|
||||
|
||||
# 创建安装器脚本头部
|
||||
create_installer() {
|
||||
cat > "$STAGING/install.sh" << 'INSTALLER_EOF'
|
||||
#!/bin/sh
|
||||
# luci-app-openclaw iStoreOS 安装器
|
||||
set -e
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ luci-app-openclaw — OpenClaw AI Gateway 插件 ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# 检查系统
|
||||
if [ ! -f /etc/openwrt_release ]; then
|
||||
echo "错误: 此安装包仅适用于 OpenWrt/iStoreOS 系统"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查架构
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64|aarch64) ;;
|
||||
*) echo "错误: 不支持的架构 $ARCH (仅支持 x86_64/aarch64)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# 检查依赖
|
||||
for dep in luci-compat luci-base; do
|
||||
if ! opkg list-installed 2>/dev/null | grep -q "^${dep} "; then
|
||||
echo "警告: 缺少依赖 $dep,尝试安装..."
|
||||
opkg update >/dev/null 2>&1 || true
|
||||
opkg install "$dep" 2>/dev/null || echo " 安装 $dep 失败,请手动安装"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "正在安装文件..."
|
||||
|
||||
# 解压 payload (从 MARKER 行之后)
|
||||
ARCHIVE=$(awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$0")
|
||||
tail -n +$ARCHIVE "$0" | tar xzf - -C / 2>/dev/null
|
||||
|
||||
# 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
|
||||
|
||||
# 注册到 opkg,使 iStore 和 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"
|
||||
|
||||
# 写入 control 文件
|
||||
cat > "$INFO_DIR/$PKG.control" << CTLEOF
|
||||
Package: $PKG
|
||||
Version: $PKG_VER
|
||||
Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
|
||||
Section: luci
|
||||
Architecture: all
|
||||
Installed-Size: 0
|
||||
Description: OpenClaw AI Gateway — LuCI 界面
|
||||
CTLEOF
|
||||
|
||||
# 写入文件列表 (payload 中已安装的文件)
|
||||
cat > "$INFO_DIR/$PKG.list" << LISTEOF
|
||||
__FILE_LIST__
|
||||
LISTEOF
|
||||
|
||||
# 写入 prerm 脚本 (卸载前执行)
|
||||
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, curl, openssl-util, script-utils, tar
|
||||
Status: install user installed
|
||||
Architecture: all
|
||||
Conffiles:
|
||||
/etc/config/openclaw 0
|
||||
Installed-Time: $INSTALL_TIME
|
||||
STEOF
|
||||
|
||||
echo "已注册到 opkg (iStore 可管理)"
|
||||
|
||||
# 执行 uci-defaults
|
||||
if [ -f /etc/uci-defaults/99-openclaw ]; then
|
||||
echo "执行初始化脚本..."
|
||||
( . /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
|
||||
|
||||
# 重启 Web PTY 服务 (使其加载新文件和新 token)
|
||||
# PTY 是 procd 管理的实例, kill 后 procd 会自动 respawn
|
||||
PTY_PID=$(pgrep -f 'web-pty.js' 2>/dev/null | head -1)
|
||||
if [ -n "$PTY_PID" ]; then
|
||||
echo "重启配置终端服务..."
|
||||
kill "$PTY_PID" 2>/dev/null
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ 安装完成!"
|
||||
echo ""
|
||||
echo "后续步骤:"
|
||||
echo " 1. 运行 openclaw-env setup — 下载 Node.js 并安装 OpenClaw"
|
||||
echo " 2. 访问 LuCI → 服务 → OpenClaw 进行配置"
|
||||
echo " 3. 或执行 /etc/init.d/openclaw enable && /etc/init.d/openclaw start"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
__ARCHIVE_BELOW__
|
||||
INSTALLER_EOF
|
||||
}
|
||||
|
||||
# 构建
|
||||
echo ""
|
||||
echo "[1/4] 安装文件到暂存区..."
|
||||
install_files "$STAGING/payload"
|
||||
|
||||
echo "[2/4] 生成文件列表..."
|
||||
# 生成安装文件列表 (供 opkg 卸载时使用)
|
||||
# 注: openclaw.default 安装后会变为 openclaw, 文件列表中记录最终路径
|
||||
FILE_LIST=$(cd "$STAGING/payload" && find . -type f | sed 's|^\./|/|' | sed 's|/etc/config/openclaw.default|/etc/config/openclaw|' | sort)
|
||||
echo " 共 $(echo "$FILE_LIST" | wc -l | tr -d ' ') 个文件"
|
||||
|
||||
echo "[3/4] 创建安装器..."
|
||||
create_installer
|
||||
|
||||
# 替换安装器中的占位符
|
||||
sed -i "s|__PKG_VERSION__|${PKG_VERSION}|g" "$STAGING/install.sh"
|
||||
# 替换文件列表占位符 — 使用临时文件拼接避免 sed/awk 多行问题
|
||||
{
|
||||
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"
|
||||
|
||||
echo "[4/4] 打包..."
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# 创建 payload tarball
|
||||
(cd "$STAGING/payload" && tar czf "$STAGING/payload.tar.gz" .)
|
||||
|
||||
# 组合: installer header + payload
|
||||
RUN_FILE="$OUT_DIR/${PKG_NAME}_${PKG_VERSION}.run"
|
||||
cat "$STAGING/install.sh" "$STAGING/payload.tar.gz" > "$RUN_FILE"
|
||||
chmod +x "$RUN_FILE"
|
||||
|
||||
FILE_SIZE=$(wc -c < "$RUN_FILE" | tr -d ' ')
|
||||
echo ""
|
||||
echo "=== 构建完成 ==="
|
||||
echo "输出文件: $RUN_FILE"
|
||||
echo "文件大小: $FILE_SIZE bytes"
|
||||
echo ""
|
||||
echo "安装方法: sh ${PKG_NAME}_${PKG_VERSION}.run"
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# 用法: gen-release-body.sh <版本号> <CHANGELOG路径> <输出目录>
|
||||
# 为指定版本生成 GitHub Release body markdown 文件
|
||||
set -e
|
||||
|
||||
VER="$1"
|
||||
CHANGELOG_FILE="$2"
|
||||
OUT_DIR="$3"
|
||||
|
||||
if [ -z "$VER" ] || [ -z "$CHANGELOG_FILE" ] || [ -z "$OUT_DIR" ]; then
|
||||
echo "用法: $0 <版本号> <CHANGELOG路径> <输出目录>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# 提取该版本的 changelog
|
||||
CONTENT=$(awk "/^## \\[${VER}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" "$CHANGELOG_FILE")
|
||||
if [ -z "$CONTENT" ]; then
|
||||
CONTENT="暂无更新日志"
|
||||
fi
|
||||
|
||||
# 写入文件
|
||||
{
|
||||
printf '%s\n' "$CONTENT"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo '**在线安装** (需联网,自动下载 Node.js + OpenClaw)'
|
||||
echo '```'
|
||||
echo '# iStoreOS'
|
||||
echo "sh luci-app-openclaw_${VER}.run"
|
||||
echo ''
|
||||
echo '# OpenWrt'
|
||||
echo "opkg install luci-app-openclaw_${VER}-1_all.ipk"
|
||||
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"
|
||||
|
||||
echo "✓ ${VER}.md ($(wc -l < "${OUT_DIR}/${VER}.md") 行)"
|
||||
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/bin/sh
|
||||
# ============================================================================
|
||||
# OpenList 网盘同步脚本 — 补齐所有历史版本 + 上传更新记录
|
||||
#
|
||||
# 功能:
|
||||
# 1. 从 GitHub Releases 下载所有版本的 .run + .ipk
|
||||
# 2. 从 CHANGELOG.md 提取每个版本的更新记录,生成 更新记录.txt
|
||||
# 3. 上传到 OpenList 网盘的 openclaw-在线安装 目录
|
||||
#
|
||||
# 用法:
|
||||
# sh scripts/sync_openlist.sh
|
||||
# ============================================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||
|
||||
# ── 配置 ──
|
||||
GITHUB_REPO="10000ge10000/luci-app-openclaw"
|
||||
OPENLIST_URL="http://124.243.178.237:15244"
|
||||
OPENLIST_USER="admin"
|
||||
OPENLIST_PASS="mingmenmama"
|
||||
OPENLIST_ROOT="/Quark"
|
||||
UPLOAD_SUBDIR="openclaw-在线安装"
|
||||
CHANGELOG="$PKG_DIR/CHANGELOG.md"
|
||||
WORK_DIR="/tmp/openlist-sync"
|
||||
|
||||
# 所有已发布的版本 (按时间顺序)
|
||||
ALL_VERSIONS="1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.0.10 1.0.11 1.0.12 1.0.14 1.0.15"
|
||||
|
||||
log_info() { printf " [\033[32m✓\033[0m] %s\n" "$1"; }
|
||||
log_warn() { printf " [\033[33m!\033[0m] %s\n" "$1"; }
|
||||
log_error() { printf " [\033[31m✗\033[0m] %s\n" "$1"; }
|
||||
log_skip() { printf " [\033[36m-\033[0m] %s\n" "$1"; }
|
||||
|
||||
# ── 获取 Token ──
|
||||
get_token() {
|
||||
local resp
|
||||
resp=$(curl -s -X POST "${OPENLIST_URL}/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"${OPENLIST_USER}\",\"password\":\"${OPENLIST_PASS}\"}")
|
||||
|
||||
local token
|
||||
token=$(echo "$resp" | grep -o '"token":"[^"]*"' | sed 's/"token":"//;s/"//')
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
log_error "OpenList 登录失败"
|
||||
echo " 响应: $resp"
|
||||
exit 1
|
||||
fi
|
||||
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
|
||||
}
|
||||
|
||||
# ── 检查远程文件是否存在 ──
|
||||
remote_file_exists() {
|
||||
local token="$1"
|
||||
local remote_path="$2"
|
||||
local filename="$3"
|
||||
|
||||
local resp
|
||||
resp=$(curl -s -X POST "${OPENLIST_URL}/api/fs/list" \
|
||||
-H "Authorization: ${token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"path\":\"${remote_path}\",\"refresh\":false}")
|
||||
|
||||
echo "$resp" | grep -q "\"name\":\"${filename}\""
|
||||
}
|
||||
|
||||
# ── 上传单个文件 ──
|
||||
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)
|
||||
|
||||
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 300 2>/dev/null)
|
||||
|
||||
local code=""
|
||||
code=$(echo "$resp" | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
|
||||
|
||||
if [ "$code" = "200" ]; then
|
||||
log_info "${filename} (${fsize}) 上传成功"
|
||||
else
|
||||
log_error "${filename} 上传失败: $resp"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── 从 CHANGELOG.md 提取指定版本的更新日志 ──
|
||||
extract_changelog() {
|
||||
local version="$1"
|
||||
local output_file="$2"
|
||||
|
||||
awk "/^## \\[${version}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" \
|
||||
"$CHANGELOG" > "$output_file"
|
||||
|
||||
# 去掉首尾空行
|
||||
sed -i '/./,$!d' "$output_file" # 去掉开头空行
|
||||
sed -i ':a; /^[[:space:]]*$/{ $d; N; ba }' "$output_file" # 去掉末尾空行 (GNU sed)
|
||||
|
||||
if [ ! -s "$output_file" ]; then
|
||||
echo "暂无更新日志" > "$output_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── 从 GitHub 下载文件 ──
|
||||
download_release_file() {
|
||||
local version="$1"
|
||||
local filename="$2"
|
||||
local output="$3"
|
||||
|
||||
local url="https://github.com/${GITHUB_REPO}/releases/download/v${version}/${filename}"
|
||||
|
||||
if [ -f "$output" ]; then
|
||||
log_skip "${filename} 已在本地缓存"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if curl -sL --fail -o "$output" "$url" 2>/dev/null; then
|
||||
# 检查是否为有效文件 (排除 GitHub 返回 Not Found HTML)
|
||||
local size=$(wc -c < "$output")
|
||||
if [ "$size" -lt 1000 ]; then
|
||||
local content=$(cat "$output")
|
||||
if echo "$content" | grep -qi "not found"; then
|
||||
rm -f "$output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
rm -f "$output"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# 主流程
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
|
||||
echo ""
|
||||
echo "================================================================"
|
||||
echo " OpenList 网盘同步 — 补齐所有历史版本"
|
||||
echo "================================================================"
|
||||
echo ""
|
||||
|
||||
# 准备工作目录
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
# 登录
|
||||
echo "正在登录 OpenList..."
|
||||
TOKEN=$(get_token)
|
||||
log_info "登录成功"
|
||||
echo ""
|
||||
|
||||
TOTAL_UPLOADED=0
|
||||
|
||||
for VER in $ALL_VERSIONS; do
|
||||
echo "── v${VER} ──────────────────────────────────────"
|
||||
REMOTE_DIR="${OPENLIST_ROOT}/${UPLOAD_SUBDIR}/v${VER}"
|
||||
VER_DIR="${WORK_DIR}/v${VER}"
|
||||
mkdir -p "$VER_DIR"
|
||||
|
||||
# 创建远程目录
|
||||
create_remote_dir "$TOKEN" "$REMOTE_DIR"
|
||||
|
||||
# 1) 下载 .run
|
||||
RUN_FILE="luci-app-openclaw_${VER}.run"
|
||||
if remote_file_exists "$TOKEN" "$REMOTE_DIR" "$RUN_FILE"; then
|
||||
log_skip "${RUN_FILE} 已存在于网盘"
|
||||
else
|
||||
echo " 下载 ${RUN_FILE}..."
|
||||
if download_release_file "$VER" "$RUN_FILE" "${VER_DIR}/${RUN_FILE}"; then
|
||||
upload_file "$TOKEN" "${VER_DIR}/${RUN_FILE}" "$REMOTE_DIR"
|
||||
TOTAL_UPLOADED=$((TOTAL_UPLOADED + 1))
|
||||
else
|
||||
log_error "${RUN_FILE} 下载失败"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2) 下载 .ipk
|
||||
IPK_FILE="luci-app-openclaw_${VER}-1_all.ipk"
|
||||
if remote_file_exists "$TOKEN" "$REMOTE_DIR" "$IPK_FILE"; then
|
||||
log_skip "${IPK_FILE} 已存在于网盘"
|
||||
else
|
||||
echo " 下载 ${IPK_FILE}..."
|
||||
if download_release_file "$VER" "$IPK_FILE" "${VER_DIR}/${IPK_FILE}"; then
|
||||
upload_file "$TOKEN" "${VER_DIR}/${IPK_FILE}" "$REMOTE_DIR"
|
||||
TOTAL_UPLOADED=$((TOTAL_UPLOADED + 1))
|
||||
else
|
||||
log_error "${IPK_FILE} 下载失败"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) 生成并上传 更新记录.txt
|
||||
CHANGELOG_FILE="更新记录.txt"
|
||||
if remote_file_exists "$TOKEN" "$REMOTE_DIR" "$CHANGELOG_FILE"; then
|
||||
log_skip "${CHANGELOG_FILE} 已存在于网盘"
|
||||
else
|
||||
extract_changelog "$VER" "${VER_DIR}/${CHANGELOG_FILE}"
|
||||
upload_file "$TOKEN" "${VER_DIR}/${CHANGELOG_FILE}" "$REMOTE_DIR"
|
||||
TOTAL_UPLOADED=$((TOTAL_UPLOADED + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# 清理
|
||||
rm -rf "$WORK_DIR"
|
||||
|
||||
echo "================================================================"
|
||||
echo " 同步完成!共上传 ${TOTAL_UPLOADED} 个文件"
|
||||
echo "================================================================"
|
||||
echo ""
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
PKG_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||
DIST_DIR="${1:-$PKG_DIR/dist}"
|
||||
|
||||
if [ -z "$OPENLIST_URL" ]; then
|
||||
echo "错误: 请设置 OPENLIST_URL 环境变量"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$OPENLIST_TOKEN" ] && { [ -z "$OPENLIST_USER" ] || [ -z "$OPENLIST_PASS" ]; }; then
|
||||
echo "错误: 请设置登录凭据"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UPLOAD_ROOT="${OPENLIST_PATH:-/}"
|
||||
PKG_VERSION=$(cat "$PKG_DIR/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "unknown")
|
||||
UPLOAD_ROOT="${UPLOAD_ROOT%/}"
|
||||
UPLOAD_SUBDIR="openclaw-在线安装"
|
||||
OPENLIST_URL="${OPENLIST_URL%/}"
|
||||
|
||||
get_token() {
|
||||
if [ -n "$OPENLIST_TOKEN" ]; then echo "$OPENLIST_TOKEN"; return; fi
|
||||
local resp=$(curl -s -X POST "${OPENLIST_URL}/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"${OPENLIST_USER}\",\"password\":\"${OPENLIST_PASS}\"}")
|
||||
local token=$(echo "$resp" | grep -o '"token":"[^"]*"' | sed 's/"token":"//;s/"//' | head -n 1)
|
||||
if [ -z "$token" ]; then exit 1; fi
|
||||
echo "$token"
|
||||
}
|
||||
|
||||
create_remote_dir() {
|
||||
curl -s -X POST "${OPENLIST_URL}/api/fs/mkdir" \
|
||||
-H "Authorization: ${1}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"path\":\"${2}\"}" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
upload_file() {
|
||||
local filename=$(basename "$2")
|
||||
echo " 上传: ${filename} ..."
|
||||
local resp=$(curl -s -X PUT "${OPENLIST_URL}/api/fs/put" \
|
||||
-H "Authorization: ${1}" \
|
||||
-H "File-Path: ${3}/${filename}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@${2}" \
|
||||
--max-time 3600)
|
||||
local code=$(echo "$resp" | grep -o '"code":[0-9]*' | grep -o '[0-9]*' | head -n 1)
|
||||
if [ "$code" = "200" ]; then echo " [✓] 上传成功"; else echo " [✗] 失败: $resp"; fi
|
||||
}
|
||||
|
||||
UPLOAD_FILES=$(find "$DIST_DIR" -type f -name "*.run" -o -name "*.ipk" 2>/dev/null)
|
||||
if [ -z "$UPLOAD_FILES" ]; then echo "错误: 未找到可上传文件"; exit 1; fi
|
||||
|
||||
TOKEN=$(get_token)
|
||||
REMOTE_DIR="${UPLOAD_ROOT}/${UPLOAD_SUBDIR}/v${PKG_VERSION}"
|
||||
REMOTE_DIR=$(echo "$REMOTE_DIR" | sed 's#^//#/#g')
|
||||
|
||||
echo "创建远程目录: ${REMOTE_DIR}"
|
||||
create_remote_dir "$TOKEN" "$REMOTE_DIR"
|
||||
|
||||
for f in $UPLOAD_FILES; do
|
||||
upload_file "$TOKEN" "$f" "$REMOTE_DIR"
|
||||
done
|
||||
echo "✅ 上传完成!"
|
||||
Reference in new issue
Block a user