# 设计文档: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 一并移除)。