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

479 lines
19 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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-proxy.sh
# 🚀 铸渊专线V1 · 一键部署脚本
#
# ⚠️ [DEPRECATED · D58] V1节点已停用
# 铸渊专线2.0已测试通过并正式启用V1节点不再使用。
# V2部署脚本: deploy-brain-proxy.sh (大脑服务器·共享流量池2000GB/月)
# 此文件保留作为V1历史参考不再部署运行。
#
# 在SG服务器上执行完成代理服务的完整部署:
# 1. 安装Xray-core + BBR
# 2. 生成密钥
# 3. 配置Xray (从环境变量或密钥文件读取)
# 4. 配置Nginx反代
# 5. 启动PM2服务
# 6. 健康检查
#
# 用法:
# bash deploy-proxy.sh install — 首次安装
# bash deploy-proxy.sh update — 更新配置
# bash deploy-proxy.sh status — 检查状态
# bash deploy-proxy.sh restart — 重启所有服务
# ═══════════════════════════════════════════════
set -uo pipefail
# 注意: 不使用 set -e关键步骤手动检查错误
PROXY_DIR="/opt/zhuyuan/proxy"
REPO_PROXY_DIR="$(dirname "$0")"
ACTION="${1:-status}"
echo "════════════════════════════════════════"
echo "🌐 铸渊专线 · 部署 · action=$ACTION"
echo "════════════════════════════════════════"
# ── 共用: 确保Xray以root运行 (修复User=nobody问题) ──
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
}
# ── 共用: 确保日志目录权限正确 ──
ensure_log_permissions() {
mkdir -p "$PROXY_DIR/logs"
chmod 755 "$PROXY_DIR/logs"
}
# ── 共用: 保存环境变量到.env.keys ──
save_server_host() {
KEYS_FILE="$PROXY_DIR/.env.keys"
if [ -n "${ZY_SERVER_HOST:-}" ] && [ -f "$KEYS_FILE" ]; then
# 检查是否已存在
if grep -q "^ZY_SERVER_HOST=" "$KEYS_FILE" 2>/dev/null; then
# 更新已有的值
sed -i "s|^ZY_SERVER_HOST=.*|ZY_SERVER_HOST=${ZY_SERVER_HOST}|" "$KEYS_FILE"
else
# 追加新行
echo "" >> "$KEYS_FILE"
echo "# 服务器地址 (部署时自动写入)" >> "$KEYS_FILE"
echo "ZY_SERVER_HOST=${ZY_SERVER_HOST}" >> "$KEYS_FILE"
fi
echo " ✅ ZY_SERVER_HOST 已保存到 .env.keys"
elif [ -n "${ZY_SERVER_HOST:-}" ] && [ ! -f "$KEYS_FILE" ]; then
echo " ⚠️ .env.keys 不存在,创建并写入 ZY_SERVER_HOST"
echo "# 服务器地址 (部署时自动写入)" > "$KEYS_FILE"
echo "ZY_SERVER_HOST=${ZY_SERVER_HOST}" >> "$KEYS_FILE"
chmod 600 "$KEYS_FILE"
fi
# 保存CN中转地址 (如果有)
if [ -n "${ZY_CN_RELAY_HOST:-}" ] && [ -f "$KEYS_FILE" ]; then
if grep -q "^ZY_CN_RELAY_HOST=" "$KEYS_FILE" 2>/dev/null; then
sed -i "s|^ZY_CN_RELAY_HOST=.*|ZY_CN_RELAY_HOST=${ZY_CN_RELAY_HOST}|" "$KEYS_FILE"
else
echo "" >> "$KEYS_FILE"
echo "# CN中转服务器地址 (部署时自动写入)" >> "$KEYS_FILE"
echo "ZY_CN_RELAY_HOST=${ZY_CN_RELAY_HOST}" >> "$KEYS_FILE"
fi
echo " ✅ ZY_CN_RELAY_HOST 已保存到 .env.keys"
fi
# 保存SMTP凭据 (如果有) — 使守护Agent和流量监控可发送告警邮件
# 注: 使用删除+追加方式避免sed特殊字符问题 (密码常含|&/$等)
if [ -n "${ZY_SMTP_USER:-}" ] && [ -f "$KEYS_FILE" ]; then
if grep -q "^ZY_SMTP_USER=" "$KEYS_FILE" 2>/dev/null; then
grep -v "^ZY_SMTP_USER=" "$KEYS_FILE" > "${KEYS_FILE}.tmp" && mv "${KEYS_FILE}.tmp" "$KEYS_FILE"
else
echo "" >> "$KEYS_FILE"
echo "# SMTP凭据 (部署时自动写入·守护Agent告警用)" >> "$KEYS_FILE"
fi
printf '%s\n' "ZY_SMTP_USER=${ZY_SMTP_USER}" >> "$KEYS_FILE"
chmod 600 "$KEYS_FILE"
echo " ✅ ZY_SMTP_USER 已保存到 .env.keys"
fi
if [ -n "${ZY_SMTP_PASS:-}" ] && [ -f "$KEYS_FILE" ]; then
if grep -q "^ZY_SMTP_PASS=" "$KEYS_FILE" 2>/dev/null; then
grep -v "^ZY_SMTP_PASS=" "$KEYS_FILE" > "${KEYS_FILE}.tmp" && mv "${KEYS_FILE}.tmp" "$KEYS_FILE"
fi
printf '%s\n' "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> "$KEYS_FILE"
chmod 600 "$KEYS_FILE"
echo " ✅ ZY_SMTP_PASS 已保存到 .env.keys"
fi
}
# ── install: 首次完整安装 ─────────────────────
install() {
echo ""
echo "═══ [1/7] 安装Xray-core + BBR ═══"
bash "$REPO_PROXY_DIR/setup/install-xray.sh"
echo ""
echo "═══ [2/7] 配置Xray ═══"
if ! configure_xray; then
echo "❌ Xray配置失败安装中止"
exit 1
fi
echo ""
echo "═══ [3/7] 启动Xray服务 ═══"
ensure_xray_root_user
ensure_log_permissions
systemctl enable xray
systemctl restart xray
sleep 2
if systemctl is-active --quiet xray; then
echo "✅ Xray运行中"
else
echo "❌ Xray启动失败"
journalctl -u xray --no-pager -n 20
exit 1
fi
echo ""
echo "═══ [4/7] 部署代理服务代码 ═══"
deploy_services
save_server_host
echo ""
echo "═══ [5/7] 配置Nginx ═══"
configure_nginx
echo ""
echo "═══ [6/7] 启动PM2服务 ═══"
start_pm2_services
echo ""
echo "═══ [7/7] 健康检查 ═══"
health_check
echo ""
echo "════════════════════════════════════════"
echo "✅ 铸渊专线安装完成"
echo ""
echo "下一步:"
echo " 1. 将生成的密钥添加到GitHub Secrets"
echo " 2. 运行 'send-subscription' 工作流发送订阅链接"
echo "════════════════════════════════════════"
}
# ── 配置Xray ──────────────────────────────────
configure_xray() {
# 读取密钥 (优先环境变量, 其次密钥文件)
KEYS_FILE="$PROXY_DIR/.env.keys"
if [ -z "${ZY_PROXY_UUID:-}" ] && [ -f "$KEYS_FILE" ]; then
# shellcheck source=/dev/null
source "$KEYS_FILE"
fi
# 验证关键变量
if [ -z "${ZY_PROXY_UUID:-}" ]; then
echo "❌ 缺少 ZY_PROXY_UUID"
echo " 请先运行 install 生成密钥,或设置环境变量"
return 1
fi
if [ -z "${ZY_PROXY_REALITY_PRIVATE_KEY:-}" ]; then
echo "❌ 缺少 ZY_PROXY_REALITY_PRIVATE_KEY"
return 1
fi
if [ -z "${ZY_PROXY_REALITY_SHORT_ID:-}" ]; then
echo "❌ 缺少 ZY_PROXY_REALITY_SHORT_ID"
return 1
fi
# 用环境变量替换模板
CONFIG_TEMPLATE="$REPO_PROXY_DIR/config/xray-config-template.json"
CONFIG_OUTPUT="/usr/local/etc/xray/config.json"
sed -e "s|{{ZY_PROXY_UUID}}|${ZY_PROXY_UUID}|g" \
-e "s|{{ZY_PROXY_REALITY_PRIVATE_KEY}}|${ZY_PROXY_REALITY_PRIVATE_KEY}|g" \
-e "s|{{ZY_PROXY_REALITY_SHORT_ID}}|${ZY_PROXY_REALITY_SHORT_ID}|g" \
"$CONFIG_TEMPLATE" > "$CONFIG_OUTPUT"
# 验证配置
if xray run -test -c "$CONFIG_OUTPUT" 2>/dev/null; then
echo "✅ Xray配置验证通过"
else
echo "⚠️ Xray配置验证失败查看详情:"
xray run -test -c "$CONFIG_OUTPUT" 2>&1 || true
echo " 配置文件: $CONFIG_OUTPUT"
return 1
fi
}
# ── 部署服务代码 ──────────────────────────────
deploy_services() {
mkdir -p "$PROXY_DIR"/{service,data,logs,dashboard}
# 复制服务文件
cp "$REPO_PROXY_DIR"/service/*.js "$PROXY_DIR/service/"
cp "$REPO_PROXY_DIR"/dashboard/*.js "$PROXY_DIR/dashboard/"
cp "$REPO_PROXY_DIR"/ecosystem.proxy.config.js "$PROXY_DIR/"
echo "✅ 服务代码已部署到 $PROXY_DIR"
}
# ── 配置Nginx ─────────────────────────────────
configure_nginx() {
# 查找正确的Nginx配置文件 (zhuyuan.conf 优先于 default)
NGINX_CONF=""
for candidate in /etc/nginx/sites-enabled/zhuyuan.conf /etc/nginx/sites-enabled/default; do
if [ -f "$candidate" ]; then
NGINX_CONF="$candidate"
break
fi
done
if [ -z "$NGINX_CONF" ]; then
echo " ⚠️ 未找到Nginx站点配置文件"
return 0
fi
echo " 使用Nginx配置: $NGINX_CONF"
# 移除默认配置文件,避免 duplicate default_server 冲突
# zhuyuan.conf 已声明 default_server不能与 default 文件共存
if [ "$NGINX_CONF" = "/etc/nginx/sites-enabled/zhuyuan.conf" ] && [ -e "/etc/nginx/sites-enabled/default" ]; then
rm -f /etc/nginx/sites-enabled/default
echo " ✅ 已移除冲突的 default 配置 (避免 duplicate default_server)"
fi
if ! grep -q "proxy-sub" "$NGINX_CONF" 2>/dev/null; then
echo " 添加Nginx代理订阅反向代理配置..."
# 在第一个 location = /health 之前插入 proxy-sub location
sed -i '/# ─── 健康探针 ───/{
# 只在第一次匹配时插入
i\ # ─── 铸渊专线订阅服务 (端口 3802) ───\n location /api/proxy-sub/ {\n proxy_pass http://127.0.0.1:3802/;\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 Cache-Control "no-store, no-cache, must-revalidate" always;\n }\n
}' "$NGINX_CONF" || true
echo " ✅ Nginx proxy-sub配置已注入"
else
echo " Nginx proxy-sub配置已存在"
fi
# 确保主服务器块是default_server (修复localhost/127.0.0.1健康检查匹配问题)
if ! grep -q "default_server" "$NGINX_CONF" 2>/dev/null; then
# 匹配 "listen 80;" 并添加 default_server兼容不同空白格式
sed -i '0,/listen[[:space:]]\+80[[:space:]]*;/{s/listen[[:space:]]\+80[[:space:]]*;/listen 80 default_server;/}' "$NGINX_CONF" || true
echo " ✅ 已设置为默认服务器 (default_server)"
fi
# 确保server_name包含localhost (使内部健康检查可匹配)
# 先检查server_name行中是否已有localhost避免重复添加
if ! grep "server_name" "$NGINX_CONF" | head -1 | grep -q "localhost" 2>/dev/null; then
sed -i '0,/server_name /{s/server_name /server_name localhost 127.0.0.1 /}' "$NGINX_CONF" || true
echo " ✅ 已添加localhost到server_name"
fi
if nginx -t 2>/dev/null; then
nginx -s reload || true
echo " ✅ Nginx配置验证通过并已重载"
else
echo " ⚠️ Nginx配置验证失败:"
nginx -t 2>&1 || true
fi
}
# ── 启动/重启PM2服务 ──────────────────────────
# 使用 pm2 startOrRestart 统一处理(已注册→重启,未注册→启动)
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.proxy.config.js --update-env
pm2 save
echo "✅ PM2代理服务已就绪"
pm2 list
}
# ── 健康检查 ──────────────────────────────────
health_check() {
echo "检查服务状态..."
# Xray
if systemctl is-active --quiet xray; then
echo " ✅ Xray: 运行中"
else
echo " ❌ Xray: 未运行"
fi
# 443端口 (应由Xray占用)
if ss -tlnp | grep -q ":443 "; then
echo " ✅ 端口443: 监听中"
# 检查是谁占用443
PORT443_PROC=$(ss -tlnp | grep ":443 " | head -1)
if echo "$PORT443_PROC" | grep -q "xray"; then
echo " → Xray占用443 (正确·VPN模式)"
echo " → dest回落: www.microsoft.com:443 (Reality反探测)"
elif echo "$PORT443_PROC" | grep -q "nginx"; then
echo " ⚠️ Nginx占用443 (应由Xray占用·VPN可能不工作)"
echo " → 请先停止Nginx的443监听再启动Xray"
fi
else
echo " ❌ 端口443: 未监听"
fi
# UFW防火墙 — 验证端口443已开放
if command -v ufw &>/dev/null; then
if ufw status | grep -q "443/tcp.*ALLOW"; then
echo " ✅ UFW防火墙: 端口443已开放"
else
echo " ❌ UFW防火墙: 端口443未开放!"
echo " → 自动修复: 正在添加UFW规则..."
if ufw allow 443/tcp comment "Xray VLESS+Reality" 2>/dev/null; then
echo " → ✅ 已添加UFW规则"
else
echo " → ❌ UFW规则添加失败 (可能需要root权限)"
fi
fi
fi
# 订阅服务 (直接访问)
if curl -sf http://127.0.0.1:3802/health >/dev/null 2>&1; then
echo " ✅ 订阅服务: 正常 (直连3802)"
else
echo " ❌ 订阅服务: 端口3802无响应"
fi
# 订阅服务 (通过Nginx反代)
# 使用ZY_SERVER_HOST作为Host头确保Nginx server_name匹配
HEALTH_HOST="${ZY_SERVER_HOST:-}"
if [ -z "$HEALTH_HOST" ] && [ -f "$PROXY_DIR/.env.keys" ]; then
HEALTH_HOST=$(grep "^ZY_SERVER_HOST=" "$PROXY_DIR/.env.keys" 2>/dev/null | sed 's/^ZY_SERVER_HOST=//;s/#.*//;s/^[[:space:]]*//;s/[[:space:]]*$//')
fi
if curl -sf -H "Host: ${HEALTH_HOST:-localhost}" http://127.0.0.1/api/proxy-sub/health >/dev/null 2>&1; then
echo " ✅ Nginx反代: 正常 (/api/proxy-sub/ → 3802)"
else
echo " ⚠️ Nginx反代: /api/proxy-sub/ 未响应 (Nginx配置可能缺失)"
echo " → 检查: /etc/nginx/sites-enabled/zhuyuan.conf 是否包含 proxy-sub location"
echo " → 检查: Nginx是否有default_server指令或server_name包含localhost"
fi
# PM2
pm2 list 2>/dev/null || echo " ⚠️ PM2: 未配置"
# ── 云防火墙诊断 (腾讯云轻量应用服务器) ──
echo ""
echo " ═══ 云防火墙诊断 ═══"
echo " ⚠️ 腾讯云有两层防火墙:"
echo " 1. UFW (操作系统层) — 已在上方检查"
echo " 2. 腾讯云控制台防火墙 — 需要冰朔手动确认"
echo ""
echo " 如果服务端一切正常但客户端仍然连接超时 (i/o timeout):"
echo " → 问题在腾讯云控制台的防火墙规则"
echo " → 冰朔操作: 腾讯云控制台 → 轻量应用服务器 → 防火墙"
echo " → 确认规则: TCP 443 端口 允许所有来源 (0.0.0.0/0)"
echo " → 确认规则: TCP 80 端口 允许所有来源 (0.0.0.0/0)"
echo ""
# 外部连通性自检 (从服务器内部测试443端口是否可达)
if timeout 5 bash -c "echo >/dev/tcp/127.0.0.1/443" 2>/dev/null; then
echo " ✅ 本地443端口自检: 可达"
else
echo " ❌ 本地443端口自检: 不可达"
fi
}
# ── update: 更新配置 ──────────────────────────
update() {
echo "更新代理服务..."
deploy_services
save_server_host
configure_xray
configure_nginx
ensure_xray_root_user
ensure_log_permissions
# 确保443端口在UFW中开放 (Xray VLESS+Reality必需)
if command -v ufw &>/dev/null; then
if ! ufw status | grep -q "443/tcp.*ALLOW" 2>/dev/null; then
if ufw allow 443/tcp comment "Xray VLESS+Reality" 2>/dev/null; then
echo " ✅ 已添加UFW端口443规则"
else
echo " ⚠️ UFW端口443规则添加失败 (请手动检查)"
fi
fi
fi
# 关闭3802外部端口 (订阅服务改为通过Nginx反代访问)
if ufw status | grep -q "3802/tcp" 2>/dev/null; then
ufw delete allow 3802/tcp || true
echo " ✅ 已移除3802端口外部访问规则"
fi
# 检查并修复443端口冲突
# 如果Nginx占用了443端口(旧SSL配置)需要移除以让Xray接管
if ss -tlnp | grep ":443 " | grep -q "nginx"; then
echo "⚠️ 检测到Nginx占用443端口 (旧SSL配置冲突)"
echo " 修复: 移除Nginx的443监听配置以让Xray接管..."
# 移除旧的SSL配置 (不再通过Xray回落提供HTTPS)
for conf in /etc/nginx/sites-enabled/ssl-*.conf; do
[ -e "$conf" ] || continue
if grep -q "listen.*443\|listen.*8443" "$conf" 2>/dev/null; then
echo " 移除旧SSL配置: $conf"
rm -f "$conf"
fi
done
nginx -t 2>/dev/null && nginx -s reload 2>/dev/null || true
echo " ✅ Nginx旧SSL配置已清理"
fi
systemctl restart xray
# PM2代理服务
start_pm2_services
health_check
echo "✅ 更新完成"
}
# ── status: 检查状态 ──────────────────────────
status() {
health_check
}
# ── restart: 重启所有 ─────────────────────────
restart() {
echo "重启所有代理服务..."
systemctl restart xray
# PM2代理服务
start_pm2_services
sleep 3
health_check
}
# ── 执行 ──────────────────────────────────────
case "$ACTION" in
install) install ;;
update) update ;;
status) status ;;
restart) restart ;;
*)
echo "用法: bash deploy-proxy.sh {install|update|status|restart}"
exit 1
;;
esac