406 lines
14 KiB
Bash
406 lines
14 KiB
Bash
#!/bin/bash
|
||
# ═══════════════════════════════════════════════
|
||
# 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
||
# 📜 Copyright: 国作登字-2026-A-00037559
|
||
# ═══════════════════════════════════════════════
|
||
# server/proxy/setup/setup-cn-relay.sh
|
||
# 🇨🇳 广州CN中转 · 安装配置脚本
|
||
#
|
||
# 在广州服务器(ZY-SVR-004)上执行
|
||
# 将VPN流量从国内中转到新加坡服务器
|
||
#
|
||
# 架构:
|
||
# 国内用户 → CN:2053 (Nginx stream) → SG:443 (Xray VPN)
|
||
# 国内用户 → CN:80/api/proxy-sub/ → SG订阅服务 (配置获取)
|
||
#
|
||
# 用法:
|
||
# bash setup-cn-relay.sh <SG_SERVER_IP>
|
||
# bash setup-cn-relay.sh 43.134.16.246
|
||
#
|
||
# 环境变量:
|
||
# ZY_SG_SERVER_HOST — 新加坡服务器IP (必需)
|
||
# ═══════════════════════════════════════════════
|
||
|
||
set -uo pipefail
|
||
# 注意: 不使用 set -e,因为 grep/nc 等命令在无匹配时返回非零
|
||
# 关键步骤手动检查错误
|
||
|
||
# 颜色
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
log_info() { echo -e "${GREEN}[CN中转]${NC} $1"; }
|
||
log_warn() { echo -e "${YELLOW}[CN中转]${NC} ⚠️ $1"; }
|
||
log_error() { echo -e "${RED}[CN中转]${NC} ❌ $1"; }
|
||
log_step() { echo -e "${BLUE}[CN中转]${NC} 📌 $1"; }
|
||
|
||
# 查找ngx_stream_module.so文件路径
|
||
find_stream_module_so() {
|
||
for mod_path in \
|
||
/usr/lib/nginx/modules/ngx_stream_module.so \
|
||
/usr/lib64/nginx/modules/ngx_stream_module.so \
|
||
/etc/nginx/modules/ngx_stream_module.so \
|
||
/usr/share/nginx/modules/ngx_stream_module.so; do
|
||
if [ -f "$mod_path" ]; then
|
||
echo "$mod_path"
|
||
return
|
||
fi
|
||
done
|
||
# 使用find作为最后回退手段 (限制深度避免扫描过慢)
|
||
find /usr/lib /usr/lib64 /usr/share /etc/nginx -maxdepth 3 -name "ngx_stream_module.so" -print -quit 2>/dev/null
|
||
}
|
||
|
||
# 尝试从modules-available启用stream模块
|
||
enable_stream_from_modules_available() {
|
||
for avail in /usr/share/nginx/modules-available/mod-stream.conf \
|
||
/etc/nginx/modules-available/mod-stream.conf; do
|
||
if [ -f "$avail" ]; then
|
||
ln -sf "$avail" /etc/nginx/modules-enabled/
|
||
log_info "✅ 已从modules-available启用stream模块"
|
||
return 0
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
# 确保stream模块已加载到nginx配置中
|
||
ensure_stream_module_loaded() {
|
||
local nginx_conf="$1"
|
||
|
||
# 检查modules-enabled是否已自动加载
|
||
if [ -d /etc/nginx/modules-enabled ]; then
|
||
if grep -q "ngx_stream_module" /etc/nginx/modules-enabled/* 2>/dev/null; then
|
||
log_info "✅ stream模块已通过modules-enabled自动加载"
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
# 检查nginx.conf是否已包含load_module指令
|
||
if grep -q "ngx_stream_module" "$nginx_conf" 2>/dev/null; then
|
||
log_info "✅ stream动态模块已在nginx.conf中加载"
|
||
return 0
|
||
fi
|
||
|
||
# 查找并加载.so文件
|
||
local module_path
|
||
module_path=$(find_stream_module_so)
|
||
|
||
if [ -n "$module_path" ]; then
|
||
log_info "加载stream动态模块: $module_path"
|
||
sed -i "1i load_module $module_path;" "$nginx_conf"
|
||
log_info "✅ stream动态模块已加载"
|
||
return 0
|
||
fi
|
||
|
||
# 尝试从modules-available启用
|
||
log_warn "未找到ngx_stream_module.so文件"
|
||
log_warn "尝试启用modules-available中的stream配置..."
|
||
if enable_stream_from_modules_available; then
|
||
return 0
|
||
fi
|
||
|
||
log_warn "stream模块可能为静态编译,继续配置..."
|
||
return 1
|
||
}
|
||
|
||
# ── 参数 ──────────────────────────────────────
|
||
SG_HOST="${ZY_SG_SERVER_HOST:-${1:-}}"
|
||
RELAY_PORT="${ZY_CN_RELAY_PORT:-2053}"
|
||
CN_ROOT="/opt/zhuyuan-cn"
|
||
|
||
if [ -z "$SG_HOST" ]; then
|
||
log_error "缺少新加坡服务器IP"
|
||
echo ""
|
||
echo "用法: bash setup-cn-relay.sh <SG_SERVER_IP>"
|
||
echo " 或: ZY_SG_SERVER_HOST=1.2.3.4 bash setup-cn-relay.sh"
|
||
exit 1
|
||
fi
|
||
|
||
echo "════════════════════════════════════════"
|
||
echo "🇨🇳 铸渊专线 · CN中转配置"
|
||
echo "════════════════════════════════════════"
|
||
echo ""
|
||
echo " SG服务器: $SG_HOST"
|
||
echo " 中转端口: $RELAY_PORT"
|
||
echo ""
|
||
|
||
# ── §1 安装Nginx stream模块 ──────────────────
|
||
log_step "§1 检查Nginx stream模块"
|
||
|
||
# 检查Nginx是否安装
|
||
if ! command -v nginx &>/dev/null; then
|
||
log_info "安装Nginx..."
|
||
apt-get update -qq
|
||
apt-get install -y -qq nginx
|
||
fi
|
||
|
||
# Ubuntu/Debian: stream模块通常是独立的动态模块包 (libnginx-mod-stream)
|
||
# 必须显式安装,即使nginx -V显示--with-stream也只表示编译支持,不代表模块已安装
|
||
log_info "确保stream动态模块包已安装..."
|
||
apt-get install -y -qq libnginx-mod-stream 2>/dev/null || true
|
||
|
||
# 验证stream模块是否可用
|
||
if nginx -V 2>&1 | grep -q "with-stream"; then
|
||
log_info "✅ Nginx stream模块已可用"
|
||
else
|
||
log_warn "Nginx未包含stream模块,安装完整版..."
|
||
apt-get install -y -qq nginx-full 2>/dev/null || apt-get install -y -qq nginx-extras 2>/dev/null || true
|
||
|
||
if nginx -V 2>&1 | grep -q "with-stream"; then
|
||
log_info "✅ Nginx stream模块已安装"
|
||
else
|
||
log_error "无法安装Nginx stream模块"
|
||
log_warn "尝试使用socat替代方案..."
|
||
apt-get install -y -qq socat 2>/dev/null || true
|
||
fi
|
||
fi
|
||
|
||
# 检查是否需要动态加载stream模块
|
||
# Ubuntu/Debian的Nginx通常将stream编译为动态模块(.so),需要用load_module显式加载
|
||
# 有两种加载机制:
|
||
# 1. modules-enabled目录: /etc/nginx/modules-enabled/ (Debian/Ubuntu自动加载)
|
||
# 2. 手动load_module: 在nginx.conf顶部显式加载.so文件
|
||
NGINX_MAIN="/etc/nginx/nginx.conf"
|
||
|
||
ensure_stream_module_loaded "$NGINX_MAIN"
|
||
|
||
# ── §2 配置Nginx stream中转 ──────────────────
|
||
log_step "§2 配置TCP中转 (端口$RELAY_PORT → $SG_HOST:443)"
|
||
|
||
# 创建stream配置目录
|
||
mkdir -p /etc/nginx/stream-conf.d
|
||
|
||
# 创建stream中转配置
|
||
cat > /etc/nginx/stream-conf.d/zy-relay.conf << STREAMCONF
|
||
# ═══════════════════════════════════════════════
|
||
# 🇨🇳 铸渊专线 · CN中转 · TCP Stream
|
||
# 自动生成于: $(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M CST')
|
||
#
|
||
# 架构: 国内用户 → CN:${RELAY_PORT} → SG:443 (Xray VPN)
|
||
# 协议: 原始TCP转发 (不解密,不修改)
|
||
# ═══════════════════════════════════════════════
|
||
|
||
upstream zy_sg_backend {
|
||
server ${SG_HOST}:443;
|
||
}
|
||
|
||
server {
|
||
listen ${RELAY_PORT};
|
||
proxy_pass zy_sg_backend;
|
||
proxy_timeout 300s;
|
||
proxy_connect_timeout 10s;
|
||
}
|
||
STREAMCONF
|
||
|
||
log_info "✅ Stream中转配置已创建"
|
||
|
||
# 确保主Nginx配置包含stream块
|
||
if ! grep -q "stream-conf.d" "$NGINX_MAIN" 2>/dev/null; then
|
||
log_info "添加stream块到nginx.conf..."
|
||
|
||
# 在文件末尾添加stream块 (stream块必须在http块之外)
|
||
cat >> "$NGINX_MAIN" << 'STREAMBLOCK'
|
||
|
||
# ═══ 铸渊专线 · CN中转 Stream ═══
|
||
stream {
|
||
include /etc/nginx/stream-conf.d/*.conf;
|
||
}
|
||
STREAMBLOCK
|
||
log_info "✅ stream块已添加到nginx.conf"
|
||
fi
|
||
|
||
# ── §3 配置Nginx HTTP反代订阅 ────────────────
|
||
log_step "§3 配置订阅服务反代 (CN → SG订阅)"
|
||
|
||
# 创建或更新CN的Nginx HTTP配置
|
||
CN_NGINX_CONF="/etc/nginx/sites-available/zy-cn-relay.conf"
|
||
cat > "$CN_NGINX_CONF" << HTTPCONF
|
||
# ═══════════════════════════════════════════════
|
||
# 🇨🇳 铸渊专线 · CN中转 · HTTP反代
|
||
# 自动生成于: $(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M CST')
|
||
#
|
||
# 功能: 将订阅请求反代到SG服务器
|
||
# 国内用户通过CN服务器获取订阅配置 (无需国际网)
|
||
# ═══════════════════════════════════════════════
|
||
|
||
server {
|
||
listen 80 default_server;
|
||
server_name _;
|
||
|
||
# ─── 健康检查 ───
|
||
location = /health {
|
||
return 200 '{"status":"ok","service":"zy-cn-relay","relay_to":"${SG_HOST}"}';
|
||
add_header Content-Type application/json;
|
||
}
|
||
|
||
# ─── 订阅服务反代 → SG服务器 ───
|
||
location /api/proxy-sub/ {
|
||
proxy_pass http://${SG_HOST}/api/proxy-sub/;
|
||
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;
|
||
}
|
||
|
||
# ─── 默认页面 ───
|
||
location / {
|
||
return 200 '铸渊CN中转节点 · ZY-SVR-004';
|
||
add_header Content-Type 'text/plain; charset=utf-8';
|
||
}
|
||
|
||
access_log /var/log/nginx/zy-cn-relay.log;
|
||
error_log /var/log/nginx/zy-cn-relay-error.log;
|
||
}
|
||
HTTPCONF
|
||
|
||
# 启用配置
|
||
ln -sf "$CN_NGINX_CONF" /etc/nginx/sites-enabled/zy-cn-relay.conf
|
||
# 移除默认配置以避免冲突
|
||
rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true
|
||
|
||
log_info "✅ HTTP反代配置已创建"
|
||
|
||
# ── §4 防火墙配置 ────────────────────────────
|
||
log_step "§4 防火墙配置"
|
||
|
||
ufw allow 80/tcp comment "HTTP (订阅反代)" 2>/dev/null || true
|
||
ufw allow ${RELAY_PORT}/tcp comment "ZY-Relay (VPN中转)" 2>/dev/null || true
|
||
ufw reload 2>/dev/null || true
|
||
log_info "✅ 防火墙已开放: 80(HTTP) + ${RELAY_PORT}(VPN中转)"
|
||
|
||
# ── §5 测试配置并重载 ────────────────────────
|
||
log_step "§5 测试并应用配置"
|
||
|
||
NGINX_TEST_OUTPUT=$(nginx -t 2>&1)
|
||
if echo "$NGINX_TEST_OUTPUT" | grep -q "test is successful"; then
|
||
# reload 只能用于已运行的Nginx,如果Nginx未运行则需要 start
|
||
if systemctl is-active --quiet nginx; then
|
||
systemctl reload nginx
|
||
log_info "✅ Nginx配置测试通过 · 已重载"
|
||
else
|
||
systemctl enable nginx 2>/dev/null || true
|
||
systemctl start nginx
|
||
if systemctl is-active --quiet nginx; then
|
||
log_info "✅ Nginx配置测试通过 · 已启动"
|
||
else
|
||
log_error "Nginx启动失败"
|
||
systemctl status nginx --no-pager 2>&1 || true
|
||
exit 1
|
||
fi
|
||
fi
|
||
else
|
||
log_warn "Nginx配置测试未通过,尝试自动修复..."
|
||
echo "$NGINX_TEST_OUTPUT"
|
||
|
||
# 修复: 如果stream指令未知,说明load_module缺失
|
||
if echo "$NGINX_TEST_OUTPUT" | grep -q 'unknown directive "stream"'; then
|
||
log_info "检测到stream指令未识别,尝试修复模块加载..."
|
||
|
||
# 强制安装stream模块包
|
||
apt-get update -qq
|
||
apt-get install -y -qq libnginx-mod-stream 2>/dev/null || true
|
||
|
||
# 使用共享函数确保模块已加载
|
||
ensure_stream_module_loaded "$NGINX_MAIN"
|
||
fi
|
||
|
||
# 重新测试
|
||
if nginx -t 2>&1; then
|
||
if systemctl is-active --quiet nginx; then
|
||
systemctl reload nginx
|
||
log_info "✅ 自动修复成功 · Nginx已重载"
|
||
else
|
||
systemctl enable nginx 2>/dev/null || true
|
||
systemctl start nginx
|
||
log_info "✅ 自动修复成功 · Nginx已启动"
|
||
fi
|
||
else
|
||
log_error "Nginx配置测试失败 (自动修复未能解决)"
|
||
nginx -t 2>&1
|
||
echo ""
|
||
log_error "请检查以下可能原因:"
|
||
echo " 1. libnginx-mod-stream 包是否可用: dpkg -l | grep libnginx"
|
||
echo " 2. 模块文件是否存在: find / -name 'ngx_stream_module.so' 2>/dev/null"
|
||
echo " 3. nginx.conf内容: cat /etc/nginx/nginx.conf"
|
||
echo " 4. modules-enabled目录: ls -la /etc/nginx/modules-enabled/"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# ── §6 保存中转状态 ──────────────────────────
|
||
log_step "§6 保存中转状态"
|
||
|
||
mkdir -p "$CN_ROOT/data"
|
||
cat > "$CN_ROOT/data/relay-status.json" << STATUSJSON
|
||
{
|
||
"service": "zy-cn-relay",
|
||
"sg_server": "$SG_HOST",
|
||
"relay_port": $RELAY_PORT,
|
||
"subscription_proxy": "http://CN_IP/api/proxy-sub/",
|
||
"vpn_relay": "CN_IP:$RELAY_PORT → $SG_HOST:443",
|
||
"configured_at": "$(TZ=Asia/Shanghai date -u '+%Y-%m-%dT%H:%M:%SZ')",
|
||
"status": "active"
|
||
}
|
||
STATUSJSON
|
||
|
||
log_info "✅ 中转状态已保存"
|
||
|
||
# ── §7 健康检查 ──────────────────────────────
|
||
log_step "§7 健康检查"
|
||
|
||
sleep 1
|
||
|
||
# 确认Nginx真正在运行
|
||
if ! systemctl is-active --quiet nginx; then
|
||
log_warn "Nginx未运行,尝试启动..."
|
||
systemctl enable nginx 2>/dev/null || true
|
||
systemctl start nginx
|
||
sleep 2
|
||
fi
|
||
|
||
HEALTH_OK=true
|
||
|
||
# 检查中转端口
|
||
if ss -tlnp | grep -q ":${RELAY_PORT}[[:space:]]"; then
|
||
log_info "✅ 中转端口 ${RELAY_PORT}: 监听中"
|
||
else
|
||
log_error "中转端口 ${RELAY_PORT}: 未监听"
|
||
HEALTH_OK=false
|
||
fi
|
||
|
||
# 检查HTTP
|
||
if curl -sf http://127.0.0.1/health >/dev/null 2>&1; then
|
||
log_info "✅ HTTP健康检查: 正常"
|
||
else
|
||
log_warn "HTTP健康检查: 未响应 (可能80端口被占用)"
|
||
fi
|
||
|
||
# 如果关键端口未监听,报告Nginx状态用于调试
|
||
if [ "$HEALTH_OK" = "false" ]; then
|
||
log_error "关键服务未就绪 · Nginx诊断信息:"
|
||
systemctl status nginx --no-pager 2>&1 || true
|
||
echo ""
|
||
echo "Nginx错误日志 (最后10行):"
|
||
tail -10 /var/log/nginx/error.log 2>/dev/null || echo " (无日志)"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "════════════════════════════════════════"
|
||
echo "✅ CN中转配置完成"
|
||
echo ""
|
||
echo "中转架构:"
|
||
echo " VPN: 国内用户 → CN:${RELAY_PORT} → SG:443 (Xray)"
|
||
echo " 订阅: http://CN_IP/api/proxy-sub/sub/{token}"
|
||
echo ""
|
||
echo "下一步:"
|
||
echo " 1. 运行 deploy-proxy-service.yml action=update 更新SG的Xray配置"
|
||
echo " 2. 重新发送订阅邮件 (新配置包含CN中转节点)"
|
||
echo "════════════════════════════════════════"
|