mirror of
https://github.com/hotwa/luci-app-openclaw.git
synced 2026-03-30 20:25:44 +00:00
634 lines
22 KiB
Bash
Executable File
634 lines
22 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.2"
|
||
# 用户可通过 OC_VERSION 环境变量覆盖安装版本
|
||
OC_VERSION="${OC_VERSION:-}"
|
||
NODE_BASE="/opt/openclaw/node"
|
||
OC_GLOBAL="/opt/openclaw/global"
|
||
OC_DATA="/opt/openclaw/data"
|
||
|
||
# ── OverlayFS 兼容性修复 ──
|
||
# iStoreOS/OpenWrt 上 Docker 的 bind mount (/overlay/upper/opt/docker)
|
||
# 会导致 OverlayFS 合并视图中 /opt 完全不可写 (mkdir 报 "Directory not empty")。
|
||
# 解决方案: 将 /overlay/upper/opt bind mount 到 /opt,绕过 OverlayFS 冲突。
|
||
_oc_fix_opt() {
|
||
# 如果 /opt 可正常写入,无需修复
|
||
if mkdir -p /opt/openclaw/.probe 2>/dev/null; then
|
||
rmdir /opt/openclaw/.probe 2>/dev/null
|
||
return 0
|
||
fi
|
||
# /opt 不可写且 overlay upper 层存在 — 执行 bind mount 修复
|
||
if [ -d /overlay/upper/opt ]; then
|
||
# 确保 upper 层有 openclaw 目录
|
||
mkdir -p /overlay/upper/opt/openclaw 2>/dev/null
|
||
# 绑定挂载 upper 层的 /opt 到合并视图的 /opt
|
||
mount --bind /overlay/upper/opt /opt 2>/dev/null && return 0
|
||
fi
|
||
return 1
|
||
}
|
||
_oc_fix_opt
|
||
|
||
NODE_BIN="${NODE_BASE}/bin/node"
|
||
NPM_BIN="${NODE_BASE}/bin/npm"
|
||
PNPM_BIN="${OC_GLOBAL}/bin/pnpm"
|
||
|
||
# Node.js 官方镜像 + musl 非官方构建
|
||
# 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"
|
||
# 项目自托管 ARM64 musl Node.js (unofficial-builds 仅提供 x64 musl)
|
||
NODE_SELF_HOST="https://github.com/10000ge10000/luci-app-openclaw/releases/download/node-bins"
|
||
|
||
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH"
|
||
export NODE_ICU_DATA="${NODE_BASE}/share/icu"
|
||
|
||
log_info() { echo " [✓] $1"; }
|
||
log_warn() { echo " [!] $1"; }
|
||
log_error() { echo " [✗] $1"; }
|
||
|
||
# 安全创建目录 (会在 _oc_fix_opt 修复 /opt 后使用标准路径)
|
||
ensure_mkdir() {
|
||
local target="$1"
|
||
[ -d "$target" ] && return 0
|
||
if ! mkdir -p "$target" 2>/dev/null; then
|
||
log_error "无法创建目录: $target"
|
||
log_error "如果安装了 Docker,可能需要手动执行: mount --bind /overlay/upper/opt /opt"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 检测 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)
|
||
|
||
# 如果已存在且版本兼容, 跳过
|
||
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
|
||
# ARM64 musl 使用 Alpine 打包,版本号可能不完全匹配
|
||
# 只要主版本号相同即认为兼容 (如 22.15.1 vs 22.16.0)
|
||
local cur_major=$(echo "$current_ver" | cut -d. -f1)
|
||
local want_major=$(echo "$node_ver" | cut -d. -f1)
|
||
if [ "$cur_major" = "$want_major" ]; then
|
||
log_info "Node.js v${current_ver} 已安装 (兼容 v${node_ver}), 跳过下载"
|
||
return 0
|
||
fi
|
||
log_warn "当前 Node.js v${current_ver}, 将更新到 v${node_ver}"
|
||
fi
|
||
|
||
# ── 构建下载 URL 列表 (按优先级排列) ──
|
||
local mirror_list=""
|
||
local musl_tarball="node-v${node_ver}-${node_arch}-musl.tar.xz"
|
||
local glibc_tarball="node-v${node_ver}-${node_arch}.tar.xz"
|
||
|
||
if [ "$libc_type" = "musl" ]; then
|
||
echo ""
|
||
echo "=== 下载 Node.js v${node_ver} (${node_arch}, musl libc) ==="
|
||
|
||
if [ "$node_arch" = "linux-arm64" ]; then
|
||
# ARM64 musl: unofficial-builds 不提供,从项目自托管下载
|
||
# 1) 项目自托管 ARM64 musl 构建
|
||
mirror_list="${NODE_SELF_HOST}/${musl_tarball}"
|
||
# 2) unofficial-builds (留作将来可能支持)
|
||
mirror_list="$mirror_list ${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}"
|
||
else
|
||
# x64 musl: unofficial-builds 提供
|
||
# 1) unofficial-builds
|
||
mirror_list="${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}"
|
||
# 2) npmmirror 镜像
|
||
mirror_list="$mirror_list ${NODE_MIRROR_CN}/v${node_ver}/${musl_tarball}"
|
||
fi
|
||
else
|
||
echo ""
|
||
echo "=== 下载 Node.js v${node_ver} (${node_arch}, glibc) ==="
|
||
|
||
mirror_list="${NODE_MIRROR}/v${node_ver}/${glibc_tarball}"
|
||
mirror_list="$mirror_list ${NODE_MIRROR_CN}/v${node_ver}/${glibc_tarball}"
|
||
fi
|
||
|
||
# ── 逐个尝试下载 ──
|
||
local downloaded=0
|
||
local tmp_file="/tmp/node-v${node_ver}.tar.xz"
|
||
local attempt=0
|
||
local total=$(echo "$mirror_list" | wc -w)
|
||
|
||
for mirror_url in $mirror_list; do
|
||
attempt=$((attempt + 1))
|
||
echo " 正在从 ${mirror_url} 下载... (${attempt}/${total})"
|
||
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
|
||
# 校验文件大小 (Node.js xz 压缩包至少 5MB; Alpine 精简包约 12MB, 官方完整包约 30MB)
|
||
local fsize=$(wc -c < "$tmp_file" 2>/dev/null || echo 0)
|
||
if [ "$fsize" -gt 5000000 ] 2>/dev/null; then
|
||
downloaded=1
|
||
break
|
||
else
|
||
log_warn "文件大小异常 (${fsize} bytes), 跳过"
|
||
rm -f "$tmp_file"
|
||
fi
|
||
fi
|
||
log_warn "下载失败, 尝试备用镜像..."
|
||
done
|
||
|
||
if [ "$downloaded" -eq 0 ]; then
|
||
log_error "所有镜像均下载失败"
|
||
rm -f "$tmp_file"
|
||
exit 1
|
||
fi
|
||
|
||
# 解压
|
||
echo " 正在解压到 ${NODE_BASE}..."
|
||
# OverlayFS 兼容: rm -rf 后可能因 whiteout 导致 mkdir 失败
|
||
# 先尝试常规方式,失败则通过 overlay upper 层操作
|
||
rm -rf "$NODE_BASE" 2>/dev/null
|
||
if [ -d /overlay/upper ]; then
|
||
rm -rf "/overlay/upper${NODE_BASE}" 2>/dev/null
|
||
fi
|
||
ensure_mkdir "$NODE_BASE"
|
||
tar xf "$tmp_file" -C "$NODE_BASE" --strip-components=1
|
||
rm -f "$tmp_file"
|
||
|
||
# 验证
|
||
if [ -x "$NODE_BIN" ]; then
|
||
local installed_ver
|
||
installed_ver=$("$NODE_BIN" --version 2>/dev/null || echo "unknown")
|
||
log_info "Node.js ${installed_ver} 安装成功"
|
||
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 到全局目录
|
||
ensure_mkdir "$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
|
||
ensure_mkdir "$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
|
||
ensure_mkdir "$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
|
||
|
||
# 安装 Gemini CLI (官方模型配置向导的 Google Gemini OAuth 依赖)
|
||
if [ -x "$NPM_BIN" ]; then
|
||
echo ""
|
||
echo "=== 安装 Gemini CLI (Google OAuth 依赖) ==="
|
||
"$NPM_BIN" install -g @google/gemini-cli --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -3
|
||
if command -v gemini >/dev/null 2>&1 || [ -x "$OC_GLOBAL/bin/gemini" ]; then
|
||
log_info "Gemini CLI 安装成功"
|
||
else
|
||
log_warn "Gemini CLI 安装失败 (不影响核心功能,仅影响 Google Gemini OAuth 登录)"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
init_openclaw() {
|
||
echo ""
|
||
echo "=== 初始化 OpenClaw ==="
|
||
|
||
# 创建数据目录
|
||
ensure_mkdir "$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 --tools-profile coding 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
|
||
}
|
||
|
||
# ── 恢复出厂设置 (非交互式) ──
|
||
do_factory_reset() {
|
||
local config_dir="${OC_DATA}/.openclaw"
|
||
local config_file="${config_dir}/openclaw.json"
|
||
local auth_file="${config_dir}/agents/main/agent/auth-profiles.json"
|
||
|
||
log_info "恢复出厂设置..."
|
||
|
||
# 1. 停止 Gateway
|
||
log_info "停止 Gateway..."
|
||
/etc/init.d/openclaw stop >/dev/null 2>&1 || true
|
||
sleep 2
|
||
|
||
# 2. 备份当前配置
|
||
if [ -f "$config_file" ]; then
|
||
local backup_dir="${config_dir}/backups"
|
||
local backup_ts=$(date +%Y%m%d_%H%M%S)
|
||
ensure_mkdir "$backup_dir"
|
||
cp "$config_file" "${backup_dir}/openclaw_${backup_ts}.json"
|
||
log_info "备份已保存: backups/openclaw_${backup_ts}.json"
|
||
fi
|
||
|
||
# 3. 重置配置
|
||
rm -f "$config_file" "${config_file}.bak" 2>/dev/null || true
|
||
echo '{}' > "$config_file"
|
||
chown openclaw:openclaw "$config_file" 2>/dev/null || true
|
||
|
||
# 4. 重置认证信息
|
||
if [ -f "$auth_file" ]; then
|
||
echo '{"version":1,"profiles":{},"usageStats":{}}' > "$auth_file"
|
||
chown openclaw:openclaw "$auth_file" 2>/dev/null || true
|
||
fi
|
||
|
||
# 5. 重新初始化
|
||
if [ -x "$NODE_BIN" ]; then
|
||
local oc_entry
|
||
oc_entry=$(find "$OC_GLOBAL" -name "openclaw.mjs" -path "*/openclaw/openclaw.mjs" 2>/dev/null | head -1)
|
||
if [ -n "$oc_entry" ]; then
|
||
log_info "重新初始化..."
|
||
OPENCLAW_HOME="$OC_DATA" "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk --tools-profile coding >/dev/null 2>&1 || true
|
||
fi
|
||
fi
|
||
|
||
# 6. 应用 OpenWrt 适配配置
|
||
if [ -x "$NODE_BIN" ] && [ -f "$config_file" ]; then
|
||
local new_token
|
||
new_token=$(head -c 24 /dev/urandom | hexdump -e '24/1 "%02x"' 2>/dev/null || dd if=/dev/urandom bs=24 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n' | head -c 48)
|
||
_JS_KEY="gateway.port" _JS_VAL="18789" "$NODE_BIN" -e "const fs=require('fs');let d={};try{d=JSON.parse(fs.readFileSync('${config_file}','utf8'));}catch(e){}const ks=process.env._JS_KEY.split('.');let o=d;for(let i=0;i<ks.length-1;i++){if(!o[ks[i]]||typeof o[ks[i]]!=='object')o[ks[i]]={};o=o[ks[i]];}let v=process.env._JS_VAL;try{v=JSON.parse(v);}catch(e){}o[ks[ks.length-1]]=v;fs.writeFileSync('${config_file}',JSON.stringify(d,null,2));" 2>/dev/null
|
||
for kv in "gateway.bind=lan" "gateway.mode=local" "gateway.auth.mode=token" "gateway.auth.token=${new_token}" "gateway.controlUi.allowInsecureAuth=true" "gateway.controlUi.dangerouslyDisableDeviceAuth=true" "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true" "gateway.tailscale.mode=off" "acp.dispatch.enabled=false" "tools.profile=coding"; do
|
||
local k="${kv%%=*}" v="${kv#*=}"
|
||
_JS_KEY="$k" _JS_VAL="$v" "$NODE_BIN" -e "const fs=require('fs');let d={};try{d=JSON.parse(fs.readFileSync('${config_file}','utf8'));}catch(e){}const ks=process.env._JS_KEY.split('.');let o=d;for(let i=0;i<ks.length-1;i++){if(!o[ks[i]]||typeof o[ks[i]]!=='object')o[ks[i]]={};o=o[ks[i]];}let v=process.env._JS_VAL;try{v=JSON.parse(v);}catch(e){}o[ks[ks.length-1]]=v;fs.writeFileSync('${config_file}',JSON.stringify(d,null,2));" 2>/dev/null
|
||
done
|
||
chown openclaw:openclaw "$config_file" 2>/dev/null || true
|
||
|
||
# 同步 token 到 UCI
|
||
. /lib/functions.sh 2>/dev/null || true
|
||
uci set openclaw.main.token="$new_token" 2>/dev/null || true
|
||
uci commit openclaw 2>/dev/null || true
|
||
log_info "新认证令牌: $new_token"
|
||
fi
|
||
|
||
# 7. 重启服务
|
||
/etc/init.d/openclaw start >/dev/null 2>&1 &
|
||
log_info "出厂设置已恢复,Gateway 重启中..."
|
||
}
|
||
|
||
# ── 主入口 ──
|
||
case "${1:-}" in
|
||
setup)
|
||
do_setup
|
||
;;
|
||
check)
|
||
do_check
|
||
;;
|
||
upgrade)
|
||
do_upgrade
|
||
;;
|
||
node)
|
||
download_node "$NODE_VERSION"
|
||
;;
|
||
factory-reset)
|
||
do_factory_reset
|
||
;;
|
||
*)
|
||
echo "用法: openclaw-env {setup|check|upgrade|node|factory-reset}"
|
||
echo ""
|
||
echo " setup — 完整安装 (下载 Node.js + pnpm + OpenClaw)"
|
||
echo " check — 检查环境状态"
|
||
echo " upgrade — 升级 OpenClaw 到最新版"
|
||
echo " node — 仅下载/更新 Node.js"
|
||
echo " factory-reset — 恢复出厂设置 (清除所有配置)"
|
||
exit 1
|
||
;;
|
||
esac
|