Merge pull request #239 from qinfendebingshuo/copilot/fix-network-access-issue

fix: restore Reality dest to external target, add CN relay architecture
This commit is contained in:
冰朔 2026-03-31 16:56:34 +08:00 committed by GitHub
commit 37f790342b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 633 additions and 88 deletions

View File

@ -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

View File

@ -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*

View File

@ -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
# ═══════════════════════════════════════════════════════════════

View File

@ -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",

View File

@ -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

View File

@ -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服务

View File

@ -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 <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"; }
# ── 参数 ──────────────────────────────────────
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
# 检查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 "════════════════════════════════════════"

View File

@ -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
}