Files
Private-Proxy/server_config/README.md
T
DSH Agent 9a64853db4 server_config: 新增 RRPP 订阅后端规则集下发文档(PAC → classical 规则集)
- 新增 §12:PAC 不能直接下发的原因、三个规则集产物、PAC 更新后的再生成流程、
  mihomo/Shadowrocket 两种客户端接入方式与验证口径
- 新增 rrpp-subscription/:API.md(订阅端点/method/rules 参数/规则集说明)
  与 gen_rulesets.py(PAC → 客户端规则集转换脚本,幂等、原子替换)
- method 新增 shadowrocket(.conf:远程 RULE-SET + FINAL,不猜测 REALITY 参数)
- method=clash 新增 &rules=provider|inline|global
2026-09-19 18:46:54 +08:00

639 lines
28 KiB
Markdown
Raw 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.
# RRPP 服务端部署规范
> 一套统一的跨境代理节点部署流程,适用于 Debian/Ubuntu 系 Linux。
> 每台节点运行相同的服务栈:**BBR 拥塞控制 + nginx CONNECT 隧道 + sing-box(VLESS+REALITY / Shadowsocks) + mtg(MTProto)**。
>
> 本文档为**通用规范**,所有 IP / 域名 / 端口 / 密钥均以**占位符**表示,部署时替换为实际值。
> 凡标注 `<...>` 的均为占位符,不要原样照抄。
---
## 目录
- [0. 架构总览](#0-架构总览)
- [1. 端口与服务规划](#1-端口与服务规划)
- [2. 系统基础优化(BBR 跨境丢包优化)](#2-系统基础优化bbr-跨境丢包优化)
- [3. 伪装站点页面](#3-伪装站点页面)
- [4. HTTPS 证书(acme.sh DNS-01)](#4-https-证书acmesh-dns-01)
- [5. nginx 源码编译 + CONNECT 隧道](#5-nginx-源码编译--connect-隧道)
- [6. sing-box(VLESS+REALITY / Shadowsocks)](#6-sing-boxvlessreality--shadowsocks)
- [7. mtg(MTProto 代理)](#7-mtgmtproto-代理)
- [8. 本机公网 IP 自动识别(防自代理回环)](#8-本机公网-ip-自动识别防自代理回环)
- [9. systemd 服务文件](#9-systemd-服务文件)
- [10. 启动顺序与验证清单](#10-启动顺序与验证清单)
- [11. 目录结构与文件索引](#11-目录结构与文件索引)
- [12. RRPP 订阅后端(PAC → 规则集下发)](#12-rrpp-订阅后端pac--规则集下发)
---
## 0. 架构总览
```
客户端 ──► nginx(443/<nginx端口>) ──┬─► CONNECT 隧道(任意端口, 全端口放行)
│ └─► 伪装页面(直接访问/IP访问)
│
├─► sing-box(<vless端口>) ──► VLESS+REALITY(vision)
├─► sing-box(<ss端口>) ──► Shadowsocks(2022-blake3-aes-256-gcm)
└─► mtg(<mtproto端口>) ──► MTProto(Fake TLS, Telegram 内置代理)
```
**四层代理各司其职:**
| 层 | 组件 | 协议 | 适用场景 |
|---|---|---|---|
| HTTP 隧道 | nginx | CONNECT + Basic Auth | 浏览器/curl/系统代理,全端口 WebSocket 透传 |
| 通用代理 | sing-box | VLESS+REALITY | Shadowrocket/Clash/v2rayN 客户端 |
| 兼容旧客户端 | sing-box | Shadowsocks | Shadowrocket 2.x 等不支持 REALITY 的客户端 || Telegram 专用 | mtg | MTProto Fake TLS | Telegram 内置代理,抗封锁 |
**核心密钥体系(2026-09 起全部每节点独立):**
- VLESS+REALITY:每节点**独立** UUID、REALITY 密钥对、short_id;SNI 仍全节点统一(用 `<伪装SNI域名>`,选一个高流量、TLS 指纹稳定的知名站点域名)
- Shadowsocks:每节点**独立** PSK,方法 `2022-blake3-aes-256-gcm`(SIP022,抗重放探测)
- nginx CONNECT:每节点**独立** Basic Auth 密码(客户端按节点携带凭据,无兼容性问题)
- MTProto:每台**独立 secret**(Fake TLS 类型,fronting 到 `<伪装SNI域名>`)
> 独立化的目的:单节点密钥泄漏/被污染时不波及其余节点,且换钥粒度更小。
> 代价是订阅/nodes.json 必须逐节点维护,改任一项后必须做端到端验证(见 6.3、第 10 节)。
---
## 1. 端口与服务规划
端口全部落在**同一区段**便于防火墙管理(示例用 1200–1220,可按需调整):
| 端口 | 服务 | 说明 |
|---|---|---|
| `<nginx端口>` | nginx | HTTPS 站点 + CONNECT 隧道(示例 `1205`) |
| `<vless端口>` | sing-box | VLESS+REALITY(示例 `1206`) |
| `<ss端口>` | sing-box | Shadowsocks(示例 `1207`) |
| `<mtproto端口>` | mtg | MTProto(示例 `1210`) |
> 节点 A 用 `80xx` 区段、节点 B 用 `120xx` 区段皆可,**只要同节点内不冲突、且运营商/防火墙放行**。
> 防火墙放行:`iptables -A INPUT -p tcp -m multiport --dports <nginx端口>,<vless端口>,<ss端口>,<mtproto端口> -j ACCEPT`
**域名规划:** 每个节点一个独立域名 `<节点域名>`(如 `node1.example.com`),A 记录指向节点公网 IP。acme.sh 用 DNS-01 签证书,无需 80 端口可达。
---
## 2. 系统基础优化(BBR 跨境丢包优化)
BBR(Bottleneck Bandwidth and RTT)+ fq 队列,显著改善跨境 TCP 丢包/高延迟下的吞吐。
### 2.1 启用 BBR
写入 `/etc/sysctl.d/99-bbr.conf`:
```conf
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
```
应用并验证:
```bash
modprobe tcp_bbr 2>/dev/null || true # 部分内核需手动加载模块
sysctl --system
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
# 期望输出: bbr / fq
```
> **内核要求**:BBR 需 Linux 4.9+。`default_qdisc=fq` 在部分新内核(6.12+ cloud 内核)可能无此参数,仅保留 `tcp_congestion_control=bbr` 即可,不影响 BBR 生效。
### 2.2 文件描述符
nginx worker 高并发需要提高 limit,在 `/etc/security/limits.conf` 追加:
```
* soft nofile 65535
* hard nofile 65535
```
### 2.3 时区与 NTP
代理证书校验、日志排障都依赖准确时间:
```bash
timedatectl set-timezone Asia/Shanghai
apt install -y chrony && systemctl enable --now chrony
```
### 2.4 PMTUD 黑洞缓解(tcp_mtu_probing)
**症状**:部分客户端网络上出现"TCP 能连上、但 TLS 握手稳定超时"
(如 `_ssl.c:1011 handshake operation timed out`),而服务器从其他网络探测一切正常。
原因是客户端↔服务器路径上某跳 MTU 变小且 ICMP Fragmentation Needed 被过滤
(PMTUD 黑洞),服务器发出的满尺寸大包永远到不了客户端。
开启 RFC 4821 PLPMTUD,让服务器在探测失败后自动降低 MSS 恢复通信。
写入 `/etc/sysctl.d/99-mtu-probing.conf`:
```conf
net.ipv4.tcp_mtu_probing = 1
```
```bash
sysctl --system
sysctl net.ipv4.tcp_mtu_probing # 期望输出: 1
```
> 只影响命中黑洞的连接,正常连接无感知,可安全全节点开启。
### 2.5 fail2ban(sshd 爆破防护)
节点 SSH 暴露在公网,爆破尝试是常态(实测单节点累计 3 万+ 次)。装 fail2ban 护 sshd:
```bash
apt install -y fail2ban
cat > /etc/fail2ban/jail.d/sshd.local << 'EOF'
[sshd]
enabled = true
backend = systemd
EOF
systemctl enable --now fail2ban
fail2ban-client status sshd # 确认 jail 在跑
```
> - `backend = systemd`:无 `/var/log/auth.log` 的最小化系统(无 rsyslog)必须加这行,
> 否则 fail2ban 启动报 "Have not found any log file for sshd jail"。
> - LXC 容器也能正常封禁(实测 ban/unban 链路可用)。
> - 只加 fail2ban、**不动 sshd 认证方式**(root 登录等现状保留)。
> - 如需同时护 nginx CONNECT 的 401 爆破,可再加 nginx jail(凭据够强时可不做)。
---
## 3. 伪装站点页面
直接访问节点/IP 时返回一个普通网页(而非代理错误),降低被探测的特征。
放置一个静态 `index.html` 到 `/var/www/html/index.html`:
- 任意抓取的合法 HTML 页面(博客/门户首页快照均可)
- **所有节点用同一份**(md5 一致),避免逐节点特征差异
```bash
mkdir -p /var/www/html
# index.html 为本仓库 fake-site/index.html 的拷贝
cp fake-site/index.html /var/www/html/index.html
```
---
## 4. HTTPS 证书(acme.sh DNS-01)
nginx 站点与 CONNECT 隧道共用一张证书。用 [acme.sh](https://github.com/acmesh-official/acme.sh) + DNS-01(以阿里云 DNS 为例,其他 DNS 商同理):
```bash
curl https://get.acme.sh | sh -s email=<你的邮箱>
export Ali_Key="<阿里云AccessKey>"
export Ali_Secret="<阿里云Secret>"
~/.acme.sh/acme.sh --issue --dns dns_ali -d <节点域名> --keylength ec-256
# 安装到 nginx 读取路径
mkdir -p /etc/nginx/ssl/<节点域名>
~/.acme.sh/acme.sh --install-cert -d <节点域名> --ecc \
--fullchain-file /etc/nginx/ssl/<节点域名>/fullchain.pem \
--key-file /etc/nginx/ssl/<节点域名>/privkey.pem
```
acme.sh 自带 cron 自动续期。证书路径固定为 `/etc/nginx/ssl/<节点域名>/{fullchain,privkey}.pem`。
---
## 5. nginx 源码编译 + CONNECT 隧道
标准 nginx 不自带 CONNECT 方法支持,需打 [ngx_http_proxy_connect_module](https://github.com/chobits/ngx_http_proxy_connect_module) 补丁后**源码编译**。
### 5.1 编译
```bash
apt install -y build-essential libssl-dev libpcre3-dev zlib1g-dev curl wget
NGINX_VER=1.26.3
cd /usr/local/src
curl -LO http://nginx.org/download/nginx-$NGINX_VER.tar.gz
tar xzf nginx-$NGINX_VER.tar.gz
git clone https://github.com/chobits/ngx_http_proxy_connect_module.git
cd nginx-$NGINX_VER
patch -p1 < ../ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch
./configure \
--prefix=/usr/local/nginx \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/var/run/nginx.pid \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--add-module=../ngx_http_proxy_connect_module
make -j$(nproc) && make install
```
> 若发行版 nginx 已自带 proxy_connect 补丁(极少见),可用 `apt install nginx` 跳过编译,但源码编译是通用做法。
### 5.2 Basic Auth 凭据
CONNECT 代理需要 Basic Auth(用户名/密码),**每节点独立密码**(客户端配置按节点携带凭据,无兼容性问题)。生成 htpasswd:
```bash
apt install -y apache2-utils
htpasswd -bc /etc/nginx/.htpasswd <代理用户名> '<本节点独立密码>'
chmod 644 /etc/nginx/.htpasswd # worker 以 nobody 运行,必须可读
```
> 无 apache2-utils 的机器可用 openssl 生成(sha512-crypt,nginx 同样支持):
> `echo "<用户名>:$(openssl passwd -6 '<密码>')" > /etc/nginx/.htpasswd`
> htpasswd 是**每次请求实时读取**的,改密码无需 reload nginx。
### 5.3 nginx.conf
完整配置见 [nginx/nginx.conf](nginx/nginx.conf)。**关键设计**:
- **两个 server 块共用 `<nginx端口>`**:
- 主块 `server_name <节点域名>`:证书站点 + `location /` 返回伪装页;非本域名一律 rewrite 到 `/__proxy` 转发
- fallback 块 `default_server`:其他 SNI 也接受 CONNECT 代理;本机/私网/公网 IP 直连返回伪装页,其余走 `/__proxy`
- `proxy_connect_allow all;`:**全端口** CONNECT 放行(codex/WebSocket 走任意端口均通)。若需限制,改回 `proxy_connect_allow 443 563;`
- `auth_basic`:仅 CONNECT/正向代理需要认证,`location /` 伪装页 `auth_basic off`
- **自代理回环保护**:`/__proxy` 转发时带 `X-Proxy-Loop-Guard` 头,二次进入返回 508 防死循环
- `/__local`:本机 IP 直连时剥前缀返回伪装页
- `map $host $is_local`:私网/回环段判定;公网 IP 由 cron 脚本动态写入 `local_ip_rules.conf`
### 5.4 自代理回环保护说明
正向代理转发目标若指向**本机自己**(如域名公网 IP 变更、或代理目标就是本节点),会无限循环。两层防护:
1. `map` 判定私网/回环段 + cron 维护的本机公网 IP 规则 → 直连伪装页
2. `/__proxy` 的 Loop-Guard 头兜底 → 二次进入返回 508
### 5.5 路线 B 裸 UoT relay
TUN 客户端的 UDP 会先通过认证 CONNECT 请求
`sp.v2.udp-over-tcp.arpa:443`。当前 hk2 使用的
`ngx_http_proxy_connect_module` 没有注册 `$connect_host`,因此在 fallback
server 实际接收的 CONNECT 请求上用 `$request` 正则匹配,再把连接转到
`127.0.0.1:15201`;nginx 只负责 TLS、Basic Auth 和字节流转发,不解析 UoT。relay 使用仓库中的
`[uot_relay.py](uot_relay.py)`,只监听回环地址,不需要 sing-box:
```bash
install -m 755 uot_relay.py /opt/rrpp-uot-relay/uot_relay.py
install -m 644 systemd/uot-relay.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now uot-relay
ss -lntp | grep 127.0.0.1:15201
nginx -t && systemctl reload nginx
```
客户端的 CONNECT 端口必须大于 0,当前统一使用 `443`;`15201` 是服务器回环
relay 的内部端口,不写入客户端 CONNECT 目标。nginx fallback 块使用
`proxy_connect_read_timeout 10m` 和 `proxy_connect_send_timeout 10m`,不要再同时
配置会产生 duplicate 的 `proxy_connect_data_timeout`。
---
### 5.5 UoT(UDP over TCP)支持
HTTP CONNECT 只隧道 TCP。要让 UDP 应用(DNS/游戏/QUIC 等)走代理,需 **UoT v2**
(SagerNet 专有协议,见 sing-box 文档 `configuration/shared/udp-over-tcp`):
客户端把 UDP 数据报封装进 TCP 流,经 CONNECT 隧道发到 magic 地址
`sp.v2.udp-over-tcp.arpa`,由本机 **UoT relay** 解封装后转发真实 UDP 目标。
```
客户端 TUN/UDP ──UoT v2──▶ TLS HTTP CONNECT ──▶ nginx proxy_connect
──▶ 127.0.0.1:15201(uot-relay)──▶ 真实 UDP 目标
```
**relay 部署**(Python 标准库实现,见 [uot-relay/uot_relay.py](uot-relay/uot_relay.py)):
```bash
install -d /opt/rrpp-uot-relay
install -m 755 uot-relay/uot_relay.py /opt/rrpp-uot-relay/uot_relay.py
install -m 644 uot-relay/uot-relay.service /etc/systemd/system/uot-relay.service
systemctl daemon-reload && systemctl enable --now uot-relay
```
relay 只监听 `127.0.0.1:15201`,空闲 9 分钟断开(须小于 nginx 隧道超时 10m)。
支持 UoT v2 两种模式:`isConnect=1` 固定目标、`isConnect=0` 每帧自带目标。
**nginx 侧**(模板已含,[nginx/nginx.conf](nginx/nginx.conf)):`map $request` 识别
magic 地址 → `127.0.0.1:15201`,其余 CONNECT 走 `$connect_addr` 默认解析;
`proxy_connect_address $rrpp_uot_connect_address;` 放在 **default_server 块**。
**四个实测坑(2026-09 全节点部署验证)**:
1. 该模块版本**未注册 `$connect_host` 变量**(README 有、源码无)→ 必须用
`$request` 正则匹配(任何阶段可用);
2. `proxy_connect_data_timeout` 与 `proxy_connect_read_timeout` **写同一配置槽**,
同时写报 duplicate → 长连接超时用 read/send timeout 设 10m;
3. **所有 CONNECT 请求都落在 `default_server`(fallback)块**——nginx 按请求
Host 头选虚拟主机,CONNECT 的 Host 是目标域名,永远匹配不到 `server_name`;
4. 模块**拒绝 CONNECT 端口 0**(`port < 1` 返回 400)→ 客户端连 magic 地址时
端口必须非 0(用 443 即可,relay 不读 CONNECT 端口,目标取自 UoT 帧头)。
**健壮性要求(2026-09 实测修复,别踩)**:
- relay 必须用**未连接**的 UDP socket(`sendto`/`recvfrom`)。用连接式 socket 时,
目标侧 ICMP(port unreachable 等)会变成 `ConnectionRefusedError` 在 send/recv 上抛出,
若被兜底 `except` 接住并 `stop.set()`,**一条数据报出错就杀掉整条 UoT 隧道**
(客户端表现为 "UoT TCP 流已关闭"、多条隧道同秒集体死亡)。
- 数据报级错误(`BlockingIOError`/EAGAIN、`ENETUNREACH`、`EHOSTUNREACH`、`EMSGSIZE`)
**只丢当前包**,EAGAIN 先等可写再重试;只有 TCP EOF、协议错误、空闲超时才关隧道。
- 按地址族惰性建 socket,non-connect 模式可混用 IPv4/IPv6 目标;IPv6 `recvfrom`
返回 4 元组需归一化。
- connect 模式要**预建** socket(否则主循环首轮无可 select 对象,会立刻关连接)。
**节点出口差异(实测)**:RU 节点到境外 DNS(8.8.8.8:53)UDP 成功率仅约 4/10
(俄 ISP 限制),表现为 UoT DNS 偶发超时——属该节点出口特性,非 relay 故障。
此类节点的 UDP 业务建议换用当地可达目标,或走其它节点。
**客户端协议**:认证与 TCP CONNECT 相同(TLS + Basic Auth),然后
`CONNECT sp.v2.udp-over-tcp.arpa:443 HTTP/1.1`,200 之后收发 UoT v2 帧:
请求头 `| isConnect u8 | ATYP u8 | address | port u16be |`,
connect 模式帧 `| length u16be | data |`,non-connect 帧每帧自带目标地址。
### 5.6 CONNECT 超时口径(避免客户端"卡死")
三个超时要按"客户端最没耐心"来配:
| 项 | 值 | 说明 |
|---|---|---|
| `proxy_connect_connect_timeout` | **10s** | 出站连接目标;须 < 客户端超时(lite 客户端 15s) |
| `proxy_connect_read_timeout` / `send_timeout` | 10m | 隧道空闲超时(UoT 长连接需要;注意与 data_timeout 同槽位) |
**典型故障**:目标 IP 是黑洞(不发 RST)时,若 nginx 出站超时设 60s 而客户端 15s 超时,
客户端会表现为 `The read operation timed out` 而不是收到 502 —— 看起来像"代理卡死",
实际是超时错配。2026-09 实测:改为 10s 后,黑洞目标从 >25s 降至 10.2s 返回,
正常目标(0.4~0.7s)不受影响。
**排查"CONNECT 502"的正确顺序**(重要):
1. 先看目标是否**本身就不可达**——从多个网络(国内/日本/美国/香港)实测该 IP:端口,
若都失败则是目标问题(服务下线/过期 IP/黑洞),不是代理问题;
2. 再看 nginx error.log:`Connection refused` / `timed out` / `Network is unreachable`
分别对应目标拒绝、目标黑洞、路由不可达;
3. 最后才怀疑 CONNECT 模块本身。
> 客户端日志只记 IP 不记域名时,无法判断"是不是 DNS 过期解析"。
> 建议客户端日志同时记录原始域名(实测案例:某 Dropbox 旧 IP 已失效,
> 域名现已解析到其它地址,客户端仍在拨旧 IP)。
## 6. sing-box(VLESS+REALITY / Shadowsocks)
### 6.1 安装
```bash
SINGBOX_VER=1.13.19
curl -LO https://github.com/SagerNet/sing-box/releases/download/v$SINGBOX_VER/sing-box-$SINGBOX_VER-linux-amd64.tar.gz
tar xzf sing-box-*.tar.gz
install -m 755 sing-box-$SINGBOX_VER-linux-amd64/sing-box /usr/local/bin/sing-box
```
### 6.2 配置
完整配置见 [sing-box/config.json](sing-box/config.json)。要点:
- **VLESS+REALITY**:每节点独立 UUID/密钥对/short_id,SNI 全节点统一,flow `xtls-rprx-vision`;REALITY handshake server 用 `<伪装SNI域名>:443`
- **Shadowsocks**:`2022-blake3-aes-256-gcm`(SIP022:会话子密钥 + 抗重放探测,显著优于老 AEAD `aes-256-gcm`)+ 每节点独立 PSK,独立端口(兼容不支持 REALITY 的老客户端)
- **PSK 格式要求**:密码必须是 base64 编码的 32 字节密钥(`openssl rand -base64 32`),不是任意字符串;改错格式 sing-box 启动即报错
- sing-box / Shadowrocket / mihomo 均支持 2022-blake3 系列
- 无 SOCKS5 inbound(已按需移除;如需可在 inbounds 追加 `type: socks`)
### 6.3 REALITY 密钥生成
**每台节点独立生成一套**(互不共用):
```bash
# 生成私钥/公钥(sing-box 1.13+ 内置 reality 密钥生成)
sing-box generate reality-keypair
# 输出 private_key / public_key,记录备用
# short_id 用 8 字节随机 hex:
openssl rand -hex 8
# UUID:
sing-box generate uuid
```
> 私钥只放服务端配置;公钥给客户端订阅。UUID/密钥对/short_id 每节点独立。
**公钥必须推导验证,禁止手抄。** 订阅/节点配置里的 `public_key` 与服务端
`private_key` 必须严格配对(X25519 推导),曾发生手抄错一个字符导致全节点
REALITY 握手失败(客户端报 `reality verification failed`)的事故。改配置后验证:
```bash
python3 - << 'EOF'
import base64
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives import serialization
priv = base64.urlsafe_b64decode('<REALITY_PRIVATE_KEY>' + '==')
pub = X25519PrivateKey.from_private_bytes(priv).public_key().public_bytes(
serialization.Encoding.Raw, serialization.PublicFormat.Raw)
print(base64.urlsafe_b64encode(pub).decode().rstrip('=')) # 应与下发的 public_key 逐字符一致
EOF
```
---
## 7. mtg(MTProto 代理)
Telegram 内置代理,Fake TLS 模式抗封锁。用 [mtg](https://github.com/9seconds/mtg)(Go 单二进制):
### 7.1 安装
```bash
MTG_VER=2.2.8
curl -LO https://github.com/9seconds/mtg/releases/download/v$MTG_VER/mtg-$MTG_VER-linux-amd64.tar.gz
tar xzf mtg-*.tar.gz
install -m 755 mtg-$MTG_VER-linux-amd64/mtg /usr/local/bin/mtg
```
### 7.2 生成 secret(每台独立)
```bash
mtg generate-secret --hex <伪装SNI域名>
# 输出 ee<32hex><域名hex>,以 ee 开头 = Fake TLS 类型
```
### 7.3 运行
```bash
mtg simple-run --prefer-ip=prefer-ipv4 0.0.0.0:<mtproto端口> <上面生成的secret>
```
> `--prefer-ip=prefer-ipv4`:强制 IPv4 出站,避免容器/双栈环境下 IPv6 不可达导致回退失败。
### 7.4 Telegram 客户端参数
- 服务器:`<节点域名>`
- 端口:`<mtproto端口>`
- Secret:上面生成的 hex(Telegram 自动识别 Fake TLS)
---
## 8. 本机公网 IP 自动识别(防自代理回环)
正向代理转发时,若目标 Host 是本机公网 IP,会被识别为"本机直连"返回伪装页(而非经代理转发本机,造成回环)。公网 IP 可能变动,用 cron 脚本每 5 分钟自动维护。
脚本见 [scripts/sync-nginx-local-ips.sh](scripts/sync-nginx-local-ips.sh),cron 见 [cron/sync-nginx-local-ips](cron/sync-nginx-local-ips):
```cron
*/5 * * * * root /usr/local/sbin/sync-nginx-local-ips.sh >/dev/null 2>&1
```
脚本逻辑:取 `api.ipify.org` 出站 IP + 节点域名 A 记录,比对重写 `/etc/nginx/local_ip_rules.conf`,**仅变更时** `nginx -t && systemctl reload nginx`。
---
## 9. systemd 服务文件
| 服务 | 文件 |
|---|---|
| nginx | [systemd/nginx.service](systemd/nginx.service) |
| sing-box | [systemd/sing-box.service](systemd/sing-box.service) |
| mtg | [systemd/mtg.service](systemd/mtg.service) |
| uot-relay | [uot-relay/uot-relay.service](uot-relay/uot-relay.service) |
```bash
cp systemd/*.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now nginx sing-box mtg
```
---
## 10. 启动顺序与验证清单
### 启动顺序
```bash
sysctl --system # 1. BBR
systemctl start sing-box # 2. VLESS/SS
systemctl start mtg # 3. MTProto
systemctl start nginx # 4. nginx(最后,依赖证书/伪装页/规则文件)
systemctl start uot-relay # 5. UoT relay(须在 nginx 前可用)
```
### 验证清单
```bash
# BBR
sysctl net.ipv4.tcp_congestion_control # => bbr
sysctl net.ipv4.tcp_mtu_probing # => 1(PMTUD 黑洞缓解,见 2.4)
# fail2ban
fail2ban-client status sshd # => jail 存在(见 2.5)
# sing-box
sing-box check -c /etc/sing-box/config.json
systemctl is-active sing-box
# mtg(Fake TLS:非法 ClientHello 应返回 TLS alert 0x15)
timeout 8 python3 -c "
import socket,os,struct
s=socket.connect(('<节点域名>',<mtproto端口>)) if False else socket.create_connection(('<节点域名>',<mtproto端口>),timeout=6)
s.sendall(b'\x16\x03\x01'+struct.pack('>H',100)+os.urandom(100))
print('resp:',s.recv(8).hex())" # 期望首字节 15 (TLS alert)
# UoT relay(127.0.0.1:15201 监听 + CONNECT magic 地址)
ss -tlnp | grep 15201 # => uot_relay.py 监听 127.0.0.1:15201
# nginx CONNECT(带 Basic Auth,B64 = base64('<代理用户名>:<代理密码>'))
B64=$(printf '<代理用户名>:<代理密码>' | base64 -w0)
curl -sk --proxy-insecure -x https://<节点域名>:<nginx端口> \
--proxy-header "Authorization: Basic $B64" \
-o /dev/null -w '%{http_code}\n' http://example.com/ # => 200
# 伪装页(直连 IP/域名,无认证)
curl -sk https://<节点域名>:<nginx端口>/ -o /dev/null -w '%{http_code}\n' # => 200
```
---
## 11. 目录结构与文件索引
```
server_config/
├── README.md ← 本文档
├── nginx/
│ ├── nginx.conf ← 完整 nginx 配置(占位符版)
│ ├── local_ip_rules.conf.example ← 本机 IP 规则文件示例(cron 自动生成,勿手改)
│ └── conf.d/ ← 预留:额外站点配置
├── uot-relay/
│ ├── uot_relay.py ← UoT v2 relay(UDP over TCP 解封装)
│ └── uot-relay.service ← systemd unit
├── sing-box/
│ └── config.json ← sing-box 配置(VLESS+REALITY + SS,占位符版)
├── systemd/
│ ├── nginx.service
│ ├── sing-box.service
│ └── mtg.service
├── scripts/
│ └── sync-nginx-local-ips.sh ← 本机公网 IP 规则自动维护
├── cron/
│ └── sync-nginx-local-ips ← cron 定义(每 5 分钟)
├── rrpp-subscription/ ← RRPP 订阅后端(bj,见 §12)
│ ├── API.md ← 订阅端点 / method / 分层 / 规则集文档
│ └── gen_rulesets.py ← PAC → 客户端规则集转换脚本
└── fake-site/
└── index.html ← 伪装站点页面(所有节点共用同一份)
```
> 旧的 `traffic-monitor/`、`pac/`、`conf.d/*.conf` 已下线或迁移,不再维护,已从本目录移除。
---
## 12. RRPP 订阅后端(PAC → 规则集下发)
订阅服务运行在 **bj**(`C:/BtSoft/nginx/others/backEnd/api-rrrosmontis-icu/RRPP/`,nssm 服务 `RRPPSubBackend`,监听 `127.0.0.1:5010`,由 nginx `api.rrrosmontis.icu` 反代)。
完整端点说明见 `rrpp-subscription/API.md`(bj 上同名文件为主副本,两处内容保持一致)。
### 12.1 PAC 为什么不能直接下发
PAC 是浏览器/系统用的 JS 自动配置,**mihomo/Clash 与 Shadowrocket 都读不了**。两者唯一共同能吃的格式是
「每行自带规则类型的规则行文本」(Clash 的 `behavior: classical`):
- mihomo → `rule-providers`(`behavior: classical` + `format: text`)
- Shadowrocket → `RULE-SET,<url>,<策略>`(官方手册:规则集内容需包含规则类型)
因此由后端 `gen_rulesets.py` 把 PAC 数据转成三个 `.list`,两种客户端共用同一份文件。
### 12.2 规则集产物
| 规则集 | 文件(`https://cdn.rrrosmontis.icu/RRPP/PAC/`) | 规则数 | 策略 |
|---|---|---|---|
| `rrpp-proxy` | `rrpp-proxy.list` | 3943(`DOMAIN` 539 + `DOMAIN-SUFFIX` 3404) | `RRPP` / `PROXY` |
| `rrpp-direct` | `rrpp-direct.list` | 16(`DOMAIN-SUFFIX`) | `DIRECT` |
| `rrpp-cn-ip` | `rrpp-cn-ip.list` | 7456(`IP-CIDR`) | `DIRECT` |
顺序(与 PAC 语义一致):强制代理 → 直连种子 → 中国 IP 直连 → `MATCH` 全代理。
### 12.3 维护:PAC 更新后重新生成
```bash
# bj 上执行(Windows:C:/BtSoft/nginx/others/htmlResources/cdn/RRPP/PAC/ 为生成目录,同时即 CDN 根)
python gen_rulesets.py # 幂等:内容无变化则不写盘;原子替换
python gen_rulesets.py --check # 只预览差异,不写盘
```
产物文件名与 URL 固定,**订阅端与客户端均无需改动**:mihomo 按 `interval: 86400` 自动更新,
Shadowrocket 在「配置 → 规则集URL」中自动更新。
### 12.4 客户端接入
```bash
# mihomo / Clash Verge:规则走 rule-providers(YAML 约 2.4KB)
...&method=clash
# 不认 rule-providers,或要「导入一次到位」:规则内联(约 440KB)
...&method=clash&rules=inline
# Shadowrocket:只下发规则([General] + 远程 RULE-SET + FINAL,不含节点)
...&method=shadowrocket
# 兜底:全流量走代理(旧行为)
...&method=clash&rules=global
```
> `method=shadowrocket` 的 `.conf` 不写 `[Proxy]` 段——Shadowrocket 的 REALITY 参数写法官方未文档化,不猜测;
> 策略 `PROXY` 指向首页当前选中的节点,节点用 `clash`/`v2ray` 订阅导入。
> 想在小火箭里「一次导入节点+规则」,用 `method=clash&rules=inline`。
### 12.5 验证口径
- 生成:11415 条规则(`regexp:` 跳过 7、非法 1)
- CDN:三个 URL HTTP 200,md5 与本地一致(`806808b8` / `8f89397e` / `3e75093d`)
- mihomo v1.19.31 真实内核:`-t` 通过;运行时 `GET /providers/rules` 报 `3943 / 16 / 7456`(`behavior=Classical`),
落地文件字节数与生成一致;`rules=inline` 亦通过 `-t`
- Shadowrocket 为 iOS 应用,未做实机验证(依官方手册 `RULE-SET` 与 Clash YAML 兼容说明实现)