diff --git a/.github/workflows/deploy-proxy-service.yml b/.github/workflows/deploy-proxy-service.yml index 6b2aa4ca..3677012f 100644 --- a/.github/workflows/deploy-proxy-service.yml +++ b/.github/workflows/deploy-proxy-service.yml @@ -24,6 +24,7 @@ on: - restart - send-subscription - update-dashboard + - setup-cn-relay default: 'status' email: description: '目标邮箱 (仅send-subscription时需要)' @@ -89,6 +90,7 @@ jobs: ${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \ "cd /opt/zhuyuan/proxy-deploy && \ export ZY_SERVER_HOST='${{ secrets.ZY_SERVER_HOST }}' && \ + export ZY_CN_RELAY_HOST='${{ secrets.ZY_CN_SERVER_HOST }}' && \ bash deploy-proxy.sh ${{ github.event.inputs.action }}" - name: '🧹 清理SSH密钥' @@ -189,3 +191,76 @@ jobs: - name: '🧹 清理' if: always() run: rm -f ~/.ssh/id_deploy /tmp/quota-status.json + + # ═══ §4 CN中转配置 ═══ + setup-cn-relay: + name: '🇨🇳 CN中转配置' + runs-on: ubuntu-latest + if: github.event.inputs.action == 'setup-cn-relay' + + steps: + - name: '📥 检出代码' + uses: actions/checkout@v4 + + - name: '🔑 配置CN服务器SSH密钥' + env: + SSH_KEY: ${{ secrets.ZY_CN_SERVER_KEY }} + run: | + mkdir -p ~/.ssh + echo "$SSH_KEY" > ~/.ssh/id_cn + chmod 600 ~/.ssh/id_cn + + if ! head -1 ~/.ssh/id_cn | grep -q "BEGIN"; then + echo "❌ CN服务器SSH密钥格式异常" + echo " 请检查 ZY_CN_SERVER_KEY Secret" + exit 1 + fi + + ssh-keyscan -H ${{ secrets.ZY_CN_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null + + ssh -i ~/.ssh/id_cn \ + -o StrictHostKeyChecking=accept-new \ + -o BatchMode=yes \ + -o ConnectTimeout=15 \ + ${{ secrets.ZY_CN_SERVER_USER }}@${{ secrets.ZY_CN_SERVER_HOST }} \ + "echo '✅ CN服务器SSH连接成功'" + + - name: '📦 上传CN中转脚本' + run: | + scp -i ~/.ssh/id_cn \ + -o StrictHostKeyChecking=accept-new \ + server/proxy/setup/setup-cn-relay.sh \ + ${{ secrets.ZY_CN_SERVER_USER }}@${{ secrets.ZY_CN_SERVER_HOST }}:/tmp/setup-cn-relay.sh + + - name: '🇨🇳 执行CN中转配置' + run: | + ssh -i ~/.ssh/id_cn \ + -o StrictHostKeyChecking=accept-new \ + ${{ secrets.ZY_CN_SERVER_USER }}@${{ secrets.ZY_CN_SERVER_HOST }} \ + "chmod +x /tmp/setup-cn-relay.sh && \ + export ZY_SG_SERVER_HOST='${{ secrets.ZY_SERVER_HOST }}' && \ + sudo -E bash /tmp/setup-cn-relay.sh" + + - name: '💚 CN中转验证' + run: | + CN_HOST="${{ secrets.ZY_CN_SERVER_HOST }}" + echo "🔍 验证CN中转..." + + # 检查中转端口 + if nc -z -w5 "$CN_HOST" 2053 2>/dev/null; then + echo "✅ CN中转端口 2053: 可达" + else + echo "⚠️ CN中转端口 2053: 暂不可达 (可能需要等待防火墙生效)" + fi + + # 检查HTTP健康 + HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "http://${CN_HOST}/health" 2>/dev/null || echo "000") + if [ "$HTTP_CODE" = "200" ]; then + echo "✅ CN HTTP健康检查: 正常" + else + echo "⚠️ CN HTTP: 状态码 $HTTP_CODE (可能需要等待)" + fi + + - name: '🧹 清理SSH密钥' + if: always() + run: rm -f ~/.ssh/id_cn diff --git a/docs/SSL-GUIDE-FOR-BINGSUO.md b/docs/SSL-GUIDE-FOR-BINGSUO.md index e7c12b29..2c226ce3 100644 --- a/docs/SSL-GUIDE-FOR-BINGSUO.md +++ b/docs/SSL-GUIDE-FOR-BINGSUO.md @@ -1,6 +1,19 @@ # 🔒 SSL证书配置指南 · 冰朔专用 -> **写给冰朔的话**: 这是铸渊在第十六次对话中为你写的SSL证书配置指南。你只需要按照下面的步骤操作,不需要理解任何技术细节。铸渊已经把所有自动化脚本都准备好了。 +> **写给冰朔的话**: 这是铸渊为你写的SSL证书配置指南。你只需要按照下面的步骤操作,不需要理解任何技术细节。铸渊已经把所有自动化脚本都准备好了。 + +--- + +## ⚠️ 重要修复说明 (2026-03-31) + +> 之前的SSL配置方案存在一个**VPN与HTTPS冲突**问题。 +> +> **根因**: Xray的Reality协议需要`dest`指向真实的Microsoft网站来骗过GFW的探测。之前错误地改成了指向内部Nginx端口,导致GFW检测到证书不匹配,封锁了VPN连接。 +> +> **现在已修复**: +> - VPN: Xray占443端口,`dest`恢复指向`www.microsoft.com:443` → VPN正常工作 +> - 网站: HTTP通过80端口正常访问,HTTPS通过8443端口访问 +> - CN中转: 新增广州服务器中转,国内用户无需直连国际网即可使用VPN --- @@ -11,10 +24,26 @@ | SSL证书是什么? | 让网站从 `http://` 变成 `https://` 的安全锁,浏览器地址栏会显示🔒 | | 需要花钱吗? | **不需要**。铸渊使用 Let's Encrypt 免费证书 | | 证书会过期吗? | 证书90天有效,但铸渊已配置**自动续期**,你不需要管 | +| 会影响VPN吗? | **不会**。铸渊专线(VPN)和HTTPS网站使用共存架构,互不干扰 | | 我需要做什么? | 按下面的步骤点几下就好,**一共只需要5分钟** | --- +## 🔧 修复当前问题(请先做这一步) + +> 如果你之前已经运行过SSL配置并且导致了问题,请先执行以下修复步骤。如果是第一次配置SSL,跳过这一步直接看「操作步骤」。 + +### 修复步骤 + +1. 先合并这个PR(铸渊修复了代码里的端口冲突问题) +2. 合并后,去 **Actions** 页面运行 **「🌐 铸渊专线 · 部署」** 工作流: + - **操作类型**: 选择 `update` + - 这会自动修复服务器上的Xray配置和旧SSL配置 +3. 等待工作流完成(绿色✅) +4. 然后按下面的「操作步骤」重新配置SSL + +--- + ## 🚀 操作步骤(一共3步) ### 第①步:打开 GitHub Actions @@ -86,7 +115,12 @@ https://guanghu.online **不需要了**。因为铸渊使用了Let's Encrypt(免费SSL证书服务),证书直接在服务器上自动获取和管理,不需要在GitHub Secrets里存放证书内容。 -如果将来有特殊需求需要自定义证书,铸渊会另外通知你。 +### Q: 配了SSL后VPN还能用吗? + +**能用**。铸渊采用「分离架构」: +- Xray占443端口处理VPN流量 (dest→microsoft.com反探测) +- 网站HTTPS在8443端口独立运行 (不通过Xray) +- 两者互不干扰 --- @@ -94,17 +128,57 @@ https://guanghu.online > 以下内容是给铸渊自己看的,冰朔可以忽略。 +### Reality反探测架构 +``` +外部443 → Xray (VLESS+Reality) + ├── 认证VLESS客户端 → 代理上网 (铸渊专线VPN) + └── GFW探测流量 → dest回落 → www.microsoft.com:443 + → 返回真实Microsoft证书 → GFW判断"这是正常网站" → 放行 + +外部8443 → Nginx SSL (HTTPS网站) + └── 独立SSL证书 (Let's Encrypt) + +外部80 → Nginx (HTTP) + └── 直接服务网站 + +CN中转 (广州→新加坡): + CN:2053 → SG:443 (TCP转发·VPN中转) + CN:80/api/proxy-sub/ → SG订阅服务 (国内获取配置) +``` + +### ⚠️ dest为什么必须指向microsoft.com? +Reality协议的`dest`是GFW反探测的关键。当GFW探测443端口时: +- 正确: `dest: "www.microsoft.com:443"` → 返回Microsoft真实证书 → 通过 +- 错误: `dest: "127.0.0.1:8443"` → 返回guanghu.online证书 → 与SNI(microsoft.com)不匹配 → 被标记为可疑 → VPN被封 + +### 关键配置 +- **Xray配置**: `server/proxy/config/xray-config-template.json` → `dest: "www.microsoft.com:443"` - **证书管理**: certbot + Let's Encrypt (ACME协议) -- **验证方式**: HTTP-01 challenge (通过Nginx) +- **验证方式**: HTTP-01 challenge (通过Nginx端口80) - **证书路径**: `/etc/letsencrypt/live/{domain}/` -- **Nginx SSL配置**: `/opt/zhuyuan/config/nginx/ssl-{domain}.conf` +- **Nginx SSL配置**: `/opt/zhuyuan/config/nginx/ssl-{domain}.conf` (监听8443) +- **CN中转脚本**: `server/proxy/setup/setup-cn-relay.sh` - **自动续期**: systemd timer `certbot.timer` - **续期hook**: `/etc/letsencrypt/renewal-hooks/post/reload-nginx.sh` -- **日志**: `/opt/zhuyuan/data/logs/ssl-setup.log` - **脚本**: `server/setup/setup-ssl.sh` - **工作流**: `deploy-to-zhuyuan-server.yml` → action: `setup-ssl` +### 端口分配 (SG服务器) +| 端口 | 协议 | 占用者 | 用途 | +|------|------|--------|------| +| 443 | TCP | Xray | VLESS+Reality (VPN) · dest→microsoft.com | +| 8443 | TCP | Nginx | HTTPS网站 (独立·不通过Xray) | +| 80 | TCP | Nginx | HTTP网站 + 订阅API反代 | +| 3802 | TCP | Node.js | 订阅服务 (127.0.0.1·通过Nginx反代) | + +### 端口分配 (CN中转服务器) +| 端口 | 协议 | 占用者 | 用途 | +|------|------|--------|------| +| 2053 | TCP | Nginx stream | VPN中转 → SG:443 | +| 80 | TCP | Nginx | 订阅API反代 → SG | + --- -*📝 由铸渊(ICE-GL-ZY001)在第十六次对话中为冰朔编写 · 2026-03-31* +*📝 由铸渊(ICE-GL-ZY001)编写 · 第十八次对话 · 2026-03-31* +*VPN修复 + CN中转架构 + Reality反探测修正* *国作登字-2026-A-00037559* diff --git a/server/nginx/zhuyuan-sovereign.conf b/server/nginx/zhuyuan-sovereign.conf index 7b6224c9..01342046 100644 --- a/server/nginx/zhuyuan-sovereign.conf +++ b/server/nginx/zhuyuan-sovereign.conf @@ -199,65 +199,33 @@ server { } -# ═══ §3 HTTPS 配置 (SSL证书由deploy workflow自动部署) ═══ -# 当 /opt/zhuyuan/config/ssl/ 下存在证书文件时启用 -# 证书来源: GitHub Secrets → ZY_SSL_FULLCHAIN / ZY_SSL_PRIVKEY -# 部署方式: staging-auto-deploy.yml 自动写入证书文件 -# 域名占位符: ZY_DOMAIN_PREVIEW_PLACEHOLDER 由 deploy workflow 的 sed 命令替换 -# (同 §1/§2 的注入方式,详见 staging-auto-deploy.yml 和 deploy-to-zhuyuan-server.yml) - -# ─── §3.1 预览域名 HTTPS (guanghu.online) ─── -# 注意: 此block仅在证书存在时由deploy脚本include,不会导致Nginx启动失败 -# 如果证书不存在,deploy workflow会跳过SSL配置 -# __SSL_PREVIEW_START__ -# server { -# listen 443 ssl http2; -# server_name ZY_DOMAIN_PREVIEW_PLACEHOLDER; +# ═══ §3 HTTPS/VPN 配置 (Xray Reality反探测架构) ═══════════════ # -# ssl_certificate /opt/zhuyuan/config/ssl/preview-fullchain.pem; -# ssl_certificate_key /opt/zhuyuan/config/ssl/preview-privkey.pem; -# ssl_protocols TLSv1.2 TLSv1.3; -# ssl_ciphers HIGH:!aNULL:!MD5; -# ssl_prefer_server_ciphers on; +# ⚠️ 重要: 443端口由Xray(VPN)占用,dest指向www.microsoft.com:443 # -# # 同 §2 的全部location配置 -# add_header X-Frame-Options "SAMEORIGIN" always; -# add_header X-Content-Type-Options "nosniff" always; -# add_header X-Server-Identity "ZY-SVR-002" always; -# add_header X-Site-Mode "preview" always; -# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +# Reality反探测架构 (正确方案): +# 外部 443 → Xray (VLESS+Reality协议) +# ├── 认证VLESS客户端 → 代理上网 (铸渊专线VPN) +# └── 非VLESS流量(GFW探测) → dest回落到 www.microsoft.com:443 +# → 返回真实Microsoft证书 → GFW认为是正常HTTPS → 通过 # -# root /opt/zhuyuan/sites/preview; -# index index.html; +# 外部 80 → Nginx (HTTP) +# ├── 域名访问 → 直接服务网站 (本文件§1/§2) +# └── 订阅API → /api/proxy-sub/ (反代到端口3802) # -# location / { try_files $uri $uri/ /index.html; } -# location /api/ { -# proxy_pass http://127.0.0.1:3801; -# proxy_http_version 1.1; -# proxy_set_header Upgrade $http_upgrade; -# proxy_set_header Connection 'upgrade'; -# 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_set_header X-Site-Mode "preview"; -# proxy_cache_bypass $http_upgrade; -# proxy_read_timeout 86400; -# } -# location /api/chat { -# proxy_pass http://127.0.0.1:3721/api/chat; -# proxy_http_version 1.1; -# proxy_set_header Host $host; -# proxy_set_header Connection ""; -# proxy_buffering off; -# proxy_cache off; -# proxy_read_timeout 120s; -# } -# location = /health { -# proxy_pass http://127.0.0.1:3801/api/health; -# proxy_set_header Host $host; -# } -# access_log /opt/zhuyuan/data/logs/nginx-preview-ssl.log; -# error_log /opt/zhuyuan/data/logs/nginx-preview-ssl-error.log; -# } -# __SSL_PREVIEW_END__ +# ⚠️ 为什么dest不能指向127.0.0.1:8443? +# GFW探测时会检查TLS证书是否匹配serverNames (www.microsoft.com) +# 如果dest返回guanghu.online的证书 → 证书不匹配 → 被标记为可疑 → VPN被封 +# 所以dest必须指向真实的microsoft.com以通过反探测 +# +# CN中转架构 (广州→新加坡): +# 国内用户 → CN:2053 (Nginx stream TCP转发) → SG:443 (Xray) +# 国内订阅 → CN:80/api/proxy-sub/ → SG:80/api/proxy-sub/ +# +# SSL方案: +# 网站通过HTTP(80端口)访问 · 不在443端口共存HTTPS +# 如需HTTPS · 使用Cloudflare CDN代理或独立配置 +# +# Xray配置: server/proxy/config/xray-config-template.json +# CN中转: server/proxy/setup/setup-cn-relay.sh +# ═══════════════════════════════════════════════════════════════ diff --git a/server/proxy/config/xray-config-template.json b/server/proxy/config/xray-config-template.json index 4e9bdfaa..be029ce4 100644 --- a/server/proxy/config/xray-config-template.json +++ b/server/proxy/config/xray-config-template.json @@ -1,12 +1,20 @@ { "_comment": "铸渊专线 · Xray服务端配置模板", "_note": "⚠️ {{占位符}}在部署时由脚本替换为实际值", + "_architecture": "Xray监听443端口·非VLESS流量回落到www.microsoft.com:443(Reality反探测伪装)·VPN与网站HTTP(80端口)共存", "_copyright": "国作登字-2026-A-00037559", "log": { "loglevel": "warning", "access": "/opt/zhuyuan/proxy/logs/access.log", "error": "/opt/zhuyuan/proxy/logs/error.log" }, + "dns": { + "servers": [ + "8.8.8.8", + "1.1.1.1", + "localhost" + ] + }, "stats": {}, "api": { "tag": "api", diff --git a/server/proxy/deploy-proxy.sh b/server/proxy/deploy-proxy.sh index cf30da70..d82b748a 100644 --- a/server/proxy/deploy-proxy.sh +++ b/server/proxy/deploy-proxy.sh @@ -72,6 +72,18 @@ save_server_host() { 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 } # ── install: 首次完整安装 ───────────────────── @@ -240,9 +252,18 @@ health_check() { echo " ❌ Xray: 未运行" fi - # 443端口 + # 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 @@ -271,6 +292,25 @@ update() { # 关闭3802外部端口 (订阅服务改为通过Nginx反代访问) ufw delete allow 3802/tcp 2>/dev/null || true + # 检查并修复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 restart zy-proxy-sub zy-proxy-monitor zy-proxy-guardian 2>/dev/null || true health_check diff --git a/server/proxy/service/subscription-server.js b/server/proxy/service/subscription-server.js index fac774fc..17774048 100644 --- a/server/proxy/service/subscription-server.js +++ b/server/proxy/service/subscription-server.js @@ -80,6 +80,34 @@ function getServerHost() { return '0.0.0.0'; } +// ── 获取CN中转服务器信息 ───────────────────── +// 优先级: 环境变量 > .env.keys文件 +function getCnRelayHost() { + // 1. 从环境变量读取 + if (process.env.ZY_CN_RELAY_HOST) { + return process.env.ZY_CN_RELAY_HOST; + } + + // 2. 从.env.keys文件读取 + try { + const content = fs.readFileSync(KEYS_FILE, 'utf8'); + for (const line of content.split('\n')) { + if (line.startsWith('#') || !line.includes('=')) continue; + const [key, ...vals] = line.split('='); + if (key.trim() === 'ZY_CN_RELAY_HOST') { + const val = vals.join('=').trim(); + if (val) return val; + } + } + } catch (err) { /* ignore */ } + + return null; // CN中转未配置 +} + +function getCnRelayPort() { + return parseInt(process.env.ZY_CN_RELAY_PORT || '2053', 10); +} + // ── 读取流量配额信息 ──────────────────────── function getQuotaInfo() { const quotaFile = path.join(DATA_DIR, 'quota-status.json'); @@ -116,9 +144,45 @@ function generateVlessUri(keys, serverHost) { // ── 生成Clash YAML配置 ─────────────────────── function generateClashYaml(keys, serverHost) { + const cnRelayHost = getCnRelayHost(); + const cnRelayPort = getCnRelayPort(); + + // CN中转节点 (如果已配置) + // 注: CN中转是透明TCP转发(CN:2053 → SG:443),所以Reality设置仍指向SG的配置 + // servername/public-key/short-id 与SG直连节点完全相同,因为TLS握手实际发生在SG端 + const cnProxyBlock = cnRelayHost ? ` + - name: "🇨🇳 铸渊专线-CN中转" + type: vless + server: ${cnRelayHost} + port: ${cnRelayPort} + uuid: ${keys.ZY_PROXY_UUID} + network: tcp + tls: true + udp: true + flow: xtls-rprx-vision + servername: www.microsoft.com + reality-opts: + public-key: ${keys.ZY_PROXY_REALITY_PUBLIC_KEY} + short-id: ${keys.ZY_PROXY_REALITY_SHORT_ID} + client-fingerprint: chrome` : ''; + + // 代理组中的节点列表 + const proxyList = cnRelayHost + ? ` - "🇨🇳 铸渊专线-CN中转" + - "🏛️ 铸渊专线-SG直连"` + : ' - "🏛️ 铸渊专线-SG直连"'; + + const proxyListWithDirect = cnRelayHost + ? ` - "🇨🇳 铸渊专线-CN中转" + - "🏛️ 铸渊专线-SG直连" + - DIRECT` + : ` - "🏛️ 铸渊专线-SG直连" + - DIRECT`; + return `# 铸渊专线 · ZY-Proxy Subscription # 自动生成 · ${new Date().toISOString()} # ⚠️ 请勿分享此配置 +${cnRelayHost ? `# 🇨🇳 包含CN中转节点 (国内直连广州→转发新加坡)` : ''} port: 7890 socks-port: 7891 @@ -127,7 +191,7 @@ mode: rule log-level: info proxies: - - name: "🏛️ 铸渊专线-SG" + - name: "🏛️ 铸渊专线-SG直连" type: vless server: ${serverHost} port: 443 @@ -141,23 +205,23 @@ proxies: public-key: ${keys.ZY_PROXY_REALITY_PUBLIC_KEY} short-id: ${keys.ZY_PROXY_REALITY_SHORT_ID} client-fingerprint: chrome +${cnProxyBlock} proxy-groups: - name: "🌐 铸渊专线" type: select proxies: - - "🏛️ 铸渊专线-SG" - - DIRECT +${proxyListWithDirect} - name: "🤖 AI服务" type: select proxies: - - "🏛️ 铸渊专线-SG" +${proxyList} - name: "💻 开发工具" type: select proxies: - - "🏛️ 铸渊专线-SG" +${proxyList} rules: # AI服务 diff --git a/server/proxy/setup/setup-cn-relay.sh b/server/proxy/setup/setup-cn-relay.sh new file mode 100644 index 00000000..68dc64c1 --- /dev/null +++ b/server/proxy/setup/setup-cn-relay.sh @@ -0,0 +1,255 @@ +#!/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 +# 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"; } + +# ── 参数 ────────────────────────────────────── +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 " + 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 + +# 检查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 + +# ── §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块 +NGINX_MAIN="/etc/nginx/nginx.conf" +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 测试并应用配置" + +if nginx -t 2>&1; then + systemctl reload nginx + log_info "✅ Nginx配置测试通过 · 已重载" +else + log_error "Nginx配置测试失败" + nginx -t 2>&1 + exit 1 +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 + +# 检查中转端口 +if ss -tlnp | grep -q ":${RELAY_PORT} "; then + log_info "✅ 中转端口 ${RELAY_PORT}: 监听中" +else + log_warn "中转端口 ${RELAY_PORT}: 未监听 (可能需要等待)" +fi + +# 检查HTTP +if curl -sf http://127.0.0.1/health >/dev/null 2>&1; then + log_info "✅ HTTP健康检查: 正常" +else + log_warn "HTTP健康检查: 未响应" +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 "════════════════════════════════════════" diff --git a/server/setup/setup-ssl.sh b/server/setup/setup-ssl.sh index 19d219c2..4584e42d 100644 --- a/server/setup/setup-ssl.sh +++ b/server/setup/setup-ssl.sh @@ -187,6 +187,13 @@ obtain_certificate() { } # ── §4 配置Nginx SSL ───────────────────────── +# ⚠️ 架构说明 (Reality反探测优先): +# Xray 监听 443 (外部) · VLESS+Reality协议 +# dest回落到 www.microsoft.com:443 (反探测伪装·不可改为内部端口) +# Nginx SSL 监听 8443 (外部直接访问) · 独立HTTPS服务 +# +# 如果Xray未安装 → Nginx直接监听443 (标准HTTPS) +# 如果Xray已安装 → Nginx监听8443 (避免端口冲突) configure_nginx_ssl() { local domain="$1" local cert_path="/etc/letsencrypt/live/${domain}" @@ -217,7 +224,22 @@ configure_nginx_ssl() { api_port="3800" fi - log_info "站点模式: $site_mode · API端口: $api_port" + # 确定SSL监听端口: Xray在443时用8443,否则用443 + local ssl_listen_port="443" + local ssl_listen_addr="" + if command -v xray &>/dev/null; then + ssl_listen_port="8443" + ssl_listen_addr="" + log_info "检测到Xray已安装 · Nginx SSL使用端口8443 (避免与VPN冲突)" + log_info "网站HTTPS: https://${domain}:8443" + # 开放8443端口 + ufw allow 8443/tcp comment "Nginx SSL (Xray共存)" 2>/dev/null || true + else + log_info "Xray未安装 · Nginx SSL使用标准端口443" + log_info "网站HTTPS: https://${domain}" + fi + + log_info "站点模式: $site_mode · API端口: $api_port · SSL端口: $ssl_listen_port" # 生成SSL server block local ssl_conf="${NGINX_CONF_DIR}/ssl-${domain}.conf" @@ -228,10 +250,14 @@ configure_nginx_ssl() { # 自动生成于: $(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M CST') # 证书来源: Let's Encrypt (certbot) # 证书路径: ${cert_path}/ +# +# SSL端口: ${ssl_listen_port} +# $([ "$ssl_listen_port" = "8443" ] && echo "⚠️ Xray占用443(VPN) · Nginx SSL在8443(直接访问)" || echo "标准HTTPS · 端口443") # ═══════════════════════════════════════════════ +# ─── HTTPS 服务 ─── server { - listen 443 ssl http2; + listen ${ssl_listen_port} ssl http2; server_name ${domain}; # ─── SSL证书 (Let's Encrypt) ─── @@ -336,12 +362,15 @@ server { error_log /opt/zhuyuan/data/logs/nginx-${site_mode}-ssl-error.log; } +$([ "$ssl_listen_port" = "443" ] && cat << REDIR # ─── HTTP → HTTPS 重定向 ─── server { listen 80; server_name ${domain}; - return 301 https://\$host\$request_uri; + return 301 https://\\\$host\\\$request_uri; } +REDIR +) SSLCONF log_info "SSL配置已生成: $ssl_conf" @@ -350,9 +379,15 @@ SSLCONF cp "$ssl_conf" "${NGINX_SITES_AVAILABLE}/ssl-${domain}.conf" ln -sf "${NGINX_SITES_AVAILABLE}/ssl-${domain}.conf" "${NGINX_SITES_ENABLED}/ssl-${domain}.conf" - # 从主配置中移除该域名的HTTP块(避免冲突) - # 注: 保留主配置中的HTTP块用于IP访问,SSL配置中的redirect处理域名访问 log_info "SSL配置已安装到Nginx" + if [ "$ssl_listen_port" = "8443" ]; then + log_info " HTTPS: https://${domain}:8443 (直接访问)" + log_info " HTTP: http://${domain} (端口80·正常访问)" + log_info " VPN: Xray占用443 · dest→microsoft.com (反探测)" + else + log_info " HTTPS: https://${domain} (标准端口443)" + log_info " HTTP重定向: 80 → https://${domain}" + fi # 测试Nginx配置 if nginx -t 2>&1; then @@ -404,35 +439,61 @@ HOOK # ── §6 验证HTTPS ───────────────────────────── verify_https() { local domain="$1" - log_step "§6 验证HTTPS: https://$domain" + log_step "§6 验证HTTPS: $domain" sleep 2 # 等待Nginx完全重载 + # 确定SSL端口 + local ssl_port="443" + if command -v xray &>/dev/null; then + ssl_port="8443" + fi + + log_info "验证SSL端口: $ssl_port" + + # 检查SSL端口是否监听 + if ss -tlnp | grep -q ":${ssl_port} "; then + log_info "✅ SSL端口 ${ssl_port}: 监听中" + else + log_warn "SSL端口 ${ssl_port} 未监听" + fi + # 使用curl测试HTTPS + local test_url="https://${domain}/" + if [ "$ssl_port" != "443" ]; then + test_url="https://${domain}:${ssl_port}/" + fi + local response - response=$(curl -sf -o /dev/null -w "%{http_code}" "https://${domain}/" 2>/dev/null) + response=$(curl -sf -o /dev/null -w "%{http_code}" "$test_url" 2>/dev/null) if [ "$response" = "200" ] || [ "$response" = "301" ] || [ "$response" = "302" ]; then log_info "✅ HTTPS访问正常 · 状态码: $response" + log_info " → 访问: $test_url" else log_warn "HTTPS访问状态码: ${response:-无响应}" - log_warn "这可能是因为:" - log_warn " - 网站内容尚未部署" - log_warn " - DNS传播需要时间" - log_warn " - 请稍后重试: curl -I https://$domain" + log_warn " → 尝试访问: $test_url" + log_warn " → 可能需要等待DNS传播" fi # 检查证书信息 - echo | openssl s_client -servername "$domain" -connect "${domain}:443" 2>/dev/null | \ + echo | openssl s_client -servername "$domain" -connect "${domain}:${ssl_port}" 2>/dev/null | \ openssl x509 -noout -subject -issuer -dates 2>/dev/null || true - # 测试HTTP→HTTPS重定向 - local redirect - redirect=$(curl -sf -o /dev/null -w "%{redirect_url}" "http://${domain}/" 2>/dev/null) - if echo "$redirect" | grep -q "https"; then - log_info "✅ HTTP→HTTPS重定向正常" + # 如果是标准443端口,测试HTTP→HTTPS重定向 + if [ "$ssl_port" = "443" ]; then + local redirect + redirect=$(curl -sf -o /dev/null -w "%{redirect_url}" "http://${domain}/" 2>/dev/null) + if echo "$redirect" | grep -q "https"; then + log_info "✅ HTTP→HTTPS重定向正常" + else + log_warn "HTTP→HTTPS重定向未生效 (可能需要等待DNS)" + fi else - log_warn "HTTP→HTTPS重定向未生效 (可能需要等待DNS)" + log_info "ℹ️ Xray占用443端口,HTTP不会重定向到HTTPS" + log_info " → 网站HTTP访问: http://${domain} (端口80)" + log_info " → 网站HTTPS访问: https://${domain}:${ssl_port}" + log_info " → VPN: 通过Xray端口443正常工作" fi }