15d91b2b7a
End-to-end matrix with real xray v26.3.27 + caddy: - server with extra padding + plain client: 400 (direct AND via caddy) - server without extra + plain client: 204 (direct AND via caddy, TLS h2) Padding placement/key names must match on both ends, but share links cannot carry these params and mainstream clients can't configure them — so a server-side-only extra block rejects every real client. Removed from the template along with XPADDING_HEADER/XPADDING_KEY. Co-Authored-By: Claude <noreply@anthropic.com>
214 lines
16 KiB
Markdown
214 lines
16 KiB
Markdown
# 设计文档:setup_caddy_xray_hysteria.sh
|
||
|
||
日期:2026-08-23
|
||
状态:已获用户批准(含 XHTTP 增量)
|
||
|
||
## 目标
|
||
|
||
新增单一自包含 bash 脚本 `setup_caddy_xray_hysteria.sh`,在一台 Debian/Ubuntu VPS 上自动部署:
|
||
|
||
- **Caddy**(cloudsmith apt 源):监听 `127.0.0.1:8003`(PROXY protocol wrapper,接收 Xray REALITY 回落流量),提供伪装站点(反代 engineersblog.net)、WebSocket 路径 `/scilad` → Xray:54442、XHTTP 路径 → Xray:8080、portainer 路径 → localhost:9000;`http://` 站跳转 https:443
|
||
- **Xray**(官方 install-release.sh):443 端口 VLESS+REALITY(xtls-rprx-vision,dest 8003 回落 Caddy,xver 1 发 PROXY protocol);54442 端口 VLESS+WS(仅 127.0.0.1);8080 端口 VLESS+XHTTP(仅 127.0.0.1)
|
||
- **Hysteria2**(get.hy2.sh):8443/UDP,Cloudflare DNS-01 申请 `*.BASE_DOMAIN` 泛域名证书,密码认证,伪装代理
|
||
- **nftables**:UDP 20000-30000 端口跳跃 redirect 到 8443
|
||
|
||
## 架构与执行流程
|
||
|
||
分阶段函数,按序执行:
|
||
|
||
```
|
||
preflight → collect_params → install_caddy → install_xray →
|
||
install_hysteria → setup_nftables → validate_configs →
|
||
enable_services → print_summary
|
||
```
|
||
|
||
- `set -euo pipefail`;脚本注释用中文(仓库约定)
|
||
- 参数:**环境变量优先,`/dev/tty` 交互兜底**(支持 `wget|bash`,同 setup_warp_zerotrust.sh 约定);无 tty 且无 env 时,无默认值的必填项报错退出
|
||
- 模板以 **quoted heredoc + 独立占位符 token**(`__DOMAIN__` 等)内嵌,写出时 `sed`(`|` 分隔符)统一替换;随机值限定 hex/base64url 字符集,无 sed 特殊字符风险
|
||
- `ACTION=install`(默认)/ `ACTION=uninstall`
|
||
- 组件开关:`SKIP_CADDY` / `SKIP_XRAY` / `SKIP_HYSTERIA` / `SKIP_NFTABLES`;跳过有依赖关系的组件时打印警告(如 REALITY 回落依赖 Caddy)
|
||
- 供应链说明:本脚本按用户手工命令添加 cloudsmith 第三方 apt 源并管道执行 Xray/hy2 官方安装脚本——与 setup_warp_zerotrust.sh 的"仅发行版签名源"约束不同,头部注释明确说明
|
||
|
||
## 变量清单
|
||
|
||
| 变量 | 来源 | 默认值 | 去向 |
|
||
|---|---|---|---|
|
||
| `DOMAIN` | 用户输入(必填,正则校验) | 无 | Caddy 站点、REALITY serverNames |
|
||
| `BASE_DOMAIN` | 推导 `${DOMAIN#*.}` | — | Hysteria 泛域名 `*.BASE_DOMAIN` |
|
||
| `EMAIL` | 输入可覆盖 | `admin@BASE_DOMAIN` | Caddy 全局 email + hy2 ACME email |
|
||
| `CF_API_TOKEN` | 输入(hy2 必填,无默认) | 无 | hy2 DNS-01 |
|
||
| `XRAY_UUID` | 自动生成可覆盖 | `xray uuid`(回退 `/proc/sys/kernel/random/uuid`) | REALITY inbound |
|
||
| `XRAY_WS_UUID` | 同上 | 同上 | WS inbound |
|
||
| `XRAY_XHTTP_UUID` | 同上 | 同上 | XHTTP inbound |
|
||
| `XRAY_PRIVATE_KEY` | 自动生成可覆盖 | `xray x25519`,解析兼容新旧输出(`grep -iE 'private'` 取私钥;公钥取 `public` 或新版 `password` 行) | REALITY |
|
||
| `XRAY_SHORT_ID` | 自动生成可覆盖 | 随机 8 位 hex(`/dev/urandom`) | REALITY shortIds |
|
||
| `XHTTP_PATH` | 自动生成可覆盖 | `/` + 8 位随机 hex | XHTTP inbound path + Caddy 路径匹配 |
|
||
| `XHTTP_PORT` | env | `8080` | XHTTP inbound(仅 127.0.0.1) |
|
||
| `XPADDING_HEADER` / `XPADDING_KEY` | 自动生成可覆盖 | 随机稀有字符串(不用 X-Cache 等常见名) | XHTTP `extra` padding 混淆 |
|
||
| `HY2_PASSWORD` | 自动生成可覆盖 | `openssl rand -hex 16` | hy2 auth |
|
||
| `HY2_MASQUERADE_URL` | 输入可覆盖 | `https://${DOMAIN}/` | hy2 masquerade |
|
||
| `CDN_DOMAIN` | env/输入(可选,默认空=不启用) | 空 | Caddy 第二个站点块(橙云 CDN 回源专用) |
|
||
| `DIST_UPGRADE` | env | 默认跳过;设 1 执行 `apt-get dist-upgrade -y` | — |
|
||
|
||
**凭据持久化**:最终参数写入 `/etc/caddy-xray-hy2/setup.env`(mode 600),重跑时自动 source 作为默认值,避免重跑重新生成 UUID 导致已分发客户端失效。
|
||
|
||
## 组件细节
|
||
|
||
### Caddy
|
||
- cloudsmith gpg key → `/usr/share/keyrings/caddy-stable-archive-keyring.gpg`;源 → `/etc/apt/sources.list.d/caddy-stable.list`(覆盖写同路径,天然幂等)→ `apt update && apt install -y caddy`
|
||
- 写 `/etc/caddy/Caddyfile`:替换 `__DOMAIN__`、`__EMAIL__`、`__XHTTP_PATH__`、`__XHTTP_PORT__`;模板原样保留 portainer 路径、`/scilad` WS 路径、php_fastcgi、伪装反代 engineersblog.net
|
||
- XHTTP 路由新增:`@xhttppaths path __XHTTP_PATH__ __XHTTP_PATH__/*` + `reverse_proxy @xhttppaths 127.0.0.1:__XHTTP_PORT__`(不 strip path)
|
||
- **可选 CDN 站点块**:`CDN_DOMAIN` 非空时,Caddyfile 追加第二个站点块——只含 XHTTP 路径、`/scilad` WS 路径与同样的伪装反代。该域名应配为橙云:CF 边缘 → 回源 443 → Xray REALITY(CF 不是 REALITY 客户端)→ 无差别回落 Caddy → 按 SNI 进入该站点块。证书用 ACME **HTTP-01**(CF 透传 challenge 请求;TLS-ALPN 不可过 CF,Caddy 自动回退)。Xray 侧零改动。REALITY 与 CDN 在同一主机名上互斥(REALITY 须灰云直连,CDN 须橙云),所以 CDN 域名必须独立;注意 CDN 不隐藏源站 IP(REALITY 域名本就直连暴露),其价值是 VPS IP 被封时的备用通道
|
||
|
||
### Xray
|
||
- `bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install`
|
||
- 写 `/usr/local/etc/xray/config.json`,三个 inbound:
|
||
1. 443 VLESS+REALITY+vision(dest `8003` 原样保留,由 `xray run -test` 校验兜底;xver 1;serverNames=`__DOMAIN__`;privateKey/shortIds/uuid 替换)——修正模板第 15 行 `""uuid-uuid` 多引号笔误
|
||
2. 54442 VLESS+WS,仅 127.0.0.1(保留兼容旧客户端)
|
||
3. `__XHTTP_PORT__`(默认 8080)VLESS+XHTTP,仅 127.0.0.1,`mode: auto`,path=`__XHTTP_PATH__`;CDN 时代 padding 混淆(XTLS/BBS #25 调研结论,必须放 `extra` 才生效):
|
||
```json
|
||
"extra": {
|
||
"xPaddingObfsMode": true,
|
||
"xPaddingMethod": "tokenish",
|
||
"xPaddingPlacement": "queryInHeader",
|
||
"xPaddingHeader": "__XPADDING_HEADER__",
|
||
"xPaddingKey": "__XPADDING_KEY__"
|
||
}
|
||
```
|
||
`__XPADDING_HEADER__`/`__XPADDING_KEY__` 自动生成(随机稀有字符串,不用常见的 X-Cache),可覆盖
|
||
- outbounds/routing/dns 保留模板原样(含 geoip:cn 阻断、私有网段阻断、bittorrent 阻断等)
|
||
|
||
### Hysteria2
|
||
- `bash <(curl -fsSL https://get.hy2.sh/)` 安装(自带 hysteria 用户与 hysteria-server.service)
|
||
- 写 `/etc/hysteria/config.yaml`:`acme.domains: ["*.BASE_DOMAIN"]`、email、cloudflare_api_token、`dir: /etc/hysteria/acme_certs`、password、masquerade proxy url
|
||
- `chown -R hysteria:hysteria /etc/hysteria/`(ACME 证书目录需可写)
|
||
|
||
### nftables
|
||
- `mkdir -p /etc/nftables` → 覆盖写 `/etc/nftables/hysteria.nft`(`table inet hysteria_nat`,udp dport 20000-30000 redirect to :8443)
|
||
- `/etc/nftables.conf`:先 grep 检查再追加 `include "/etc/nftables/hysteria.nft"`(幂等);文件不存在则新建
|
||
- `systemctl enable --now nftables`
|
||
|
||
## 校验与错误处理
|
||
|
||
- 启动前校验:`caddy validate --config /etc/caddy/Caddyfile`、`xray run -test -config /usr/local/etc/xray/config.json`;任一失败即中止,不 enable 带病服务;hy2 无离线校验命令,靠启动后 `systemctl is-active` 检查
|
||
- 启动顺序无关依赖:Caddy(127.0.0.1:8003)与 Xray(443)互相独立,回落连接由 Xray 发起
|
||
- 本脚本不改 resolv.conf,无需 cleanup trap
|
||
- 自检(结尾):`curl -skI https://${DOMAIN}` 验证 REALITY→Caddy 回落伪装、`ss -lun` 查 UDP 8443、`nft list table inet hysteria_nat`、三服务 `is-active`
|
||
|
||
## 卸载(ACTION=uninstall)
|
||
|
||
1. `systemctl disable --now` caddy / xray / hysteria-server(best-effort)
|
||
2. 删 `/etc/caddy/Caddyfile`、`/usr/local/etc/xray/config.json`、`/etc/hysteria/config.yaml`
|
||
3. 删 `/etc/nftables.conf` 中的 include 行(sed 精确删除)、删 `hysteria.nft`、`nft delete table inet hysteria_nat`(best-effort)
|
||
4. `PURGE=1` 时:`apt purge caddy`、删 cloudsmith 源与 keyring、Xray 官方脚本 `remove --purge`、hy2 官方脚本 `--remove`
|
||
5. 默认保留 `/etc/caddy-xray-hy2/setup.env`;`KEEP_ENV` 未设且 PURGE=1 时删除
|
||
|
||
## 输出
|
||
|
||
结尾打印客户端参数汇总表 + 分享链接:
|
||
|
||
- REALITY:`vless://XRAY_UUID@DOMAIN:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=DOMAIN&fp=chrome&pbk=公钥&sid=shortId&type=tcp#...`
|
||
- WS(旧):`vless://WS_UUID@DOMAIN:443?...&type=ws&path=/scilad#...`
|
||
- XHTTP:`vless://XHTTP_UUID@DOMAIN:443?...&type=xhttp&path=XHTTP_PATH&mode=auto#...`(备注:如需套 CF CDN 把该域名改橙云;REALITY 必须灰云)
|
||
- XHTTP(CDN 版,仅 CDN_DOMAIN 非空时):同上但地址/SNI 换为 `CDN_DOMAIN`
|
||
- Hysteria2:`hysteria2://HY2_PASSWORD@IP:8443/?mport=20000-30000&sni=BASE_DOMAIN#...`(公网 IP 用 curl best-effort 探测)
|
||
|
||
## 调研结论(2026-08,影响设计的部分)
|
||
|
||
1. **REALITY 不能走 CDN 是原理性限制**(客户端须直连校验被偷的目标证书;CDN 边缘终止 TLS 后证书是 CF 的),REALITY 域名须保持灰云。参照 chika0801/Xray-examples#49。同一主机名上 REALITY 与 CDN 前置互斥 → CDN 通道必须用独立的第二个子域名(可选 `CDN_DOMAIN`)
|
||
2. **WS 是上一代方案**,XHTTP 是 XTLS 官方继任者("XHTTP: Beyond REALITY",Xray-core discussion #4113);WS inbound 保留仅为兼容
|
||
3. **2026 年 CF 开始自动检测 XHTTP 的 x_padding 特征并发滥用警告**(XTLS/BBS #25、Xray-core #5967/#5414):缓解=最新 Xray(修 UA)+ padding 混淆放 `extra` + 稀有自定义 padding header/key + 非根路径;可选 VLESS-Enc(`mlkem768x25519plus.random`)与客户端 ECH,本设计默认不开 Enc(客户端兼容性),在输出中提示
|
||
|
||
## 测试
|
||
|
||
- `bash -n setup_caddy_xray_hysteria.sh` + `shellcheck`
|
||
- 真机验证只能在目标 VPS:检查三服务 active、`curl -skI https://DOMAIN` 拿到伪装站响应、hy2 UDP 8443 监听、nft 表生效、ACME 证书签出(caddy HTTP-01 走 80 端口,hy2 走 CF DNS-01)
|
||
|
||
## 明确不做(YAGNI)
|
||
|
||
- 不装 php-fpm / portainer(模板路径保留,未使用时 Caddy 仅在该路径被访问时才报 502)
|
||
- 不做 hy2 官方安装脚本之外的安装渠道
|
||
- 不做多用户/多 UUID 管理
|
||
- 不自动改 CF DNS 记录(域名解析与灰/橙云状态由用户自行配置,脚本输出提示)
|
||
|
||
## 修订(2026-08-24):hy2 证书默认改 HTTP-01
|
||
|
||
原设计 hy2 只有 Cloudflare DNS-01 通配符。修订为 `HY2_CERT_MODE` 二选一:
|
||
|
||
- `http`(**默认**):hy2 内置 certmagic 走 HTTP-01,`listenHost: 127.0.0.1` +
|
||
`http.altPort: 9180`(仅回环,不开公网端口),Caddy 80 端口块把
|
||
`/.well-known/acme-challenge/` 反代给它(Caddy 只拦截自己正在签的 token,
|
||
互不影响)。签 DOMAIN 非通配证书;certmagic 自动续期且新证书对新握手热生效。
|
||
不再需要 CF_API_TOKEN。
|
||
- `dns`(备选):维持原 Cloudflare DNS-01 通配符,需 CF_API_TOKEN。
|
||
|
||
被否决的方案:
|
||
- ACME TLS-ALPN:CA 只连 TCP 443(改 altPort 无效,需 SNI proxy),443 是 Xray
|
||
REALITY 的 → 不可行。
|
||
- `tls:` 复用 Caddy 证书:hy2 LocalCertificateLoader 按 mtime 热加载(新连接生效,
|
||
无需重启),技术上可行;但 /var/lib/caddy 为 0700 caddy:caddy,需 systemd.path
|
||
监听+复制+chown,Caddy 无内置续期 hook(events exec 是第三方插件),存储路径还随
|
||
CA 目录名变化。部件最多,不选。
|
||
|
||
## 调研结论补充(2026-08-24,近半年动态评估)
|
||
|
||
**Xray-core v26.2→v26.7**:
|
||
- v26.3.27 Xray 原生支持完整 Hysteria2 入站/传输层——本架构跑独立 hy2 进程,不受影响
|
||
- REALITY:非 443 端口与"偷苹果"目标会被官方警告(易封 IP);本架构 443 + 自有 Caddy 回落,合规。新版服务端自动探测 target maxUselessRecords,无需配置
|
||
- TLS `allowInsecure` 已移除并于 2026-06-01 硬禁用(GFW MITM 能力背景);本脚本分享链接本就不含该参数
|
||
- XHTTP CDN 检测绕过选项(PR #5414)即 extra 块 xPadding 混淆,已实现;UA 改动态 Chrome,服务端零配置受益
|
||
- VLESS 后量子加密(mlkem768x25519plus)与 TLS ECH 已可用,但要求客户端同步升级;REALITY 本身不暴露真实 SNI,默认不开,属可选增强
|
||
|
||
**Hysteria2 2.8→2.12**:
|
||
- 2.8.0 内置端口范围监听(listen :20000-50000,自动配防火墙)——但主端口会变成范围首端口、需 root/CAP_NET_ADMIN;维持现有手动 nftables(文档认可的方式,主端口 8443 保留)
|
||
- 2.8.0 拥塞控制可配(bbr/reno + 三档 profile),默认 bbr standard 已合理
|
||
- 2.9.2 Gecko 混淆(实验性)与 2.10.0 ECH:均需客户端同开/额外密钥分发,默认不开
|
||
- 2.11.0 客户端 Chrome QUIC 指纹拟态默认开启;2.12.1 服务端 stateless reset 加速移动端重连——均为零配置受益
|
||
- 2.8.2/2.9.2 重要安全修复:安装脚本始终拉最新版,天然覆盖
|
||
- 2.12.0 mimic(XDP 伪装 TCP):UDP 全封场景专用,需内核模块+root,默认不加
|
||
|
||
**AnyTLS**:2025 年新协议(sing-box/mihomo 系),主打低开销与 TLS-in-TLS 缓解,
|
||
与 REALITY/hy2 是并列选项而非替代;Xray-core 不支持其服务端,引入需新增
|
||
sing-box 组件与供应链。现有 REALITY(隐蔽)+ hy2(速度)+ XHTTP(CDN 备用)
|
||
覆盖已完整,不加入。
|
||
|
||
**总评**:服务端配置无需功能性更新;保持安装最新版即可获得全部安全修复。
|
||
|
||
---
|
||
|
||
## 修订(2026-08-24):auto_https disable_redirects —— 第三层 challenge 拦截
|
||
|
||
上线后 hy2 HTTP-01 仍失败,LE 报 `Invalid response from https://DOMAIN/...: 404`
|
||
(https + 无端口 + 404 = 经 443 落到伪装反代)。根因:Caddy 为**它自己管理证书的域名**
|
||
(DOMAIN/CDN_DOMAIN)在运行时向 80 端口服务器注入自动 HTTP→HTTPS 重定向路由,
|
||
该路由带 host 匹配器、排在 catch-all `http://` 站点块的路由之前,且**不排除**
|
||
ACME challenge 路径——Caddy 自己的 token 由更靠前的拦截器应答,hy2 的 token
|
||
被 308 到 443。此路由在 `caddy adapt` 静态输出中不可见,只抽取 `http://` 块的
|
||
活测试无法复现;必须带一个被管理证书的站点块才触发。
|
||
|
||
修复:全局块加 `auto_https disable_redirects`(证书管理不受影响;80 端口重定向
|
||
本就由 `http://` 块手动接管)。已用真实 caddy v2.11.4 双向验证:无此行 → challenge
|
||
308(精确复现线上);有此行 → challenge 200 来自 hy2、其他路径 301。
|
||
活测试已改为抽取全局块+http 块并附加 `tls internal` 受管站点块,永久防回归。
|
||
|
||
## 修订(2026-08-24 之二):handle_path 剥离 /scilad 导致 WS 全挂;ALPN 去掉 h3
|
||
|
||
线上实测 WS 握手(完整 Upgrade 头)直连/CDN 均 404:原模板的
|
||
`handle_path /scilad{...}` 会剥离路径前缀,xray wsSettings.path=/scilad 收到 `/`
|
||
直接 404。改为站点级 `@websockets` 匹配器(path + Upgrade 头)+ reverse_proxy
|
||
保路径转发,CDN 块删除冗余有害的 `handle_path /scilad*`(它还把 `@wspaths`
|
||
变成死代码)。已用真实 caddy + mock upstream 验证:101 且上游收到 `/scilad`。
|
||
|
||
同批:Caddy tls 块 `alpn h3 h2 http/1.1` 是从直Face模板继承的隐患——本监听器只收
|
||
Xray 回落的 TCP 流量,实测客户端 offer h3 时 Caddy 在 TCP 上协商出 h3 直接挂死,
|
||
改为 `alpn h2 http/1.1`。所有 TLS 客户端链接显式带 fp=chrome。
|
||
|
||
## 修订(2026-08-24 之三):移除 XHTTP extra/xPadding 块(客户端全部 400)
|
||
|
||
线上 XHTTP 直连+CDN 全挂。本机用真实 xray v26.3.27 服务端+客户端+caddy 做
|
||
端到端对照实验:服务端带 extra(xPaddingObfsMode/tokenish/queryInHeader)
|
||
时,无 padding 参数的客户端(=所有从分享链接导入的客户端,链接无法携带这些
|
||
参数)直连 xray 也收到 **400**;去掉 extra 后直连与经 caddy(TLS h2)全链路
|
||
均 204 通过。结论:padding 的位置/键名必须两端一致,服务端单方面开启即破坏
|
||
互操作,故从模板删除(XPADDING_HEADER/XPADDING_KEY 一并移除)。
|