guanghulab/server/proxy/deploy-brain-proxy.sh
2026-05-10 13:12:44 +08:00

904 lines
35 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <email> [quota_gb] — 添加用户
# bash deploy-brain-proxy.sh remove-user <email> — 移除用户
# 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 <<EOF
[Service]
User=root
EOF
systemctl daemon-reload
echo " ✅ Xray服务已配置为root用户运行"
fi
}
# ── install: 首次完整安装 ─────────────────────
install() {
echo ""
echo "═══ [1/8] 安装Xray-core + BBR ═══"
bash "$REPO_PROXY_DIR/setup/install-xray.sh"
echo ""
echo "═══ [2/8] 创建V2目录结构 ═══"
mkdir -p "$PROXY_DIR"/{config,data,logs,service}
echo ""
echo "═══ [3/8] 生成V2密钥 ═══"
generate_v2_keys
echo ""
echo "═══ [4/8] 部署V2服务代码 ═══"
deploy_services
echo ""
echo "═══ [5/8] 配置Xray ═══"
ensure_xray_root_user
mkdir -p "$PROXY_DIR/logs"
chmod 755 "$PROXY_DIR/logs"
# Xray配置由user-manager动态生成初始为空用户
node "$PROXY_DIR/service/user-manager.js" rebuild 2>/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 <email>"
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" <<EOF
# 铸渊专线V2密钥 · 大脑服务器 · 自动生成 · $(date -u +"%Y-%m-%dT%H:%M:%SZ")
# ⚠️ 仅root可读 · 不可提交到仓库
# ⚠️ V2密钥独立于V1 · 两套VPN互不影响
ZY_PROXY_REALITY_PRIVATE_KEY=$PRIVATE_KEY
ZY_PROXY_REALITY_PUBLIC_KEY=$PUBLIC_KEY
ZY_PROXY_REALITY_SHORT_ID=$SHORT_ID
EOF
# 保存服务器地址
if [ -n "${ZY_BRAIN_HOST:-}" ]; then
echo "ZY_BRAIN_HOST=${ZY_BRAIN_HOST}" >> "$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 <email> [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 <email>"
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