#!/bin/bash # ═══════════════════════════════════════════════ # 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 # 📜 Copyright: 国作登字-2026-A-00037559 # ═══════════════════════════════════════════════ # server/proxy/deploy-brain-proxy.sh # 🚀 铸渊专线V2 + 光湖语言世界V3 · 大脑服务器部署脚本 # # 在大脑服务器(ZY-SVR-005)上执行 # 部署多用户VPN系统 (独立于面孔服务器的V1) # # 用法: # bash deploy-brain-proxy.sh install — 首次安装 # bash deploy-brain-proxy.sh update — 更新代码 # bash deploy-brain-proxy.sh status — 检查状态 # bash deploy-brain-proxy.sh restart — 重启服务 # bash deploy-brain-proxy.sh add-user [quota_gb] — 添加用户 # bash deploy-brain-proxy.sh remove-user — 移除用户 # bash deploy-brain-proxy.sh list-users — 列出用户 # bash deploy-brain-proxy.sh deploy-v3 — 部署V3生产环境 # bash deploy-brain-proxy.sh switch-v3 — 切换V2→V3 (用户刷新即升级) # ═══════════════════════════════════════════════ set -uo pipefail PROXY_DIR="/opt/zhuyuan-brain/proxy" NGINX_BACKUP_DIR="/opt/zhuyuan-brain/nginx-backups" REPO_PROXY_DIR="$(dirname "$0")" ACTION="${1:-status}" echo "════════════════════════════════════════" echo "🌐 铸渊专线V2 · 大脑服务器部署 · action=$ACTION" echo "════════════════════════════════════════" # ── 共用: 确保Xray以root运行 ── ensure_xray_root_user() { if [ ! -f /etc/systemd/system/xray.service.d/override.conf ]; then mkdir -p /etc/systemd/system/xray.service.d cat > /etc/systemd/system/xray.service.d/override.conf </dev/null || { echo " ⚠️ 初始Xray配置为空(无用户),添加用户后自动生成" } systemctl enable xray systemctl restart xray 2>/dev/null || true sleep 2 echo "" echo "═══ [6/8] 配置Nginx反向代理 ═══" configure_nginx echo "" echo "═══ [7/8] 启动PM2服务 ═══" start_pm2_services echo "" echo "═══ [8/8] 健康检查 ═══" health_check echo "" echo "════════════════════════════════════════" echo "✅ 铸渊专线V2安装完成 (大脑服务器)" echo "" echo "下一步:" echo " 1. 将生成的密钥添加到GitHub Secrets" echo " 2. 添加用户: bash deploy-brain-proxy.sh add-user " echo " 3. 配置腾讯云防火墙: 开放443端口" echo "════════════════════════════════════════" } # ── 生成V2独立密钥 ──────────────────────────── generate_v2_keys() { KEYS_FILE="$PROXY_DIR/.env.keys" # 如果已有密钥文件,跳过 if [ -f "$KEYS_FILE" ] && grep -q "ZY_PROXY_REALITY_PRIVATE_KEY" "$KEYS_FILE" 2>/dev/null; then echo " 密钥文件已存在,跳过生成" return fi echo " 生成V2独立密钥..." # Reality密钥对 PRIVATE_KEY="" PUBLIC_KEY="" if command -v xray &>/dev/null; then KEYS_OUTPUT=$(xray x25519 2>&1) PRIVATE_KEY=$(echo "$KEYS_OUTPUT" | grep -i "private" | awk '{print $NF}') || true PUBLIC_KEY=$(echo "$KEYS_OUTPUT" | grep -i "public" | awk '{print $NF}') || true fi if [ -z "$PRIVATE_KEY" ] && command -v openssl &>/dev/null; then TMPKEY=$(mktemp) if openssl genpkey -algorithm X25519 -out "$TMPKEY" 2>/dev/null; then PRIVATE_KEY=$(openssl pkey -in "$TMPKEY" -outform DER 2>/dev/null | tail -c 32 | base64 | tr '+/' '-_' | tr -d '=') PUBLIC_KEY=$(openssl pkey -in "$TMPKEY" -pubout -outform DER 2>/dev/null | tail -c 32 | base64 | tr '+/' '-_' | tr -d '=') fi rm -f "$TMPKEY" fi if [ -z "$PRIVATE_KEY" ]; then echo " ❌ 无法生成X25519密钥" exit 1 fi SHORT_ID=$(openssl rand -hex 8) cat > "$KEYS_FILE" <> "$KEYS_FILE" elif [ -n "${ZY_SERVER_HOST:-}" ]; then echo "ZY_SERVER_HOST=${ZY_SERVER_HOST}" >> "$KEYS_FILE" fi # 保存面孔服务器节点信息 (多节点智能选路) if [ -n "${ZY_FACE_HOST:-}" ]; then echo "" >> "$KEYS_FILE" echo "# 面孔服务器节点 (ZY-SVR-002 · SG2 · 多节点智能选路)" >> "$KEYS_FILE" echo "ZY_FACE_HOST=${ZY_FACE_HOST}" >> "$KEYS_FILE" fi if [ -n "${ZY_FACE_REALITY_PUBLIC_KEY:-}" ]; then echo "ZY_FACE_REALITY_PUBLIC_KEY=${ZY_FACE_REALITY_PUBLIC_KEY}" >> "$KEYS_FILE" fi if [ -n "${ZY_FACE_REALITY_SHORT_ID:-}" ]; then echo "ZY_FACE_REALITY_SHORT_ID=${ZY_FACE_REALITY_SHORT_ID}" >> "$KEYS_FILE" fi # 保存CN中转节点信息 if [ -n "${ZY_CN_RELAY_HOST:-}" ]; then echo "" >> "$KEYS_FILE" echo "# CN中转节点 (国内→SG · 低延迟)" >> "$KEYS_FILE" echo "ZY_CN_RELAY_HOST=${ZY_CN_RELAY_HOST}" >> "$KEYS_FILE" echo "ZY_CN_RELAY_PORT=${ZY_CN_RELAY_PORT:-2053}" >> "$KEYS_FILE" fi # 保存硅谷服务器节点信息 (Claude专线 · 美国IP出口) # D61: 冰朔已配置硅谷服务器,新增Claude单独访问VPN节点 if [ -n "${ZY_SVR_SV_HOST:-}" ] && [ -n "${ZY_SVR_SV_REALITY_PUBLIC_KEY:-}" ]; then echo "" >> "$KEYS_FILE" echo "# 硅谷服务器节点 (ZY-SVR-SV · Claude专线 · 美国IP出口)" >> "$KEYS_FILE" echo "ZY_SVR_SV_HOST=${ZY_SVR_SV_HOST}" >> "$KEYS_FILE" echo "ZY_SVR_SV_REALITY_PUBLIC_KEY=${ZY_SVR_SV_REALITY_PUBLIC_KEY}" >> "$KEYS_FILE" echo "ZY_SVR_SV_REALITY_SHORT_ID=${ZY_SVR_SV_REALITY_SHORT_ID:-}" >> "$KEYS_FILE" echo "ZY_SVR_SV_PORT=${ZY_SVR_SV_PORT:-443}" >> "$KEYS_FILE" fi # 保存SMTP邮件凭据 (带宽共享验证码·邮件通知) if [ -n "${ZY_SMTP_USER:-}" ] && [ -n "${ZY_SMTP_PASS:-}" ]; then echo "" >> "$KEYS_FILE" echo "# SMTP邮件凭据 (带宽共享验证码·邮件通知)" >> "$KEYS_FILE" echo "ZY_SMTP_USER=${ZY_SMTP_USER}" >> "$KEYS_FILE" echo "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> "$KEYS_FILE" fi chmod 600 "$KEYS_FILE" echo "" echo " ══════════ V2密钥已生成 ══════════" echo " ⚠️ 密钥已保存到: $KEYS_FILE (权限600·仅root可读)" echo " ⚠️ 如需查看密钥,请SSH到服务器执行: cat $KEYS_FILE" echo " ⚠️ 部署完成后,将公钥和ShortID添加到GitHub Secrets:" echo " ZY_BRAIN_PROXY_REALITY_PUBLIC_KEY" echo " ZY_BRAIN_PROXY_REALITY_SHORT_ID" echo " ═══════════════════════════════════" } # ── 注入SMTP邮件凭据到.env.keys ────────────── # 如果环境变量中有SMTP凭据且.env.keys中没有,自动追加 inject_smtp_credentials() { local KEYS_FILE="$PROXY_DIR/.env.keys" if [ ! -f "$KEYS_FILE" ]; then echo " ⚠️ .env.keys不存在,跳过SMTP注入" return fi if [ -z "${ZY_SMTP_USER:-}" ] || [ -z "${ZY_SMTP_PASS:-}" ]; then echo " ℹ️ ZY_SMTP_USER/ZY_SMTP_PASS未提供,跳过SMTP注入" return fi # 如果.env.keys已有SMTP配置,更新之 if grep -q "ZY_SMTP_USER" "$KEYS_FILE" 2>/dev/null; then # 移除旧的SMTP行并重新追加 sed -i '/^ZY_SMTP_USER=/d' "$KEYS_FILE" sed -i '/^ZY_SMTP_PASS=/d' "$KEYS_FILE" sed -i '/^# SMTP邮件凭据/d' "$KEYS_FILE" fi echo "" >> "$KEYS_FILE" echo "# SMTP邮件凭据 (带宽共享验证码·邮件通知)" >> "$KEYS_FILE" echo "ZY_SMTP_USER=${ZY_SMTP_USER}" >> "$KEYS_FILE" echo "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> "$KEYS_FILE" chmod 600 "$KEYS_FILE" echo " ✅ SMTP凭据已注入到 .env.keys" } # ── 部署V2服务代码 ──────────────────────────── deploy_services() { mkdir -p "$PROXY_DIR"/{service,data,logs,config} # 复制V2服务文件 cp "$REPO_PROXY_DIR"/service/user-manager.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/subscription-server-v2.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/traffic-monitor-v2.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/zy-cloud-vpn.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/vpn-worker.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/ecosystem.brain-proxy.config.js "$PROXY_DIR/" # 复制V3服务文件 (独立运行·不影响V2) cp "$REPO_PROXY_DIR"/service/subscription-server-v3.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/llm-router.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/reverse-boost-agent.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/service/proxy-guardian.js "$PROXY_DIR/service/" cp "$REPO_PROXY_DIR"/ecosystem.brain-proxy-v3.config.js "$PROXY_DIR/" # 复制∞版本服务文件 (邮件·带宽共享·守护·自动进化) cp "$REPO_PROXY_DIR"/service/email-hub.js "$PROXY_DIR/service/" 2>/dev/null || true cp "$REPO_PROXY_DIR"/service/bandwidth-pool-agent.js "$PROXY_DIR/service/" 2>/dev/null || true cp "$REPO_PROXY_DIR"/service/user-guardian-agent.js "$PROXY_DIR/service/" 2>/dev/null || true cp "$REPO_PROXY_DIR"/service/auto-evolution.js "$PROXY_DIR/service/" 2>/dev/null || true # 复制V2 Nginx配置参考 cp "$REPO_PROXY_DIR"/config/nginx-brain-proxy-snippet.conf "$PROXY_DIR/config/" 2>/dev/null || true cp "$REPO_PROXY_DIR"/config/nginx-brain-proxy-v3-snippet.conf "$PROXY_DIR/config/" 2>/dev/null || true # 复制共用文件(发邮件等) cp "$REPO_PROXY_DIR"/service/send-subscription.js "$PROXY_DIR/service/" 2>/dev/null || true echo "✅ V2服务代码已部署到 $PROXY_DIR" } # ── 启动PM2服务 ─────────────────────────────── start_pm2_services() { cd "$PROXY_DIR" || { echo "❌ 无法进入 $PROXY_DIR"; return 1; } # 加载密钥作为环境变量 if [ -f "$PROXY_DIR/.env.keys" ]; then set -a # shellcheck source=/dev/null source "$PROXY_DIR/.env.keys" set +a fi pm2 startOrRestart ecosystem.brain-proxy.config.js --update-env pm2 save echo "✅ V2 PM2服务已就绪" pm2 list } # ── 配置Nginx反向代理 (V2·端口3803) ────────── configure_nginx() { # 安装Nginx (如果未安装) if ! command -v nginx &>/dev/null; then echo " 安装Nginx..." if apt-get update -qq && apt-get install -y nginx 2>&1 | tail -5; then systemctl enable nginx echo " ✅ Nginx已安装" else echo " ❌ Nginx安装失败" return 1 fi fi # 查找Nginx配置文件 NGINX_CONF="" for candidate in /etc/nginx/sites-enabled/zhuyuan-brain.conf /etc/nginx/sites-enabled/default; do if [ -f "$candidate" ]; then NGINX_CONF="$candidate" break fi done # 如果没有配置文件,创建大脑服务器专用配置 if [ -z "$NGINX_CONF" ]; then NGINX_CONF="/etc/nginx/sites-enabled/zhuyuan-brain.conf" echo " 创建大脑服务器Nginx配置..." cat > "$NGINX_CONF" <<'NGINXEOF' server { listen 80 default_server; server_name localhost 127.0.0.1; # ─── 铸渊专线V2订阅服务 (端口 3803) ─── location /api/proxy-v2/ { proxy_pass http://127.0.0.1:3803/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 10s; proxy_read_timeout 30s; proxy_send_timeout 30s; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Cache-Control "no-store, no-cache, must-revalidate" always; } # ─── 健康探针 ─── location = /health { return 200 '{"status":"ok","server":"ZY-SVR-005-brain"}'; add_header Content-Type application/json; } } NGINXEOF # 移除默认配置避免冲突 if [ -f /etc/nginx/sites-enabled/default ] && [ "$NGINX_CONF" != "/etc/nginx/sites-enabled/default" ]; then rm -f /etc/nginx/sites-enabled/default echo " ✅ 已移除冲突的default配置" fi echo " ✅ 已创建大脑服务器Nginx配置" else echo " 使用现有Nginx配置: $NGINX_CONF" # 修复端口错误: 如果存在3802端口配置,替换为3803 if grep -q "proxy_pass.*127\.0\.0\.1:3802" "$NGINX_CONF" 2>/dev/null; then sed -i 's|proxy_pass[[:space:]]*http://127\.0\.0\.1:3802|proxy_pass http://127.0.0.1:3803|g' "$NGINX_CONF" echo " ✅ 已修复端口: 3802 → 3803" fi # 修复路径错误: 如果V2配置仍使用旧的 /api/proxy-sub/ 路径,替换为 /api/proxy-v2/ if grep -q "location.*/api/proxy-sub/" "$NGINX_CONF" 2>/dev/null && grep -q "127\.0\.0\.1:3803" "$NGINX_CONF" 2>/dev/null; then sed -i 's|location /api/proxy-sub/|location /api/proxy-v2/|g' "$NGINX_CONF" echo " ✅ 已修复V2路径: /api/proxy-sub/ → /api/proxy-v2/" fi # 如果没有proxy-v2配置,注入V2配置 if ! grep -q "proxy-v2" "$NGINX_CONF" 2>/dev/null; then echo " 添加V2订阅服务反向代理配置..." # 在server块内的最后一个location之后、server块结束}之前插入 # 使用更安全的锚点: 匹配server块内的 } (缩进的) sed -i '/^[[:space:]]*location/,/^[[:space:]]*}/ { # 找到最后一个location块结束后的位置 }' "$NGINX_CONF" 2>/dev/null || true # 使用perl进行更安全的插入(在server块的最后一个}之前) if command -v perl &>/dev/null; then perl -i -0pe 's/(server\s*\{(?:(?!server\s*\{).)*?)(^\})/\1 # ─── 铸渊专线V2订阅服务 (端口 3803) ───\n location \/api\/proxy-v2\/ {\n proxy_pass http:\/\/127.0.0.1:3803\/;\n proxy_http_version 1.1;\n proxy_set_header Host \$host;\n proxy_set_header X-Real-IP \$remote_addr;\n proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \$scheme;\n proxy_connect_timeout 10s;\n proxy_read_timeout 30s;\n proxy_send_timeout 30s;\n add_header X-Content-Type-Options nosniff always;\n add_header X-Frame-Options DENY always;\n add_header Cache-Control "no-store, no-cache, must-revalidate" always;\n }\n\n\2/ms' "$NGINX_CONF" 2>/dev/null else # 回退: 在第一个顶层 } 之前插入 sed -i '/^}/i\ # ─── 铸渊专线V2订阅服务 (端口 3803) ───\ location /api/proxy-v2/ {\ proxy_pass http://127.0.0.1:3803/;\ proxy_http_version 1.1;\ proxy_set_header Host $host;\ proxy_set_header X-Real-IP $remote_addr;\ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\ proxy_set_header X-Forwarded-Proto $scheme;\ proxy_connect_timeout 10s;\ proxy_read_timeout 30s;\ proxy_send_timeout 30s;\ add_header X-Content-Type-Options nosniff always;\ add_header X-Frame-Options DENY always;\ add_header Cache-Control "no-store, no-cache, must-revalidate" always;\ }' "$NGINX_CONF" || true fi echo " ✅ V2 proxy-v2配置已注入" else echo " proxy-v2配置已存在" fi fi # 验证并重载Nginx if nginx -t 2>/dev/null; then if nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null; then echo " ✅ Nginx配置验证通过并已重载" else echo " ⚠️ Nginx配置有效但重载失败,尝试重启..." systemctl restart nginx 2>/dev/null || true fi else echo " ⚠️ Nginx配置验证失败:" nginx -t 2>&1 || true fi } # ── 健康检查 ────────────────────────────────── health_check() { echo "检查V2服务状态..." # Xray if systemctl is-active --quiet xray 2>/dev/null; then echo " ✅ Xray: 运行中" else echo " ⚠️ Xray: 未运行 (可能还没有用户)" fi # 443端口 if ss -tlnp | grep -q ":443 "; then echo " ✅ 端口443: 监听中" else echo " ⚠️ 端口443: 未监听 (添加用户后启动)" fi # V2订阅服务 if curl -sf http://127.0.0.1:3803/health >/dev/null 2>&1; then HEALTH=$(curl -sf http://127.0.0.1:3803/health) USERS=$(echo "$HEALTH" | grep -o '"users_count":[0-9]*' | cut -d: -f2) echo " ✅ V2订阅服务: 正常 (${USERS:-0}个用户)" else echo " ❌ V2订阅服务: 端口3803无响应" fi # V3订阅服务 (如果已部署) if curl -sf http://127.0.0.1:3805/health >/dev/null 2>&1; then HEALTH3=$(curl -sf http://127.0.0.1:3805/health) USERS3=$(echo "$HEALTH3" | grep -o '"users_count":[0-9]*' | cut -d: -f2) echo " ✅ V3订阅服务: 正常 (${USERS3:-0}个用户) · 光湖语言世界" else echo " ⚠️ V3订阅服务: 端口3805无响应 (未部署或未启动)" fi # Nginx反向代理 if systemctl is-active --quiet nginx 2>/dev/null; then if curl -sf http://127.0.0.1:80/api/proxy-v2/health >/dev/null 2>&1; then echo " ✅ Nginx反代: 正常 (/api/proxy-v2/ → 3803)" else echo " ⚠️ Nginx反代: /api/proxy-v2/ 无法访问 (检查proxy_pass端口)" fi else echo " ⚠️ Nginx: 未运行" fi # PM2 pm2 list 2>/dev/null || echo " ⚠️ PM2: 未配置" # 用户列表 echo "" echo " ═══ 用户列表 ═══" node "$PROXY_DIR/service/user-manager.js" list 2>/dev/null || echo " (无用户)" # 云防火墙提醒 echo "" echo " ═══ 云防火墙提醒 ═══" echo " ⚠️ 大脑服务器腾讯云控制台防火墙需开放:" echo " TCP 443 端口 允许所有来源 (0.0.0.0/0) ← VPN入口" echo " TCP 22 端口 允许所有来源 ← SSH管理" } # ── update: 更新代码 ────────────────────────── update() { echo "更新V2代理服务..." deploy_services ensure_xray_root_user mkdir -p "$PROXY_DIR/logs" chmod 755 "$PROXY_DIR/logs" # 重建Xray配置(根据当前用户列表) node "$PROXY_DIR/service/user-manager.js" rebuild 2>/dev/null || true systemctl restart xray 2>/dev/null || true # 确保UFW开放443 if command -v ufw &>/dev/null; then ufw allow 443/tcp comment "Xray VLESS+Reality V2" 2>/dev/null || true fi # 修复/更新Nginx反向代理配置 configure_nginx start_pm2_services health_check echo "✅ V2更新完成" } # ── restart: 重启 ───────────────────────────── restart() { echo "重启V2代理服务..." systemctl restart xray 2>/dev/null || true start_pm2_services sleep 3 health_check } # ── add-user: 添加用户 ──────────────────────── add_user() { EMAIL="${2:-}" QUOTA="${3:-500}" if [ -z "$EMAIL" ]; then echo "用法: bash deploy-brain-proxy.sh add-user [quota_gb]" exit 1 fi node "$PROXY_DIR/service/user-manager.js" add "$EMAIL" "$QUOTA" } # ── remove-user: 移除用户 ───────────────────── remove_user() { EMAIL="${2:-}" if [ -z "$EMAIL" ]; then echo "用法: bash deploy-brain-proxy.sh remove-user " exit 1 fi node "$PROXY_DIR/service/user-manager.js" remove "$EMAIL" } # ── list-users: 列出用户 ────────────────────── list_users() { node "$PROXY_DIR/service/user-manager.js" list } # ── V3 Nginx配置辅助函数 ────────────────────── # 安全注入V3 location块到nginx配置 # 仅在最后一个顶层 } 之前插入(主server块的结束位置) # 避免旧 sed '/^}/i\' 模式在多server块配置中重复注入的问题 inject_v3_nginx_block() { local conf="$1" if grep -q "proxy-v3" "$conf" 2>/dev/null; then return 0 # 已存在 fi local last_brace last_brace=$(grep -n '^}' "$conf" | tail -1 | cut -d: -f1) if [ -z "$last_brace" ] || [ "$last_brace" -le 1 ]; then echo " ❌ 未找到有效的server块结束位置" return 1 fi { head -n $((last_brace - 1)) "$conf" cat <<'V3BLOCK' # ─── 光湖语言世界V3 (端口 3805) ─── location /api/proxy-v3/ { proxy_pass http://127.0.0.1:3805/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 10s; proxy_read_timeout 30s; proxy_send_timeout 30s; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Cache-Control "no-store, no-cache, must-revalidate" always; } V3BLOCK tail -n +${last_brace} "$conf" } > "${conf}.v3-tmp" if [ -s "${conf}.v3-tmp" ]; then mv "${conf}.v3-tmp" "$conf" return 0 else rm -f "${conf}.v3-tmp" echo " ❌ V3注入生成文件为空" return 1 fi } # 移除所有V3 location块(用于修复错误注入) remove_v3_nginx_blocks() { local conf="$1" # 删除从V3注释行到该location块闭合}的所有内容 sed -i '/# ─── 光湖语言世界V3/,/^[[:space:]]*}/d' "$conf" # 清理连续空行(保留最多一个空行) sed -i '/^$/N;/^\n$/D' "$conf" } # 清理 sites-enabled/ 中残留的备份文件 # nginx 会加载 sites-enabled/ 中的所有文件,备份文件中的 default_server 会导致冲突 clean_stale_nginx_backups() { local stale_count=0 for stale in /etc/nginx/sites-enabled/*.v2-backup \ /etc/nginx/sites-enabled/*.pre-repair \ /etc/nginx/sites-enabled/*.v3-tmp; do if [ -f "$stale" ]; then echo " 🧹 清理残留备份: $stale" rm -f "$stale" stale_count=$((stale_count + 1)) fi done if [ "$stale_count" -gt 0 ]; then echo " ✅ 已清理 $stale_count 个残留备份文件" fi } # 验证nginx配置并返回错误信息 validate_nginx() { local result result=$(nginx -t 2>&1) if echo "$result" | grep -q "successful"; then return 0 else echo "$result" | grep -E 'emerg|error' | head -5 return 1 fi } # ── deploy-v3: 部署V3生产环境 ────────────────── # V2继续运行·V3独立启动·/api/proxy-v3/ deploy_v3() { echo "部署V3生产环境 (V2继续可用)..." deploy_services # 注入SMTP凭据到.env.keys (如果环境变量中有且.env.keys中没有) inject_smtp_credentials # 清理 sites-enabled/ 中残留的备份文件(防止 duplicate default_server) clean_stale_nginx_backups # 加载密钥作为环境变量 if [ -f "$PROXY_DIR/.env.keys" ]; then set -a # shellcheck source=/dev/null source "$PROXY_DIR/.env.keys" set +a fi # 启动V3 PM2进程 cd "$PROXY_DIR" || { echo "❌ 无法进入 $PROXY_DIR"; return 1; } pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env pm2 save # 添加V3 Nginx路径 NGINX_CONF="" for candidate in /etc/nginx/sites-enabled/zhuyuan-brain.conf /etc/nginx/sites-enabled/default; do if [ -f "$candidate" ]; then NGINX_CONF="$candidate" break fi done if [ -n "$NGINX_CONF" ]; then if ! grep -q "proxy-v3" "$NGINX_CONF" 2>/dev/null; then echo " 添加V3路径..." if inject_v3_nginx_block "$NGINX_CONF"; then echo " ✅ V3路径已添加" else echo " ❌ V3路径注入失败" fi else echo " proxy-v3配置已存在" fi if validate_nginx; then nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true echo " ✅ Nginx已重载" else echo " ❌ Nginx配置验证失败,V3 PM2进程已启动但Nginx未更新" echo " 请手动检查: nginx -t" fi else echo " ⚠️ 未找到Nginx配置文件,跳过V3路径注入" fi echo "" echo "════════════════════════════════════════" echo "✅ V3生产环境已就绪" echo "" echo "访问方法:" echo " V3订阅: https://域名/api/proxy-v3/sub/{token}" echo " V3仪表盘: https://域名/api/proxy-v3/dashboard/{token}" echo " V3健康: https://域名/api/proxy-v3/health" echo "" echo "V2继续运行在 /api/proxy-v2/ (端口3803)" echo "V3运行在 /api/proxy-v3/ (端口3805)" echo "" echo "切换V2用户到V3: bash deploy-brain-proxy.sh switch-v3" echo "升级到∞版本: bash deploy-brain-proxy.sh deploy-infinity" echo "════════════════════════════════════════" # V3健康检查 sleep 2 if curl -sf http://127.0.0.1:3805/health >/dev/null 2>&1; then HEALTH=$(curl -sf http://127.0.0.1:3805/health) echo " ✅ V3订阅服务: 正常" echo " $HEALTH" else echo " ⚠️ V3订阅服务(3805)尚未就绪,请稍等几秒后检查" fi } # ── switch-v3: 切换V2→V3 ────────────────────── # 用户刷新订阅即获取V3配置 switch_v3() { echo "切换 /api/proxy-v2/ → V3 (端口3805)..." NGINX_CONF="" for candidate in /etc/nginx/sites-enabled/zhuyuan-brain.conf /etc/nginx/sites-enabled/default; do if [ -f "$candidate" ]; then NGINX_CONF="$candidate" break fi done if [ -z "$NGINX_CONF" ]; then echo "❌ 未找到Nginx配置文件" exit 1 fi # 清理 sites-enabled/ 中残留的备份文件(nginx会加载该目录所有文件,导致duplicate default_server) clean_stale_nginx_backups # 确保备份目录存在(备份存放在 sites-enabled 外部) mkdir -p "$NGINX_BACKUP_DIR" || { echo "❌ 无法创建备份目录: $NGINX_BACKUP_DIR"; exit 1; } local CONF_BASENAME CONF_BASENAME="$(basename "$NGINX_CONF")" # 验证V3服务是否运行 if ! curl -sf http://127.0.0.1:3805/health >/dev/null 2>&1; then echo "❌ V3服务(3805)未运行!请先执行 deploy-v3" exit 1 fi # 预检查: 验证当前Nginx配置是否有效 if ! validate_nginx; then echo " ⚠️ 当前Nginx配置存在问题,尝试自动修复..." # 如果V3注入导致的问题(旧sed '/^}/i'在多server块配置中重复注入) if grep -q "光湖语言世界V3" "$NGINX_CONF" 2>/dev/null; then echo " 🔧 修复V3 location块注入错误..." cp "$NGINX_CONF" "${NGINX_BACKUP_DIR}/${CONF_BASENAME}.pre-repair" remove_v3_nginx_blocks "$NGINX_CONF" inject_v3_nginx_block "$NGINX_CONF" if validate_nginx; then echo " ✅ V3注入已修复" nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true else echo " ❌ 自动修复失败,恢复原配置" cp "${NGINX_BACKUP_DIR}/${CONF_BASENAME}.pre-repair" "$NGINX_CONF" exit 1 fi else echo " ❌ Nginx配置错误非V3相关,请手动检查: nginx -t" exit 1 fi fi # 备份当前(已验证有效的)Nginx配置(存到 sites-enabled 外部) cp "$NGINX_CONF" "${NGINX_BACKUP_DIR}/${CONF_BASENAME}.v2-backup" echo " ✅ 已备份Nginx配置到 ${NGINX_BACKUP_DIR}/${CONF_BASENAME}.v2-backup" # 将 /api/proxy-v2/ 的 proxy_pass 从 3803 改为 3805 if grep -q "127\.0\.0\.1:3803" "$NGINX_CONF" 2>/dev/null; then sed -i 's|127\.0\.0\.1:3803|127.0.0.1:3805|g' "$NGINX_CONF" echo " ✅ /api/proxy-v2/ 已切换到V3 (3803→3805)" else echo " ⚠️ 未找到3803端口配置,可能已切换" fi # 重载Nginx if validate_nginx; then nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true echo " ✅ Nginx已重载" else echo " ❌ Nginx配置验证失败,回滚..." cp "${NGINX_BACKUP_DIR}/${CONF_BASENAME}.v2-backup" "$NGINX_CONF" nginx -s reload 2>/dev/null || systemctl reload nginx 2>/dev/null || true echo " ✅ 已回滚到V2配置" exit 1 fi echo "" echo "════════════════════════════════════════" echo "✅ V2→V3 切换完成!" echo "" echo "现在 /api/proxy-v2/sub/{token} 指向V3 (端口3805)" echo "用户刷新订阅即可获取「光湖语言世界」配置" echo "" echo "回滚方法 (如有问题):" echo " cp ${NGINX_BACKUP_DIR}/${CONF_BASENAME}.v2-backup $NGINX_CONF" echo " nginx -s reload" echo "════════════════════════════════════════" } # ── deploy-infinity: 部署∞版本 ───────────────── # 在V3基础上启动自主进化引擎和协议镜像引擎 deploy_infinity() { echo "部署 ∞ 版本 (在V3基础上添加自主进化能力)..." deploy_services # 注入SMTP凭据到.env.keys (如果环境变量中有且.env.keys中没有) inject_smtp_credentials # 确保V3进程已在运行 if ! pm2 list 2>/dev/null | grep -q "zy-proxy-v3-sub"; then echo " ⚠️ V3进程未运行,先部署V3..." deploy_v3 fi # 加载密钥作为环境变量 if [ -f "$PROXY_DIR/.env.keys" ]; then set -a # shellcheck source=/dev/null source "$PROXY_DIR/.env.keys" set +a fi # 复制 ∞ 版本新增文件 echo " 复制∞版本模块..." for f in auto-evolution.js email-hub.js protocol-mirror.js; do if [ -f "$REPO_PROXY_DIR/service/$f" ]; then cp "$REPO_PROXY_DIR/service/$f" "$PROXY_DIR/service/$f" echo " ✅ $f" else echo " ⚠️ $f 不存在于仓库" fi done # 复制更新后的PM2配置 cp "$REPO_PROXY_DIR/ecosystem.brain-proxy-v3.config.js" "$PROXY_DIR/ecosystem.brain-proxy-v3.config.js" # 创建进化报告目录 mkdir -p "$PROXY_DIR/data/evolution-reports" # 重启所有V3+∞进程 (PM2会根据新配置启动新增进程) cd "$PROXY_DIR" || { echo "❌ 无法进入 $PROXY_DIR"; return 1; } pm2 startOrRestart ecosystem.brain-proxy-v3.config.js --update-env pm2 save echo "" echo "════════════════════════════════════════" echo "♾️ ∞ 版本部署完成!" echo "" echo "新增进程:" echo " zy-auto-evolution — 自主进化引擎 (调度中枢)" echo " zy-protocol-mirror — 协议镜像引擎 (Xray自动更新)" echo "" echo "定时任务:" echo " 每30分钟 — 协议版本检查" echo " 每月1号 — 月度进化 + 流量重置邮件" echo " 流量70% — 全用户预警邮件" echo "" echo "PM2进程列表:" pm2 list 2>/dev/null || true echo "════════════════════════════════════════" # 健康检查 sleep 2 echo "" echo "∞ 版本健康检查:" for proc in zy-proxy-v3-sub zy-proxy-guardian zy-reverse-boost zy-auto-evolution zy-protocol-mirror; do if pm2 show "$proc" 2>/dev/null | grep -q "online"; then echo " ✅ $proc: 在线" else echo " ⚠️ $proc: 未就绪 (等待启动)" fi done } # ── 执行 ────────────────────────────────────── case "$ACTION" in install) install ;; update) update ;; status) health_check ;; restart) restart ;; add-user) add_user "$@" ;; remove-user) remove_user "$@" ;; list-users) list_users ;; deploy-v3) deploy_v3 ;; switch-v3) switch_v3 ;; deploy-infinity) deploy_infinity ;; *) echo "用法: bash deploy-brain-proxy.sh {install|update|status|restart|add-user|remove-user|list-users|deploy-v3|switch-v3|deploy-infinity}" exit 1 ;; esac