v2: 全平台支持(Linux/macOS/WSL/Windows引导) + API 配置向导扩充为 7 个 provider + 连通性测试
This commit is contained in:
+176
-115
@@ -46,7 +46,11 @@ NODE_MAJOR_REQUIRED=20
|
||||
NODE_MAJOR_INSTALL=24
|
||||
OPENCLAW_HOME="" # 稍后按真实用户 HOME 推导
|
||||
OPENCLAW_CONFIG="" # 稍后探测
|
||||
PKG_MGR="" # apt | dnf
|
||||
PKG_MGR=""
|
||||
PLATFORM=""
|
||||
IS_WSL="0"
|
||||
CFG_PNAME=""
|
||||
CFG_DEFMODEL=""
|
||||
OS_ID=""
|
||||
OS_VERSION=""
|
||||
ARCH=""
|
||||
@@ -198,32 +202,66 @@ check_root() {
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
PLATFORM="unknown"; IS_WSL="0"; PKG_MGR=""
|
||||
|
||||
# 原生 Windows(Git Bash / MSYS / Cygwin)
|
||||
case "${OSTYPE:-}" in
|
||||
msys*|cygwin*|win32*) PLATFORM="windows" ;;
|
||||
esac
|
||||
if [ "$PLATFORM" = "windows" ]; then
|
||||
warn "检测到原生 Windows 环境。"
|
||||
plain ""
|
||||
plain "${C_BOLD}Windows 请用官方 PowerShell 安装器(在 PowerShell 中执行):${C_RESET}"
|
||||
plain " iwr -useb https://openclaw.ai/install.ps1 | iex"
|
||||
plain ""
|
||||
plain "${C_BOLD}或者装 WSL2(推荐,体验与 Linux 一致):${C_RESET}"
|
||||
plain " 1) 管理员 PowerShell 执行: wsl --install -d Debian"
|
||||
plain " 2) 重启后进入 Debian,再执行本脚本同一条命令"
|
||||
plain ""
|
||||
hint "装完 OpenClaw 后,可再回来运行本脚本,选择「仅配置」补齐中文配置向导。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$(uname -s 2>/dev/null || echo unknown)" in
|
||||
Darwin) PLATFORM="macos" ;;
|
||||
Linux) PLATFORM="linux" ;;
|
||||
esac
|
||||
|
||||
# WSL 识别
|
||||
if [ "$PLATFORM" = "linux" ]; then
|
||||
if [ -n "${WSL_DISTRO_NAME:-}" ] || grep -qi microsoft /proc/version 2>/dev/null; then
|
||||
IS_WSL="1"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" = "macos" ]; then
|
||||
OS_ID="macos"; OS_VERSION="$(sw_vers -productVersion 2>/dev/null || echo unknown)"
|
||||
command -v brew >/dev/null 2>&1 && PKG_MGR="brew" || PKG_MGR="none"
|
||||
ok "系统:macOS ${OS_VERSION}"
|
||||
[ "$PKG_MGR" = "none" ] && warn "未检测到 Homebrew,官方安装器会在需要时自行安装。"
|
||||
elif [ "$PLATFORM" = "linux" ]; then
|
||||
if [ -r /etc/os-release ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
OS_ID="${ID:-unknown}"
|
||||
OS_VERSION="${VERSION_ID:-unknown}"
|
||||
OS_ID="${ID:-unknown}"; OS_VERSION="${VERSION_ID:-unknown}"
|
||||
else
|
||||
OS_ID="unknown"; OS_VERSION="unknown"
|
||||
fi
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
PKG_MGR="apt"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
PKG_MGR="dnf"
|
||||
if command -v apt-get >/dev/null 2>&1; then PKG_MGR="apt"
|
||||
elif command -v dnf >/dev/null 2>&1; then PKG_MGR="dnf"
|
||||
elif command -v yum >/dev/null 2>&1; then PKG_MGR="yum"
|
||||
elif command -v apk >/dev/null 2>&1; then PKG_MGR="apk"
|
||||
else PKG_MGR="none"; fi
|
||||
local wsl_tag=""; [ "$IS_WSL" = "1" ] && wsl_tag="(WSL)"
|
||||
case "$OS_ID" in
|
||||
debian|ubuntu) ok "系统:${PRETTY_NAME:-$OS_ID $OS_VERSION}${wsl_tag}(受支持)" ;;
|
||||
*) warn "系统:${PRETTY_NAME:-$OS_ID $OS_VERSION}${wsl_tag}(由官方安装器尽力兼容)" ;;
|
||||
esac
|
||||
else
|
||||
die "未找到受支持的包管理器(需要 apt-get 或 dnf)。" \
|
||||
"本脚本主要支持 Debian 12/13 与 Ubuntu 22.04/24.04。"
|
||||
die "无法识别当前操作系统。" "本脚本支持 macOS、Linux(含 WSL);Windows 请用官方 install.ps1。"
|
||||
fi
|
||||
|
||||
case "$OS_ID" in
|
||||
debian|ubuntu) ok "系统:${PRETTY_NAME:-$OS_ID $OS_VERSION}(受支持)" ;;
|
||||
rhel|centos|rocky|almalinux|fedora)
|
||||
warn "系统:${PRETTY_NAME:-$OS_ID $OS_VERSION}(尽力兼容,未完整测试)" ;;
|
||||
*)
|
||||
warn "系统:${PRETTY_NAME:-$OS_ID $OS_VERSION}(未知发行版,可能失败)" ;;
|
||||
esac
|
||||
_log "OS_ID=$OS_ID OS_VERSION=$OS_VERSION PKG_MGR=$PKG_MGR"
|
||||
_log "PLATFORM=$PLATFORM IS_WSL=$IS_WSL OS_ID=${OS_ID:-} PKG_MGR=$PKG_MGR"
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
@@ -317,78 +355,49 @@ node_major() {
|
||||
}
|
||||
|
||||
install_node() {
|
||||
title "检查 Node.js"
|
||||
local major=""
|
||||
title "安装 Node.js 与 OpenClaw(官方安装器)"
|
||||
info "调用官方安装器 https://openclaw.ai/install.sh"
|
||||
info "它会自动处理 Node.js(macOS 用 Homebrew,Linux 用 NodeSource/apk)"
|
||||
|
||||
local tmp_inst
|
||||
tmp_inst="$(mktemp /tmp/openclaw_official.XXXXXX.sh)" || \
|
||||
die "无法创建临时文件。" "请检查 /tmp 是否可写、磁盘是否已满。"
|
||||
|
||||
if ! curl -fsSL --proto '=https' --tlsv1.2 -m 120 \
|
||||
https://openclaw.ai/install.sh -o "$tmp_inst"; then
|
||||
rm -f "$tmp_inst"
|
||||
die "下载官方安装器失败。" \
|
||||
"排查:1) 检查网络能否访问 openclaw.ai;2) 国内网络可能需要代理;3) 稍后重试。"
|
||||
fi
|
||||
|
||||
if ! head -1 "$tmp_inst" | grep -q '^#!'; then
|
||||
rm -f "$tmp_inst"
|
||||
die "官方安装器内容异常(不是脚本)。" "可能被网络劫持,请检查网络环境后重试。"
|
||||
fi
|
||||
|
||||
_log "RUN: bash official install.sh --yes"
|
||||
if bash "$tmp_inst" --yes 2>&1 | tee -a "$LOG_FILE"; then
|
||||
ok "官方安装器执行完成"
|
||||
else
|
||||
warn "官方安装器返回非零状态,继续检查实际安装结果。"
|
||||
fi
|
||||
rm -f "$tmp_inst"
|
||||
|
||||
# 官方装完可能只在当前 shell 之外的 PATH 里,补几个常见位置
|
||||
for d in /usr/local/bin /opt/homebrew/bin "$HOME/.local/bin" "$HOME/.openclaw/bin"; do
|
||||
[ -d "$d" ] && case ":$PATH:" in *":$d:"*) : ;; *) PATH="$d:$PATH" ;; esac
|
||||
done
|
||||
export PATH
|
||||
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
major="$(node_major)"
|
||||
fi
|
||||
|
||||
if [ -n "$major" ] && [ "$major" -ge "$NODE_MAJOR_REQUIRED" ] 2>/dev/null; then
|
||||
ok "已安装 Node.js $(node -v),满足要求(>= ${NODE_MAJOR_REQUIRED}),跳过。"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$major" ]; then
|
||||
warn "当前 Node.js $(node -v) 版本过低(需要 >= ${NODE_MAJOR_REQUIRED}),将安装 Node.js ${NODE_MAJOR_INSTALL}。"
|
||||
ok "Node.js $(node -v) 就绪"
|
||||
else
|
||||
info "未检测到 Node.js,开始安装 Node.js ${NODE_MAJOR_INSTALL}。"
|
||||
die "Node.js 安装后仍找不到 node 命令。" \
|
||||
"请打开一个新终端后重试,或手动安装 Node 22.22.3+ / 24.15+ / 26。"
|
||||
fi
|
||||
|
||||
local setup_url="https://deb.nodesource.com/setup_${NODE_MAJOR_INSTALL}.x"
|
||||
[ "$PKG_MGR" = "dnf" ] && setup_url="https://rpm.nodesource.com/setup_${NODE_MAJOR_INSTALL}.x"
|
||||
|
||||
local tmp_script
|
||||
tmp_script="$(mktemp /tmp/nodesource_setup.XXXXXX.sh)" || \
|
||||
die "无法创建临时文件。" "请检查 /tmp 是否可写、磁盘是否已满(df -h /tmp)。"
|
||||
|
||||
info "下载 NodeSource 安装源脚本…"
|
||||
if ! run_quiet curl -fsSL --retry 3 --connect-timeout 20 -o "$tmp_script" "$setup_url"; then
|
||||
rm -f "$tmp_script"
|
||||
die "下载 NodeSource 脚本失败:${setup_url}" \
|
||||
"常见原因:服务器无法访问外网或 DNS 异常。可先执行 ping -c2 deb.nodesource.com 排查,或手动安装 Node.js ${NODE_MAJOR_REQUIRED}+ 后重跑本脚本。"
|
||||
fi
|
||||
|
||||
info "配置 NodeSource 软件源…"
|
||||
if ! run_quiet bash "$tmp_script"; then
|
||||
rm -f "$tmp_script"
|
||||
die "NodeSource 软件源配置失败。" \
|
||||
"该发行版可能暂不被 NodeSource 支持。可改用发行版自带的 nodejs 包(需 >= ${NODE_MAJOR_REQUIRED}),或用 nvm 安装后重跑本脚本。"
|
||||
fi
|
||||
rm -f "$tmp_script"
|
||||
|
||||
info "安装 Node.js ${NODE_MAJOR_INSTALL}…"
|
||||
if [ "$PKG_MGR" = "apt" ]; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
run_quiet apt-get install -y nodejs || \
|
||||
die "Node.js 安装失败。" "请查看日志末尾的 apt 报错:tail -n 40 ${LOG_FILE}"
|
||||
else
|
||||
run_quiet dnf install -y nodejs || \
|
||||
die "Node.js 安装失败。" "请查看日志末尾的 dnf 报错:tail -n 40 ${LOG_FILE}"
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
die "Node.js 安装后仍找不到 node 命令。" "请重新登录 shell 或检查 PATH 后重试。"
|
||||
fi
|
||||
major="$(node_major)"
|
||||
if [ -z "$major" ] || [ "$major" -lt "$NODE_MAJOR_REQUIRED" ] 2>/dev/null; then
|
||||
die "Node.js 版本仍不满足要求(当前 $(node -v))。" "请手动安装 Node.js ${NODE_MAJOR_REQUIRED}+ 后重跑本脚本。"
|
||||
fi
|
||||
ok "Node.js 安装完成:$(node -v),npm $(npm -v 2>/dev/null || echo '未知')"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. 安装 / 更新 OpenClaw
|
||||
# ---------------------------------------------------------------------------
|
||||
openclaw_local_version() {
|
||||
npm list -g openclaw --depth=0 --no-update-notifier 2>/dev/null \
|
||||
| grep openclaw | awk '{print $NF}' | sed 's/^.*@//'
|
||||
}
|
||||
|
||||
openclaw_remote_version() {
|
||||
npm view openclaw version --no-update-notifier 2>/dev/null | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
npm_install_openclaw() {
|
||||
install_openclaw() {
|
||||
info "正在执行 npm install -g openclaw@latest(首次安装可能需要几分钟)…"
|
||||
if run_quiet npm install -g openclaw@latest --no-update-notifier; then
|
||||
return 0
|
||||
@@ -402,7 +411,7 @@ npm_install_openclaw() {
|
||||
}
|
||||
|
||||
install_openclaw() {
|
||||
title "安装 OpenClaw"
|
||||
title "校验 OpenClaw"
|
||||
local local_ver remote_ver
|
||||
local_ver="$(openclaw_local_version)"
|
||||
|
||||
@@ -525,49 +534,101 @@ wizard() {
|
||||
|
||||
# --- Provider ---
|
||||
plain ""
|
||||
plain "${C_BOLD}[3/3] AI 模型服务${C_RESET}"
|
||||
plain " 1) OpenAI 兼容接口(自定义 baseURL + Key,适用于中转站/自建/DeepSeek 等)"
|
||||
plain "${C_BOLD}[3/3] AI 模型服务(API 配置)${C_RESET}"
|
||||
plain " 1) OpenAI 官方"
|
||||
plain " 2) Anthropic 官方(Claude)"
|
||||
plain " 3) 稍后手动配置"
|
||||
plain " 3) DeepSeek"
|
||||
plain " 4) 硅基流动 SiliconFlow"
|
||||
plain " 5) OpenRouter"
|
||||
plain " 6) 自定义 OpenAI 兼容接口(中转站/自建,手填 baseURL)"
|
||||
plain " 7) 稍后手动配置"
|
||||
local choice
|
||||
while true; do
|
||||
ask " 请输入编号" "3"
|
||||
ask " 请输入编号" "7"
|
||||
choice="$REPLY_VALUE"
|
||||
case "$choice" in
|
||||
1) CFG_PROVIDER="openai-compat"; break ;;
|
||||
2) CFG_PROVIDER="anthropic"; break ;;
|
||||
3) CFG_PROVIDER="skip"; break ;;
|
||||
*) warn " 请输入 1、2 或 3。" ;;
|
||||
1) CFG_PROVIDER="openai-compat"; CFG_BASEURL="https://api.openai.com/v1"
|
||||
CFG_DEFMODEL="gpt-4o-mini"; CFG_PNAME="OpenAI 官方"; break ;;
|
||||
2) CFG_PROVIDER="anthropic"; CFG_BASEURL=""
|
||||
CFG_DEFMODEL="claude-sonnet-4-5"; CFG_PNAME="Anthropic 官方"; break ;;
|
||||
3) CFG_PROVIDER="openai-compat"; CFG_BASEURL="https://api.deepseek.com/v1"
|
||||
CFG_DEFMODEL="deepseek-chat"; CFG_PNAME="DeepSeek"; break ;;
|
||||
4) CFG_PROVIDER="openai-compat"; CFG_BASEURL="https://api.siliconflow.cn/v1"
|
||||
CFG_DEFMODEL="deepseek-ai/DeepSeek-V3"; CFG_PNAME="硅基流动"; break ;;
|
||||
5) CFG_PROVIDER="openai-compat"; CFG_BASEURL="https://openrouter.ai/api/v1"
|
||||
CFG_DEFMODEL="anthropic/claude-sonnet-4.5"; CFG_PNAME="OpenRouter"; break ;;
|
||||
6) CFG_PROVIDER="openai-compat"; CFG_BASEURL=""
|
||||
CFG_DEFMODEL="gpt-4o-mini"; CFG_PNAME="自定义"; break ;;
|
||||
7) CFG_PROVIDER="skip"; break ;;
|
||||
*) warn " 请输入 1-7 之间的编号。" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$CFG_PROVIDER" in
|
||||
openai-compat)
|
||||
ask " 接口地址 baseURL" "https://api.openai.com/v1"
|
||||
if [ "$CFG_PROVIDER" = "skip" ]; then
|
||||
plain " 已跳过模型配置。稍后可重跑本脚本选择「仅配置」补上。"
|
||||
return 0
|
||||
fi
|
||||
|
||||
plain ""
|
||||
ok " 已选择:${CFG_PNAME}"
|
||||
|
||||
# baseURL:官方预设直接用,自定义才问
|
||||
if [ "$CFG_PROVIDER" = "openai-compat" ]; then
|
||||
if [ -z "$CFG_BASEURL" ]; then
|
||||
while true; do
|
||||
ask " 接口地址 baseURL(例:https://xxx.com/v1)" ""
|
||||
CFG_BASEURL="$REPLY_VALUE"
|
||||
case "$CFG_BASEURL" in
|
||||
http://*|https://*) : ;;
|
||||
*) warn " baseURL 通常以 http:// 或 https:// 开头,请稍后确认。" ;;
|
||||
http://*|https://*) break ;;
|
||||
"") warn " baseURL 不能为空,中转站地址一般以 /v1 结尾。" ;;
|
||||
*) warn " 地址需以 http:// 或 https:// 开头。" ;;
|
||||
esac
|
||||
ask_secret " API Key"
|
||||
done
|
||||
else
|
||||
plain " 接口地址:${CFG_BASEURL}"
|
||||
if ask_yes_no " 需要改成别的地址吗?" "n"; then
|
||||
ask " 新的 baseURL" "$CFG_BASEURL"
|
||||
CFG_BASEURL="$REPLY_VALUE"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# API Key
|
||||
while true; do
|
||||
ask_secret " API Key(输入不显示,直接回车可跳过)"
|
||||
CFG_APIKEY="$REPLY_VALUE"
|
||||
[ -z "$CFG_APIKEY" ] && warn " 未填写 API Key,模型将无法调用,需稍后补上。"
|
||||
ask " 模型名称" "gpt-4o-mini"
|
||||
if [ -z "$CFG_APIKEY" ]; then
|
||||
warn " 未填写 API Key,模型将无法调用。"
|
||||
ask_yes_no " 确定跳过吗?" "n" && break
|
||||
else
|
||||
ok " API Key 已记录(长度 ${#CFG_APIKEY},内容不显示)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# 模型名
|
||||
ask " 模型名称" "$CFG_DEFMODEL"
|
||||
CFG_MODEL="$REPLY_VALUE"
|
||||
ok " 已记录:baseURL=${CFG_BASEURL},模型=${CFG_MODEL}(Key 不显示)"
|
||||
;;
|
||||
anthropic)
|
||||
ask_secret " Anthropic API Key(形如 sk-ant-...)"
|
||||
CFG_APIKEY="$REPLY_VALUE"
|
||||
[ -z "$CFG_APIKEY" ] && warn " 未填写 API Key,模型将无法调用,需稍后补上。"
|
||||
ask " 模型名称" "claude-sonnet-4-5"
|
||||
CFG_MODEL="$REPLY_VALUE"
|
||||
ok " 已记录:Anthropic,模型=${CFG_MODEL}(Key 不显示)"
|
||||
;;
|
||||
skip)
|
||||
plain " 已跳过模型配置。"
|
||||
;;
|
||||
[ -z "$CFG_MODEL" ] && CFG_MODEL="$CFG_DEFMODEL"
|
||||
|
||||
# 连通性测试(仅 openai-compat 且有 key 时)
|
||||
if [ -n "$CFG_APIKEY" ] && [ "$CFG_PROVIDER" = "openai-compat" ] && [ -n "$CFG_BASEURL" ]; then
|
||||
if ask_yes_no " 现在测试一下这个 API 通不通?" "y"; then
|
||||
info " 正在测试 ${CFG_BASEURL}/models ..."
|
||||
local code
|
||||
code="$(curl -s -o /dev/null -m 15 -w '%{http_code}' \
|
||||
-H "Authorization: Bearer ${CFG_APIKEY}" \
|
||||
"${CFG_BASEURL%/}/models" 2>/dev/null || echo 000)"
|
||||
case "$code" in
|
||||
200) ok " API 测试通过(HTTP 200),Key 有效。" ;;
|
||||
401|403) warn " API 返回 ${code}:Key 可能无效或没权限,配置仍会写入,稍后可改。" ;;
|
||||
000) warn " 连不上该地址(超时/DNS 失败),请确认地址与网络,配置仍会写入。" ;;
|
||||
*) warn " API 返回 HTTP ${code},不确定是否可用,配置仍会写入。" ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
ok " 模型配置已记录:${CFG_PNAME} / ${CFG_MODEL}"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user