mirror of
https://github.com/hotwa/luci-app-openclaw.git
synced 2026-03-31 04:52:33 +00:00
479 lines
15 KiB
Bash
Executable File
479 lines
15 KiB
Bash
Executable File
#!/bin/sh
|
||
# ============================================================================
|
||
# openclaw-env — Node.js 环境自动检测/下载 + OpenClaw 安装
|
||
# 用法:
|
||
# openclaw-env setup — 完整安装 (Node.js + pnpm + OpenClaw)
|
||
# openclaw-env check — 检查环境状态
|
||
# openclaw-env upgrade — 升级 OpenClaw 到最新版
|
||
# openclaw-env node — 仅下载/更新 Node.js
|
||
# 环境变量:
|
||
# OC_VERSION — 指定 OpenClaw 版本 (如 2026.3.1),不设置则安装最新版
|
||
# ============================================================================
|
||
set -e
|
||
|
||
NODE_VERSION="${NODE_VERSION:-22.16.0}"
|
||
# 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
|
||
OC_TESTED_VERSION="2026.3.1"
|
||
# 用户可通过 OC_VERSION 环境变量覆盖安装版本
|
||
OC_VERSION="${OC_VERSION:-}"
|
||
NODE_BASE="/opt/openclaw/node"
|
||
OC_GLOBAL="/opt/openclaw/global"
|
||
OC_DATA="/opt/openclaw/data"
|
||
NODE_BIN="${NODE_BASE}/bin/node"
|
||
NPM_BIN="${NODE_BASE}/bin/npm"
|
||
PNPM_BIN="${OC_GLOBAL}/bin/pnpm"
|
||
|
||
# Node.js 官方镜像 + musl 非官方构建
|
||
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"
|
||
|
||
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH"
|
||
|
||
log_info() { echo " [✓] $1"; }
|
||
log_warn() { echo " [!] $1"; }
|
||
log_error() { echo " [✗] $1"; }
|
||
|
||
# 检测 C 运行时类型 (glibc vs musl)
|
||
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
|
||
}
|
||
|
||
# 在所有可能的位置查找 openclaw 入口文件
|
||
# pnpm 全局安装路径形如: $OC_GLOBAL/5/node_modules/openclaw
|
||
find_oc_entry() {
|
||
local search_dirs="${OC_GLOBAL}/lib/node_modules/openclaw
|
||
${OC_GLOBAL}/node_modules/openclaw
|
||
${NODE_BASE}/lib/node_modules/openclaw"
|
||
|
||
# 添加 pnpm 版本化目录 (如 /opt/openclaw/global/5/node_modules/openclaw)
|
||
for ver_dir in "${OC_GLOBAL}"/*/node_modules/openclaw; do
|
||
[ -d "$ver_dir" ] 2>/dev/null && search_dirs="$search_dirs
|
||
$ver_dir"
|
||
done
|
||
|
||
local d
|
||
echo "$search_dirs" | while read -r d; do
|
||
[ -z "$d" ] && continue
|
||
if [ -f "${d}/openclaw.mjs" ]; then
|
||
echo "${d}/openclaw.mjs"
|
||
return
|
||
elif [ -f "${d}/dist/cli.js" ]; then
|
||
echo "${d}/dist/cli.js"
|
||
return
|
||
fi
|
||
done
|
||
}
|
||
|
||
detect_arch() {
|
||
local arch
|
||
arch=$(uname -m)
|
||
case "$arch" in
|
||
x86_64) echo "linux-x64" ;;
|
||
aarch64) echo "linux-arm64" ;;
|
||
armv7l|armv6l)
|
||
log_error "不支持的 CPU 架构: $arch"
|
||
log_error "Node.js v22+ 不提供 32 位 ARM 预编译包。"
|
||
log_error "建议: 使用 aarch64 (ARM64) 版本的固件,或使用 x86_64 设备。"
|
||
exit 1
|
||
;;
|
||
*)
|
||
log_error "不支持的 CPU 架构: $arch (仅支持 x86_64/aarch64)"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
download_node() {
|
||
local node_ver="$1"
|
||
local node_arch
|
||
node_arch=$(detect_arch)
|
||
local libc_type
|
||
libc_type=$(detect_libc)
|
||
|
||
local tarball=""
|
||
local url="" url_fallback=""
|
||
|
||
if [ "$libc_type" = "musl" ]; then
|
||
tarball="node-v${node_ver}-${node_arch}-musl.tar.xz"
|
||
url="${NODE_MUSL_MIRROR}/v${node_ver}/${tarball}"
|
||
url_fallback=""
|
||
echo ""
|
||
echo "=== 下载 Node.js v${node_ver} (${node_arch}, musl libc) ==="
|
||
else
|
||
tarball="node-v${node_ver}-${node_arch}.tar.xz"
|
||
url="${NODE_MIRROR}/v${node_ver}/${tarball}"
|
||
url_fallback="${NODE_MIRROR_CN}/v${node_ver}/${tarball}"
|
||
echo ""
|
||
echo "=== 下载 Node.js v${node_ver} (${node_arch}, glibc) ==="
|
||
fi
|
||
|
||
local tmp_file="/tmp/${tarball}"
|
||
|
||
# 如果已存在且版本正确, 跳过
|
||
if [ -x "$NODE_BIN" ]; then
|
||
local current_ver
|
||
current_ver=$("$NODE_BIN" --version 2>/dev/null | sed 's/^v//')
|
||
if [ "$current_ver" = "$node_ver" ]; then
|
||
log_info "Node.js v${node_ver} 已安装, 跳过下载"
|
||
return 0
|
||
fi
|
||
log_warn "当前 Node.js v${current_ver}, 将更新到 v${node_ver}"
|
||
fi
|
||
|
||
# 下载 (带重试)
|
||
local downloaded=0
|
||
local mirror_list="$url"
|
||
[ -n "$url_fallback" ] && mirror_list="$url $url_fallback"
|
||
for mirror_url in $mirror_list; do
|
||
echo " 正在从 ${mirror_url} 下载..."
|
||
if curl -fSL --connect-timeout 15 --max-time 300 -o "$tmp_file" "$mirror_url" 2>/dev/null || \
|
||
wget -q --timeout=15 -O "$tmp_file" "$mirror_url" 2>/dev/null; then
|
||
downloaded=1
|
||
break
|
||
fi
|
||
log_warn "下载失败, 尝试备用镜像..."
|
||
done
|
||
|
||
if [ "$downloaded" -eq 0 ]; then
|
||
log_error "所有镜像均下载失败"
|
||
rm -f "$tmp_file"
|
||
exit 1
|
||
fi
|
||
|
||
# 解压
|
||
echo " 正在解压到 ${NODE_BASE}..."
|
||
rm -rf "$NODE_BASE"
|
||
mkdir -p "$NODE_BASE"
|
||
tar xf "$tmp_file" -C "$NODE_BASE" --strip-components=1
|
||
rm -f "$tmp_file"
|
||
|
||
# 验证
|
||
if [ -x "$NODE_BIN" ]; then
|
||
log_info "Node.js $($NODE_BIN --version) 安装成功"
|
||
else
|
||
log_error "Node.js 安装验证失败"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
install_pnpm() {
|
||
echo ""
|
||
echo "=== 安装 pnpm ==="
|
||
|
||
if [ -x "$PNPM_BIN" ]; then
|
||
log_info "pnpm 已安装: $($PNPM_BIN --version 2>/dev/null)"
|
||
return 0
|
||
fi
|
||
|
||
# 使用 npm 安装 pnpm 到全局目录
|
||
mkdir -p "$OC_GLOBAL"
|
||
"$NPM_BIN" install -g pnpm --prefix="$OC_GLOBAL" 2>/dev/null
|
||
|
||
if [ -x "$OC_GLOBAL/bin/pnpm" ]; then
|
||
PNPM_BIN="$OC_GLOBAL/bin/pnpm"
|
||
log_info "pnpm $($PNPM_BIN --version 2>/dev/null) 安装成功"
|
||
elif [ -x "$NODE_BASE/bin/pnpm" ]; then
|
||
PNPM_BIN="$NODE_BASE/bin/pnpm"
|
||
log_info "pnpm $($PNPM_BIN --version 2>/dev/null) 安装成功"
|
||
else
|
||
log_warn "pnpm 安装失败, 将使用 npm 作为回退"
|
||
fi
|
||
}
|
||
|
||
install_openclaw() {
|
||
echo ""
|
||
echo "=== 安装 OpenClaw ==="
|
||
|
||
# 确定安装版本
|
||
local oc_pkg="openclaw"
|
||
if [ -n "$OC_VERSION" ]; then
|
||
oc_pkg="openclaw@${OC_VERSION}"
|
||
log_info "指定版本: v${OC_VERSION}"
|
||
else
|
||
oc_pkg="openclaw@latest"
|
||
log_info "安装最新版本"
|
||
fi
|
||
|
||
local libc_type
|
||
libc_type=$(detect_libc)
|
||
|
||
# musl 系统使用 npm + --ignore-scripts 避免 node-llama-cpp 编译失败
|
||
# glibc 系统正常安装
|
||
local install_flags=""
|
||
if [ "$libc_type" = "musl" ]; then
|
||
log_warn "检测到 musl libc,将跳过本地编译依赖 (不影响核心功能)"
|
||
install_flags="--ignore-scripts"
|
||
fi
|
||
|
||
# 检查 git 是否可用 (openclaw 部分依赖可能使用 git:// 协议)
|
||
if ! command -v git >/dev/null 2>&1; then
|
||
log_warn "未检测到 git,正在尝试安装..."
|
||
opkg update >/dev/null 2>&1
|
||
opkg install git git-http 2>&1 | tail -3 || true
|
||
if command -v git >/dev/null 2>&1; then
|
||
log_info "git 安装成功"
|
||
else
|
||
log_warn "git 安装失败,将尝试无 git 模式安装"
|
||
fi
|
||
fi
|
||
|
||
# 优先用 npm 安装 (pnpm 在 musl 上全局安装可能有路径问题)
|
||
local npm_ok=0
|
||
if [ -x "$NPM_BIN" ]; then
|
||
mkdir -p "$OC_GLOBAL"
|
||
"$NPM_BIN" install -g "$oc_pkg" --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -10
|
||
# 检查是否安装成功
|
||
if [ -n "$(find_oc_entry)" ]; then
|
||
npm_ok=1
|
||
else
|
||
log_warn "首次安装未成功,尝试 --no-optional 模式重试..."
|
||
"$NPM_BIN" install -g "$oc_pkg" --prefix="$OC_GLOBAL" $install_flags --no-optional 2>&1 | tail -10
|
||
[ -n "$(find_oc_entry)" ] && npm_ok=1
|
||
fi
|
||
elif [ -x "$PNPM_BIN" ]; then
|
||
mkdir -p "$OC_GLOBAL"
|
||
"$PNPM_BIN" install -g "$oc_pkg" --prefix="$OC_GLOBAL" 2>&1 | tail -5
|
||
else
|
||
log_error "npm 和 pnpm 均不可用"
|
||
exit 1
|
||
fi
|
||
|
||
# 验证
|
||
local oc_ver=""
|
||
local oc_found
|
||
oc_found=$(find_oc_entry)
|
||
if [ -n "$oc_found" ]; then
|
||
oc_ver=$("$NODE_BIN" "$oc_found" --version 2>/dev/null | tr -d '[:space:]')
|
||
fi
|
||
|
||
if [ -n "$oc_ver" ]; then
|
||
log_info "OpenClaw v${oc_ver} 安装成功"
|
||
else
|
||
log_error "OpenClaw 安装验证失败"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
init_openclaw() {
|
||
echo ""
|
||
echo "=== 初始化 OpenClaw ==="
|
||
|
||
# 创建数据目录
|
||
mkdir -p "$OC_DATA/.openclaw"
|
||
|
||
# 运行 onboard
|
||
local oc_entry=""
|
||
oc_entry=$(find_oc_entry)
|
||
|
||
if [ -n "$oc_entry" ]; then
|
||
HOME="$OC_DATA" \
|
||
OPENCLAW_HOME="$OC_DATA" \
|
||
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
|
||
OPENCLAW_CONFIG_PATH="${OC_DATA}/.openclaw/openclaw.json" \
|
||
"$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk 2>/dev/null || true
|
||
log_info "初始化完成"
|
||
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
|
||
}
|
||
|
||
do_setup() {
|
||
local node_ver="$NODE_VERSION"
|
||
|
||
# 检查是否已安装
|
||
if [ -x "$NODE_BIN" ] && [ -n "$(find_oc_entry)" ]; then
|
||
local oc_ver=""
|
||
local oc_entry="$(find_oc_entry)"
|
||
local oc_pkg_dir="$(dirname "$oc_entry")"
|
||
[ -f "$oc_pkg_dir/package.json" ] && \
|
||
oc_ver=$("$NODE_BIN" -e "try{console.log(require('$oc_pkg_dir/package.json').version)}catch(e){}" 2>/dev/null) || true
|
||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||
echo "║ ⚠️ OpenClaw 运行环境已安装,无需重复安装 ║"
|
||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||
echo ""
|
||
echo " Node.js: $($NODE_BIN --version 2>/dev/null)"
|
||
[ -n "$oc_ver" ] && echo " OpenClaw: v${oc_ver}"
|
||
echo ""
|
||
echo " 如需升级,请使用: openclaw-env upgrade"
|
||
echo " 如需重装,请先卸载: 在 LuCI 界面点击「卸载环境」"
|
||
echo ""
|
||
exit 0
|
||
fi
|
||
|
||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||
echo "║ 一万AI分享 OpenClaw 环境安装 ║"
|
||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||
echo ""
|
||
echo " 架构: $(uname -m)"
|
||
echo " Node 版本: v${node_ver}"
|
||
if [ -n "$OC_VERSION" ]; then
|
||
echo " OpenClaw: v${OC_VERSION} (稳定版)"
|
||
else
|
||
echo " OpenClaw: 最新版"
|
||
fi
|
||
echo " 安装路径: ${NODE_BASE}"
|
||
echo " 数据路径: ${OC_DATA}"
|
||
echo ""
|
||
|
||
download_node "$node_ver"
|
||
install_pnpm
|
||
install_openclaw
|
||
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 "╚══════════════════════════════════════════════════════════════╝"
|
||
}
|
||
|
||
do_check() {
|
||
echo "=== OpenClaw 环境检查 ==="
|
||
echo ""
|
||
|
||
# Node.js
|
||
if [ -x "$NODE_BIN" ]; then
|
||
echo " Node.js: $($NODE_BIN --version 2>/dev/null)"
|
||
else
|
||
echo " Node.js: 未安装"
|
||
fi
|
||
|
||
# pnpm
|
||
if [ -x "$PNPM_BIN" ]; then
|
||
echo " pnpm: $($PNPM_BIN --version 2>/dev/null)"
|
||
elif [ -x "${NODE_BASE}/bin/pnpm" ]; then
|
||
echo " pnpm: $(${NODE_BASE}/bin/pnpm --version 2>/dev/null)"
|
||
else
|
||
echo " pnpm: 未安装"
|
||
fi
|
||
|
||
# OpenClaw
|
||
local oc_entry=""
|
||
oc_entry=$(find_oc_entry)
|
||
if [ -n "$oc_entry" ] && [ -x "$NODE_BIN" ]; then
|
||
echo " OpenClaw: v$($NODE_BIN $oc_entry --version 2>/dev/null | tr -d '[:space:]')"
|
||
else
|
||
echo " OpenClaw: 未安装"
|
||
fi
|
||
|
||
# 配置文件
|
||
if [ -f "$OC_DATA/.openclaw/openclaw.json" ]; then
|
||
echo " 配置: $OC_DATA/.openclaw/openclaw.json (存在)"
|
||
else
|
||
echo " 配置: 未初始化"
|
||
fi
|
||
|
||
# 磁盘使用
|
||
local used
|
||
used=$(du -sh /opt/openclaw 2>/dev/null | awk '{print $1}')
|
||
echo " 磁盘: ${used:-N/A}"
|
||
|
||
# libc 类型
|
||
echo " C库: $(detect_libc)"
|
||
}
|
||
|
||
do_upgrade() {
|
||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||
echo "║ 一万AI分享 OpenClaw 升级 ║"
|
||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||
echo ""
|
||
|
||
if [ ! -x "$NODE_BIN" ]; then
|
||
log_error "Node.js 未安装, 请先运行: openclaw-env setup"
|
||
exit 1
|
||
fi
|
||
|
||
# 获取当前版本
|
||
local current_ver=""
|
||
local oc_entry=""
|
||
oc_entry=$(find_oc_entry)
|
||
if [ -n "$oc_entry" ]; then
|
||
local oc_pkg_dir="$(dirname "$oc_entry")"
|
||
[ -f "$oc_pkg_dir/package.json" ] && \
|
||
current_ver=$("$NODE_BIN" -e "try{console.log(require('$oc_pkg_dir/package.json').version)}catch(e){}" 2>/dev/null) || true
|
||
fi
|
||
|
||
echo " Node.js: $($NODE_BIN --version 2>/dev/null)"
|
||
[ -n "$current_ver" ] && echo " 当前版本: v${current_ver}"
|
||
echo ""
|
||
|
||
local libc_type
|
||
libc_type=$(detect_libc)
|
||
local install_flags=""
|
||
[ "$libc_type" = "musl" ] && install_flags="--ignore-scripts"
|
||
|
||
echo "=== 正在升级 OpenClaw ==="
|
||
echo ""
|
||
"$NPM_BIN" install -g openclaw@latest --prefix="$OC_GLOBAL" $install_flags 2>&1
|
||
|
||
# 验证升级结果
|
||
local new_ver=""
|
||
local new_entry=""
|
||
new_entry=$(find_oc_entry)
|
||
if [ -n "$new_entry" ]; then
|
||
local new_pkg_dir="$(dirname "$new_entry")"
|
||
[ -f "$new_pkg_dir/package.json" ] && \
|
||
new_ver=$("$NODE_BIN" -e "try{console.log(require('$new_pkg_dir/package.json').version)}catch(e){}" 2>/dev/null) || true
|
||
fi
|
||
|
||
echo ""
|
||
if [ -n "$new_ver" ]; then
|
||
if [ "$current_ver" = "$new_ver" ]; then
|
||
log_info "当前已是最新版本 v${new_ver}"
|
||
else
|
||
log_info "升级成功: v${current_ver} → v${new_ver}"
|
||
fi
|
||
echo ""
|
||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||
echo "║ ✅ 升级完成! ║"
|
||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||
else
|
||
log_error "升级验证失败,OpenClaw 入口文件未找到"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# ── 主入口 ──
|
||
case "${1:-}" in
|
||
setup)
|
||
do_setup
|
||
;;
|
||
check)
|
||
do_check
|
||
;;
|
||
upgrade)
|
||
do_upgrade
|
||
;;
|
||
node)
|
||
download_node "$NODE_VERSION"
|
||
;;
|
||
*)
|
||
echo "用法: openclaw-env {setup|check|upgrade|node}"
|
||
echo ""
|
||
echo " setup — 完整安装 (下载 Node.js + pnpm + OpenClaw)"
|
||
echo " check — 检查环境状态"
|
||
echo " upgrade — 升级 OpenClaw 到最新版"
|
||
echo " node — 仅下载/更新 Node.js"
|
||
exit 1
|
||
;;
|
||
esac
|