#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════════════════════ # NeoWeb AI 一键安装脚本 # 一键安装: curl -fsSL https://ai.neowebai.com/install.sh | bash # 本地安装: bash install.sh # 更新版本: bash install.sh update # 查看状态: bash install.sh status # 卸载服务: bash install.sh uninstall # ═══════════════════════════════════════════════════════════════════════════ set -e BOLD="\033[1m" CYAN="\033[36m" GREEN="\033[32m" YELLOW="\033[33m" RED="\033[31m" DIM="\033[2m" RESET="\033[0m" NEOWEB_DIR="${NEOWEB_DIR:-$HOME/NeoWeb-AI}" PORT="${PORT:-3001}" CMD="${1:-install}" # ── 修复 PATH(macOS Homebrew / nvm 常见问题)──────────────────────────── # curl | bash 管道模式下 .zshrc/.bashrc 不会被 source,PATH 可能缺失 for p in /opt/homebrew/bin /usr/local/bin "$HOME/.nvm/versions/node"/*/bin; do [ -d "$p" ] && case ":$PATH:" in *":$p:"*) ;; *) export PATH="$p:$PATH" ;; esac done # Homebrew shellenv(设置 HOMEBREW_PREFIX 等) eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null)" 2>/dev/null || true eval "$(/usr/local/bin/brew shellenv 2>/dev/null)" 2>/dev/null || true # ── 法律声明 ────────────────────────────────────────────────────────────── show_legal() { echo "" echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${RESET}" echo -e "${CYAN}${BOLD} 🌐 NeoWeb AI — Web 4.0 智能系统 www.neowebai.com${RESET}" echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${RESET}" echo -e " ${DIM}Open Core · BSL 1.1 · 核心引擎商业授权,工具/UI 开源${RESET}" echo -e " ${DIM}官网: https://www.neowebai.com | 技能中心: https://skills.neowebai.com${RESET}" echo -e " ${DIM}开源政策: https://www.neowebai.com/open-source${RESET}" echo "" echo -e "${DIM}开发方:广西六壬数字科技 www.liurenai.com${RESET}" echo "" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "${BOLD}📄 使用与数据声明${RESET}" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "本软件为本地运行系统,所有数据默认存储于用户本地设备。" echo -e "除用户主动配置或授权外,本程序不会向任何第三方服务器上传数据。" echo "" echo -e "用户在使用本软件前,应充分了解其功能及潜在风险。" echo -e "一旦安装或运行本软件,即视为您已阅读并同意本声明全部内容。" echo "" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "${YELLOW}${BOLD}⚠️ 权限与风险提示(重要)${RESET}" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "本软件为高权限智能系统,运行过程中可能具备以下能力:" echo "" echo -e " • ${BOLD}Shell 命令执行${RESET} — 可调用系统终端执行命令" echo -e " • ${BOLD}文件系统访问${RESET} — 可读取、修改、删除本地文件" echo -e " • ${BOLD}脚本执行能力${RESET} — 可运行 Python 等脚本程序" echo -e " • ${BOLD}网络访问权限${RESET} — 可发起 HTTP/HTTPS 请求" echo -e " • ${BOLD}远程连接能力${RESET} — 可通过 SSH 连接远程服务器(需用户配置)" echo "" echo -e "${YELLOW}⚠️ 请注意:${RESET}" echo -e "上述功能具有较高系统权限,可能对系统数据、安全及稳定性产生影响。" echo -e "请仅在受控环境下使用,并确保您具备相应技术能力。" echo "" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "${BOLD}⚖️ 责任限制声明${RESET}" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "1. 本软件仅作为技术工具提供,不对用户的具体使用行为负责。" echo -e "2. 用户需对自身操作及由此产生的全部后果承担完全责任。" echo -e "3. 因用户使用本软件所造成的任何后果,开发方不承担任何责任。" echo "" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "${BOLD}🔐 安全建议${RESET}" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e " • 建议在虚拟机或隔离环境中运行" echo -e " • 避免授予不必要的系统权限" echo -e " • 定期备份重要数据" echo -e " • 审慎执行 AI 自动生成的命令" echo "" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "${BOLD}🌐 共享技能库说明${RESET}" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -e "NeoWeb AI 内置共享技能库功能,所有用户的 AI 共同进化:" echo -e " • AI 进化产出的有用工具会自动共享到技能中心" echo -e " • 其他用户缺少某种能力时会自动下载安装" echo -e " • 技能库网站: ${BOLD}https://skills.neowebai.com${RESET}" echo "" echo -e "${BOLD}🛡️ 隐私保护:${RESET}" echo -e " • 只上传工具代码,不上传用户对话、文件内容、个人信息" echo -e " • 上传前自动脱敏(移除路径/API Key/IP/用户名/邮箱)" echo -e " • 四层安全审核:本地脱敏 → 静态分析 → 服务端验证 → 社区审核" echo -e " • 完全匿名贡献,不追踪用户身份" echo "" echo -e "${CYAN}────────────────────────────────────${RESET}" echo -ne "${BOLD}同意请输入 y 继续安装: ${RESET}" if [ -t 0 ]; then # 终端模式(bash install.sh) read confirm elif read confirm < /dev/tty 2>/dev/null; then # 管道模式但 tty 可用 true else # curl | bash 管道模式,tty 不可用,自动同意 echo -e "y ${DIM}(管道模式自动同意)${RESET}" confirm="y" fi if [[ ! "$confirm" =~ ^[Yy]$ ]]; then echo -e "${RED}安装已取消${RESET}" exit 0 fi echo "" } # ── 自动打开浏览器 ──────────────────────────────────────────────────────── open_browser() { local url="http://localhost:${PORT}" echo -e " ${CYAN}正在打开浏览器...${RESET}" if [ "$(uname -s)" = "Darwin" ]; then open "$url" 2>/dev/null || true elif command -v xdg-open &> /dev/null; then xdg-open "$url" 2>/dev/null || true elif command -v sensible-browser &> /dev/null; then sensible-browser "$url" 2>/dev/null || true fi } # ── 等待服务启动 ────────────────────────────────────────────────────────── wait_for_service() { local max_wait=15 local waited=0 echo -ne " 等待服务启动" while [ $waited -lt $max_wait ]; do if curl -sf http://localhost:${PORT}/api/health > /dev/null 2>&1; then echo -e " ${GREEN}✅${RESET}" return 0 fi echo -ne "." sleep 1 waited=$((waited + 1)) done echo -e " ${YELLOW}超时${RESET}" return 1 } # ── 安装主流程 ──────────────────────────────────────────────────────────── do_install() { show_legal # Step 1: 检测系统 echo -e "${BOLD}[1/6] 检测系统环境...${RESET}" OS="$(uname -s)" ARCH="$(uname -m)" echo -e " 系统: ${OS} ${ARCH}" # Step 2: 检查 Node.js echo -e "${BOLD}[2/6] 检查 Node.js...${RESET}" if ! command -v node &> /dev/null; then echo -e "${YELLOW}未找到 Node.js,尝试自动安装...${RESET}" if [ "$OS" = "Darwin" ]; then if command -v brew &> /dev/null; then brew install node else echo -e "${YELLOW}安装 Homebrew...${RESET}" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" < /dev/tty # Homebrew 可能需要添加到 PATH eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null || /usr/local/bin/brew shellenv 2>/dev/null)" 2>/dev/null || true brew install node fi elif [ "$OS" = "Linux" ]; then if command -v apt-get &> /dev/null; then curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs elif command -v yum &> /dev/null; then curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash - sudo yum install -y nodejs elif command -v dnf &> /dev/null; then curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash - sudo dnf install -y nodejs else echo -e "${RED}❌ 无法自动安装 Node.js,请手动安装: https://nodejs.org${RESET}" exit 1 fi fi fi if ! command -v node &> /dev/null; then echo -e "${RED}❌ Node.js 安装失败,请手动安装: https://nodejs.org${RESET}" exit 1 fi NODE_VER=$(node --version | sed 's/v//') NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1) if [ "$NODE_MAJOR" -lt 18 ]; then echo -e "${RED}❌ Node.js 版本太低 (${NODE_VER}),需要 18+${RESET}" echo -e " 升级: brew install node 或 nvm install 22" exit 1 fi echo -e "${GREEN}✅ Node.js ${NODE_VER}${RESET}" # Step 3: 检查 Python3(可选) echo -e "${BOLD}[3/6] 检查 Python3...${RESET}" if command -v python3 &> /dev/null; then PY_VER=$(python3 --version 2>&1 | awk '{print $2}') echo -e "${GREEN}✅ Python ${PY_VER}${RESET}" else echo -e "${YELLOW}⚠️ Python3 未安装(AI Python 工具将不可用,其他功能正常)${RESET}" fi # Step 4: 下载/复制文件 echo -e "${BOLD}[4/6] 安装文件...${RESET}" # 获取下载地址(从版本 API 动态获取) DOWNLOAD_URL="" if command -v python3 &> /dev/null; then DOWNLOAD_URL=$(curl -sf https://ai.neowebai.com/api/version 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);print(d.get('download',d.get('url','')))" 2>/dev/null) || true fi if [ -z "$DOWNLOAD_URL" ]; then # python3 不可用时用 grep 解析 DOWNLOAD_URL=$(curl -sf https://ai.neowebai.com/api/version 2>/dev/null | grep -oE '"(download|url)":"[^"]*"' | head -1 | cut -d'"' -f4) || true fi if [ -z "$DOWNLOAD_URL" ]; then echo -e "${RED}❌ 无法获取下载地址,请检查网络连接${RESET}" echo -e " 手动下载: https://ai.neowebai.com" exit 1 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null || echo ".")" 2>/dev/null && pwd)" # 判断是 curl | bash 管道模式还是本地执行 if [ -z "${BASH_SOURCE[0]}" ] || [ "${BASH_SOURCE[0]}" = "bash" ] || [ ! -f "${BASH_SOURCE[0]}" ]; then # curl | bash 模式 → 从服务器下载 echo -e " 从 ${CYAN}${DOWNLOAD_URL}${RESET} 下载..." mkdir -p "$NEOWEB_DIR" TMP_FILE=$(mktemp /tmp/neoweb-ai-XXXXXX) curl -fSL --progress-bar "$DOWNLOAD_URL" -o "$TMP_FILE" # 完整性校验 — 防止下载包被篡改 EXPECTED_SHA256=$(curl -sf https://ai.neowebai.com/api/version 2>/dev/null | python3 -c "import json,sys;print(json.load(sys.stdin).get('sha256',''))" 2>/dev/null) || true if [ -n "$EXPECTED_SHA256" ]; then ACTUAL_SHA256=$(shasum -a 256 "$TMP_FILE" 2>/dev/null | cut -d' ' -f1 || sha256sum "$TMP_FILE" 2>/dev/null | cut -d' ' -f1) if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then echo -e "${RED}❌ 安全警告:文件校验失败!下载包可能被篡改,已中止安装。${RESET}" echo -e " 期望: $EXPECTED_SHA256" echo -e " 实际: $ACTUAL_SHA256" rm -f "$TMP_FILE" exit 1 fi echo -e " ${GREEN}✅ SHA256 校验通过${RESET}" fi tar xzf "$TMP_FILE" -C "$NEOWEB_DIR" rm -f "$TMP_FILE" # 修复权限(macOS tar 打包可能是 501:staff) chmod -R u+rwX "$NEOWEB_DIR" echo -e "${GREEN}✅ 文件下载并解压到 ${NEOWEB_DIR}${RESET}" elif [ "$SCRIPT_DIR" != "$NEOWEB_DIR" ] && [ "$SCRIPT_DIR" != "$NEOWEB_DIR/scripts" ]; then # 本地 install.sh(比如解压 tar.gz 后在目录里直接运行) mkdir -p "$NEOWEB_DIR" rsync -a --exclude=node_modules --exclude='*.db' --exclude='*.db-*' --exclude='.env' --exclude='logs/' --exclude='backups/' "$SCRIPT_DIR/" "$NEOWEB_DIR/" chmod -R u+rwX "$NEOWEB_DIR" echo -e "${GREEN}✅ 文件安装到 ${NEOWEB_DIR}${RESET}" else echo -e "${GREEN}✅ 已在目标目录${RESET}" fi cd "$NEOWEB_DIR" # Step 5: 安装依赖(强制重新编译,确保 native 模块匹配当前 Node 版本) echo -e "${BOLD}[5/6] 安装依赖...${RESET}" rm -rf node_modules if ! npm install --omit=dev 2>&1 | tail -5; then echo -e "${RED}❌ 依赖安装失败,尝试清理后重试...${RESET}" rm -rf node_modules package-lock.json npm install --omit=dev 2>&1 | tail -5 || { echo -e "${RED}❌ 依赖安装失败,请检查网络连接后重试: cd $NEOWEB_DIR && npm install${RESET}" exit 1 } fi echo -e "${GREEN}✅ 依赖安装完成${RESET}" # Step 6: 初始化配置 + 启动 echo -e "${BOLD}[6/6] 初始化并启动...${RESET}" # 创建 .env if [ -f "$NEOWEB_DIR/.env.example" ] && [ ! -f "$NEOWEB_DIR/.env" ]; then cp "$NEOWEB_DIR/.env.example" "$NEOWEB_DIR/.env" echo -e " ${GREEN}✅ 配置文件已创建${RESET}" fi # 创建必要目录 mkdir -p logs tmp tools/custom skills backups # ── 启动服务(自动选择最佳方式)────────────────────────────────────── echo "" # 优先用 PM2(进程守护 + 崩溃自动重启 + 开机自启) USE_PM2=true if ! command -v pm2 &> /dev/null; then echo -e " ${YELLOW}安装 PM2 进程守护(崩溃自动重启 + 开机自启)...${RESET}" npm install -g pm2 2>&1 | tail -1 || { # 某些系统需要 sudo sudo npm install -g pm2 2>&1 | tail -1 || { echo -e " ${YELLOW}PM2 安装失败,将使用后台守护模式${RESET}" USE_PM2=false } } fi if [ "$USE_PM2" = true ] && command -v pm2 &> /dev/null; then cd "$NEOWEB_DIR" # 清理旧版本安装目录(如果存在) [ -d "$HOME/NeoWebAI" ] && rm -rf "$HOME/NeoWebAI" 2>/dev/null && echo -e " ${DIM}清理旧版本目录 ~/NeoWebAI/${RESET}" # 迁移旧版本数据库(保留用户数据) if [ -f "$NEOWEB_DIR/neowebai.db" ] && [ ! -s "$NEOWEB_DIR/neoweb-ai.db" ]; then mv "$NEOWEB_DIR/neowebai.db" "$NEOWEB_DIR/neoweb-ai.db" 2>/dev/null mv "$NEOWEB_DIR/neowebai.db-shm" "$NEOWEB_DIR/neoweb-ai.db-shm" 2>/dev/null || true mv "$NEOWEB_DIR/neowebai.db-wal" "$NEOWEB_DIR/neoweb-ai.db-wal" 2>/dev/null || true echo -e " ${DIM}已迁移旧版本数据库(模型配置和聊天记录已保留)${RESET}" fi # 清理旧版 .env 中的 DB_PATH(指向旧文件名) [ -f "$NEOWEB_DIR/.env" ] && sed -i.bak "/^DB_PATH/d" "$NEOWEB_DIR/.env" 2>/dev/null && rm -f "$NEOWEB_DIR/.env.bak" # 清理所有旧版本进程(防止端口冲突) pm2 delete neoweb-ai 2>/dev/null || true pm2 delete neowebai 2>/dev/null || true pm2 delete neowebai-auto-ops 2>/dev/null || true # 杀掉占用 3001 端口的进程 lsof -ti:${PORT} 2>/dev/null | xargs kill -9 2>/dev/null || true sleep 1 # 启动(优先用 ecosystem 配置,包含完整守护策略) if [ -f ecosystem.config.cjs ]; then pm2 start ecosystem.config.cjs --env production 2>/dev/null else pm2 start server.js --name neoweb-ai \ --max-memory-restart 512M \ --restart-delay 2000 \ --exp-backoff-restart-delay=1500 \ 2>/dev/null fi # 保存进程列表(重启后自动恢复) pm2 save 2>/dev/null || true # Fix 9: 设置开机自启(系统重启后自动拉起) pm2 save 2>/dev/null || true if [ "$OS" = "Darwin" ]; then # macOS: PM2 生成 launchd plist # eval 可能需要 sudo,捕获错误不崩溃 STARTUP_CMD=$(pm2 startup launchd 2>/dev/null | grep 'sudo' | head -1) if [ -n "$STARTUP_CMD" ]; then echo -e " 设置开机自启..." eval $STARTUP_CMD 2>/dev/null && echo -e " ${GREEN}✅ 开机自启设置成功${RESET}" || echo -e " ${YELLOW}⚠️ 开机自启需要 sudo 权限,请手动执行: ${STARTUP_CMD}${RESET}" fi elif [ "$OS" = "Linux" ]; then if [ "$(id -u)" = "0" ]; then pm2 startup systemd -u root --hp /root 2>/dev/null || true else STARTUP_CMD=$(pm2 startup systemd 2>/dev/null | grep 'sudo' | head -1) if [ -n "$STARTUP_CMD" ]; then eval $STARTUP_CMD 2>/dev/null && echo -e " ${GREEN}✅ 开机自启设置成功${RESET}" || echo -e " ${YELLOW}⚠️ 开机自启需要 sudo 权限,请手动执行: ${STARTUP_CMD}${RESET}" fi fi fi pm2 save 2>/dev/null || true echo -e " ${GREEN}✅ PM2 守护进程已启动${RESET}" echo -e " ${DIM} ✓ 崩溃自动重启(无限次)${RESET}" echo -e " ${DIM} ✓ 内存超限自动重启(512MB)${RESET}" echo -e " ${DIM} ✓ 开机自动启动${RESET}" else # 没有 PM2 → 用 nohup + 看门狗脚本 cd "$NEOWEB_DIR" # 创建看门狗脚本(每 30 秒检查,崩了自动拉起) cat > "$NEOWEB_DIR/watchdog.sh" << 'WATCHDOG' #!/bin/bash # NeoWeb AI 看门狗 — 每 30 秒检查服务是否存活,崩溃自动重启 NEOWEB_DIR="$(cd "$(dirname "$0")" && pwd)" PORT="${PORT:-3001}" LOG="/tmp/neoweb-ai.log" PIDFILE="/tmp/neoweb-ai.pid" while true; do # 检查进程是否存活 if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then # 进程在,检查 HTTP 是否响应 if ! curl -sf "http://localhost:${PORT}/api/health" > /dev/null 2>&1; then echo "[$(date)] 进程在但 HTTP 无响应,等 10 秒再检查..." >> "$LOG" sleep 10 if ! curl -sf "http://localhost:${PORT}/api/health" > /dev/null 2>&1; then echo "[$(date)] HTTP 仍无响应,重启..." >> "$LOG" kill "$(cat "$PIDFILE")" 2>/dev/null sleep 2 cd "$NEOWEB_DIR" && nohup node server.js >> "$LOG" 2>&1 & echo $! > "$PIDFILE" fi fi else echo "[$(date)] 进程不存在,启动..." >> "$LOG" cd "$NEOWEB_DIR" && nohup node server.js >> "$LOG" 2>&1 & echo $! > "$PIDFILE" fi sleep 30 done WATCHDOG chmod +x "$NEOWEB_DIR/watchdog.sh" # 启动看门狗(它会自动启动 server.js) nohup bash "$NEOWEB_DIR/watchdog.sh" > /dev/null 2>&1 & echo $! > /tmp/neoweb-ai-watchdog.pid # 等看门狗启动 server sleep 3 echo -e " ${GREEN}✅ 看门狗守护已启动${RESET}" echo -e " ${DIM} ✓ 每 30 秒健康检查${RESET}" echo -e " ${DIM} ✓ 崩溃自动重启${RESET}" echo -e " ${YELLOW} ⚠️ 建议安装 PM2 以获得更好的开机自启: npm install -g pm2${RESET}" # Fix 9: 非 PM2 模式的开机自启 NODE_BIN=$(which node 2>/dev/null || echo "node") NODE_DIR=$(dirname "$NODE_BIN") if [ "$OS" = "Darwin" ]; then # macOS: 生成 launchd plist mkdir -p "$HOME/Library/LaunchAgents" PLIST_FILE="$HOME/Library/LaunchAgents/com.neowebai.server.plist" cat > "$PLIST_FILE" << PLIST Labelcom.neowebai.server ProgramArguments ${NODE_BIN} ${NEOWEB_DIR}/server.js WorkingDirectory${NEOWEB_DIR} RunAtLoad KeepAlive StandardOutPath/tmp/neoweb-ai.log StandardErrorPath/tmp/neoweb-ai-error.log EnvironmentVariables PORT${PORT} PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${NODE_DIR} PLIST launchctl unload "$PLIST_FILE" 2>/dev/null || true launchctl load "$PLIST_FILE" 2>/dev/null && echo -e " ${GREEN}✅ macOS 开机自启已设置 (launchd)${RESET}" || echo -e " ${YELLOW}⚠️ launchd 加载失败,请手动: launchctl load ${PLIST_FILE}${RESET}" elif [ "$OS" = "Linux" ] && command -v systemctl &>/dev/null && [ "$(id -u)" != "0" ]; then # Linux 非 root: 尝试 systemd user 服务 SYSTEMD_USER_DIR="$HOME/.config/systemd/user" mkdir -p "$SYSTEMD_USER_DIR" cat > "$SYSTEMD_USER_DIR/neoweb-ai.service" << SERVICE [Unit] Description=NeoWeb AI Server After=network.target [Service] Type=simple WorkingDirectory=${NEOWEB_DIR} ExecStart=${NODE_BIN} ${NEOWEB_DIR}/server.js Restart=always RestartSec=3 Environment=PORT=${PORT} Environment=PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${NODE_DIR} [Install] WantedBy=default.target SERVICE systemctl --user daemon-reload 2>/dev/null && systemctl --user enable neoweb-ai 2>/dev/null && systemctl --user start neoweb-ai 2>/dev/null && echo -e " ${GREEN}✅ Linux 开机自启已设置 (systemd user)${RESET}" || echo -e " ${YELLOW}⚠️ systemd user 设置失败,建议安装 PM2${RESET}" elif [ "$OS" = "Linux" ] && command -v systemctl &>/dev/null && [ "$(id -u)" = "0" ]; then # Linux root: 系统级 systemd 服务 cat > /etc/systemd/system/neoweb-ai.service << SERVICE [Unit] Description=NeoWeb AI Server After=network.target [Service] Type=simple User=$(whoami) WorkingDirectory=${NEOWEB_DIR} ExecStart=${NODE_BIN} ${NEOWEB_DIR}/server.js Restart=always RestartSec=3 Environment=PORT=${PORT} Environment=PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${NODE_DIR} [Install] WantedBy=multi-user.target SERVICE systemctl daemon-reload 2>/dev/null && systemctl enable neoweb-ai 2>/dev/null && echo -e " ${GREEN}✅ Linux 开机自启已设置 (systemd)${RESET}" || true fi fi # ── 等待服务就绪并打开浏览器 ──────────────────────────────────────── echo "" if wait_for_service; then open_browser else echo -e " ${YELLOW}服务可能还在初始化,请稍后手动访问 http://localhost:${PORT}${RESET}" fi # ── 安装完成 ──────────────────────────────────────────────────────── echo "" echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${RESET}" echo -e "${CYAN}${BOLD} 🎉 NeoWeb AI 安装完成!${RESET}" echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${RESET}" echo "" echo -e " 🌐 访问地址: ${CYAN}${BOLD}http://localhost:${PORT}${RESET}" echo -e " 📁 安装目录: ${NEOWEB_DIR}" echo "" echo -e " ${BOLD}首次使用:${RESET}" echo -e " 1. 在界面中添加 AI 模型(配置 API Key)" echo -e " 2. 开始与 NeoWeb AI 对话" echo "" echo -e " ${DIM}管理命令:${RESET}" echo -e " ${DIM} pm2 status — 查看运行状态${RESET}" echo -e " ${DIM} pm2 logs neoweb-ai — 查看日志${RESET}" echo -e " ${DIM} pm2 restart neoweb-ai — 重启服务${RESET}" echo "" } # ── 更新 ────────────────────────────────────────────────────────────────── do_update() { echo -e "${CYAN}${BOLD}🔄 NeoWeb AI 更新检查${RESET}" cd "$NEOWEB_DIR" 2>/dev/null || { echo -e "${RED}未找到安装目录 ${NEOWEB_DIR}${RESET}"; exit 1; } # 获取线上版本 REMOTE_VER=$(curl -sf https://ai.neowebai.com/api/version 2>/dev/null) if [ -z "$REMOTE_VER" ]; then echo -e "${RED}❌ 无法连接更新服务器${RESET}" exit 1 fi LOCAL_VER=$(node -e "console.log(require('./package.json').version)" 2>/dev/null || echo "unknown") LATEST=$(echo "$REMOTE_VER" | python3 -c "import json,sys;print(json.load(sys.stdin)['version'])" 2>/dev/null || echo "unknown") DOWNLOAD=$(echo "$REMOTE_VER" | python3 -c "import json,sys;d=json.load(sys.stdin);print(d.get('download',d.get('url','')))" 2>/dev/null) echo -e " 当前版本: ${LOCAL_VER}" echo -e " 最新版本: ${LATEST}" if [ "$LOCAL_VER" = "$LATEST" ]; then echo -e "${GREEN}✅ 已是最新版本${RESET}" exit 0 fi echo -e " ${CYAN}下载更新...${RESET}" TMP_FILE=$(mktemp /tmp/neoweb-ai-update-XXXXXX) curl -fSL --progress-bar "$DOWNLOAD" -o "$TMP_FILE" # 备份当前版本 BACKUP_DIR="$NEOWEB_DIR/backups/v${LOCAL_VER}-$(date +%Y%m%d%H%M%S)" mkdir -p "$BACKUP_DIR" cp package.json server.js "$BACKUP_DIR/" 2>/dev/null || true # 解压覆盖(保留 DB、.env、logs) tar xzf "$TMP_FILE" -C "$NEOWEB_DIR" --exclude='*.db' --exclude='*.db-*' --exclude='.env' --exclude='logs/*' rm -f "$TMP_FILE" chmod -R u+rwX "$NEOWEB_DIR" # 更新依赖(删除旧 node_modules 确保 native 模块重新编译) cd "$NEOWEB_DIR" rm -rf node_modules npm install --omit=dev 2>&1 | tail -3 # Fix 8: 重启(优化顺序:先停 pm2 / PID,等端口释放,再重新启动) echo -e " 重启服务..." if command -v pm2 &> /dev/null && pm2 list 2>/dev/null | grep -q "neoweb\|neowebai"; then # pm2 模式:用 pm2 stop 优雅停止 pm2 stop neoweb-ai 2>/dev/null || true pm2 stop neowebai 2>/dev/null || true pm2 delete neoweb-ai 2>/dev/null || true pm2 delete neowebai 2>/dev/null || true else # 非 pm2 模式:用 PID 文件精准杀进程 if [ -f /tmp/neoweb-ai.pid ]; then OLD_PID=$(cat /tmp/neoweb-ai.pid 2>/dev/null) [ -n "$OLD_PID" ] && kill "$OLD_PID" 2>/dev/null || true rm -f /tmp/neoweb-ai.pid fi # 兼容旧并发运行情况 pkill -f "node.*${NEOWEB_DIR}/server.js" 2>/dev/null || true fi # 等端口确实释放 local _waited=0 while [ $_waited -lt 8 ]; do lsof -ti:${PORT:-3001} > /dev/null 2>&1 || break sleep 1 _waited=$((_waited + 1)) done lsof -ti:${PORT:-3001} 2>/dev/null | xargs kill -9 2>/dev/null || true sleep 1 # 重新启动 cd "$NEOWEB_DIR" if command -v pm2 &> /dev/null; then if [ -f ecosystem.config.cjs ]; then pm2 start ecosystem.config.cjs --env production 2>/dev/null else pm2 start server.js --name neoweb-ai --max-memory-restart 512M 2>/dev/null fi pm2 save 2>/dev/null || true else nohup node server.js > /tmp/neoweb-ai.log 2>&1 & echo $! > /tmp/neoweb-ai.pid fi sleep 3 # 验证 if curl -sf http://localhost:${PORT:-3001}/api/health > /dev/null 2>&1; then NEW_VER=$(curl -s http://localhost:${PORT:-3001}/api/health | python3 -c "import json,sys;print(json.load(sys.stdin).get('version','?'))" 2>/dev/null || echo "$LATEST") echo -e "${GREEN}✅ 更新完成: ${LOCAL_VER} → v${NEW_VER}${RESET}" else echo -e "${YELLOW}⚠️ 文件已更新到 ${LATEST},但服务启动失败,请手动启动: cd $NEOWEB_DIR && node server.js${RESET}" fi } # ── 状态 ────────────────────────────────────────────────────────────────── do_status() { echo -e "${CYAN}${BOLD}📊 NeoWeb AI 状态${RESET}" echo "" if command -v pm2 &> /dev/null && pm2 list 2>/dev/null | grep -q neoweb-ai; then pm2 describe neoweb-ai 2>/dev/null | grep -E 'status|version|uptime|restarts|script path' | head -6 fi echo "" if curl -sf http://localhost:${PORT}/api/health > /dev/null 2>&1; then STATUS_JSON=$(curl -s http://localhost:${PORT}/api/health) echo -e " 状态: ${GREEN}✅ 运行中${RESET}" echo "$STATUS_JSON" | python3 -c " import json, sys d = json.load(sys.stdin) print(f\" 版本: v{d.get('version', '?')}\") print(f\" 工具: {d.get('tools', '?')} 个\") print(f\" 模型: {d.get('models', '?')} 个\") print(f\" 端口: ${PORT}\") " 2>/dev/null || echo " 端口: ${PORT}" else echo -e " 状态: ${RED}❌ 未运行${RESET}" echo -e " 启动: cd $NEOWEB_DIR && pm2 start ecosystem.config.cjs" fi } # ── 卸载 ────────────────────────────────────────────────────────────────── do_uninstall() { echo -e "${YELLOW}${BOLD}🗑️ 卸载 NeoWeb AI${RESET}" echo -e "${DIM}注意:仅停止服务和取消自启,不删除文件(数据保留在 ${NEOWEB_DIR})${RESET}" echo "" echo -ne "确认卸载?[y/N]: " if [ -t 0 ]; then read yn else read yn < /dev/tty 2>/dev/null || yn="n" fi [[ ! "$yn" =~ ^[Yy]$ ]] && exit 0 # 停止 pm2 pm2 stop neoweb-ai 2>/dev/null && pm2 delete neoweb-ai 2>/dev/null && pm2 save 2>/dev/null || true # 停止 nohup 进程 [ -f /tmp/neoweb-ai.pid ] && kill $(cat /tmp/neoweb-ai.pid) 2>/dev/null && rm -f /tmp/neoweb-ai.pid || true pkill -f "node.*server.js.*NeoWeb-AI" 2>/dev/null || true echo -e "${GREEN}✅ 服务已停止${RESET}" echo "" echo -e "文件保留在: ${NEOWEB_DIR}" echo -e "如需完全删除: ${BOLD}rm -rf ${NEOWEB_DIR}${RESET}" } # ── 路由 ────────────────────────────────────────────────────────────────── case "$CMD" in install) do_install ;; update) do_update ;; status) do_status ;; uninstall) do_uninstall ;; *) echo -e "${CYAN}${BOLD}NeoWeb AI${RESET} — 安装管理工具" echo "" echo "用法: bash install.sh [命令]" echo "" echo " install 首次安装(默认)" echo " update 检查并安装更新" echo " status 查看运行状态" echo " uninstall 停止服务并取消自启" ;; esac