VPS 开荒配置脚本集

新购 VPS 的一键初始化脚本集合。每个脚本都是单文件、自包含、无外部依赖的 bash, wget 下来直接 sudo bash 就跑;参数全部支持环境变量传入(支持 wget|bash 管道), 缺失的凭据自动生成并落盘保存,重跑幂等、不会失效已分发的客户端配置。

索引

选型总览

脚本 用途 适用场景
setup_warp_zerotrust.sh 给 IPv6-only VPS 加 Cloudflare WARP IPv4 出口(纯 WireGuard,无官方客户端) 没有 IPv4 的廉价 VPS 需要访问 IPv4 网络
setup_caddy_xray_hysteria.sh Caddy + Xray(REALITY/WS/XHTTP)+ Hysteria2 + nftables 端口跳跃 自建代理全家桶:隐蔽直连 + CDN 备用 + UDP 加速
setup_cdn_bypass_tproxy.sh 本机到 CDN 的流量透明代理到上游 xray 节点 Google Cloud 等封锁/计费 CDN 出口的 VPS
migrate_existing_stack.sh 把手动部署的 Caddy+Xray+Hy2 栈纳入 setup.env 管理(凭据保留、客户端不断线) 老机器想纳入 ACTION=upgrade 统一升级体系
flowchart TD
  A["新购 VPS"] --> B{"缺什么?"}
  B -->|"IPv6-only<br/>没有 IPv4 出口"| C["setup_warp_zerotrust.sh<br/>WARP WireGuard IPv4 出口"]
  B -->|"要自建代理全家桶"| D["setup_caddy_xray_hysteria.sh<br/>Caddy + Xray + Hy2 + 端口跳跃"]
  B -->|"CDN 出口被封/计费<br/>(如 Google Cloud)"| E["setup_cdn_bypass_tproxy.sh<br/>CDN 流量透明代理到上游节点"]
  D -.->|"装了全家桶后才发现<br/>CDN 被封 (证书无法续期)"| E

共同约定:Debian/Ubuntu;中文注释;ACTION=uninstall 卸载;凭据仅存本机 600 权限文件。


setup_warp_zerotrust.sh

给 IPv6-only 的 VPS 加一个 Cloudflare WARP 的 IPv4 出口 —— 纯 WireGuard 实现, 不装官方 WARP 客户端(不引入任何桌面组件),全部软件只走 apt 官方签名源。

flowchart LR
  subgraph VPS["IPv6-only VPS"]
    WG["wg0 WireGuard<br/>AllowedIPs 0.0.0.0/0"]
    V6["原生 IPv6 协议栈"]
  end
  V6 -->|"注册 / 隧道握手走 IPv6"| API["Cloudflare 注册 API<br/>IPv6 anycast"]
  WG -->|"IPv4 流量"| WARP["Cloudflare WARP 骨干网"]
  V6 -->|"IPv6 流量不接管"| NET["原生 IPv6 出口"]

两种模式:

模式 说明 设备管理
免费版(默认) 匿名注册 consumer WARP,拿到 IPv4 出口 无
Zero Trust 团队版 注册为组织的团队设备,出口走 Cloudflare 骨干网 设备出现在 Zero Trust 仪表盘,受 Gateway L3 策略管控

快速开始

# 免费版: 交互提示出现时直接回车
wget -qO- https://git.henhaoji.site/eli/vpsetup/raw/branch/main/setup_warp_zerotrust.sh | bash

# 团队版: 填团队名 + Service Token 凭据即可(见下方配置步骤)

可选环境变量: ALLOWED_IPS(默认 0.0.0.0/0)、MTU(默认 1280)、 DNS64_SERVERS(替换安装期临时 DNS64)、ACTION=delete(注销设备并卸载)。


Zero Trust 仪表盘配置(2026 年最新路径)

⚠️ 网上老教程写的 Settings > WARP Client > Device enrollment permissions 是旧版导航,现在找不到很正常。以下是 2026 年现行界面路径 (依据官方文档 Deploy the Cloudflare One Client on headless Linux machines)。

第 0 步:开通 Zero Trust(已有组织可跳过)

  1. 登录 Cloudflare 仪表盘,左侧进入 Zero Trust
  2. 首次进入会引导创建组织、设置 team name(团队名,全局唯一)
  3. 选 Free 计划。注意: 免费计划也要求绑定支付方式(信用卡/PayPal) 做验证,不会收费;Free 计划上限 50 台设备

第 1 步:确认团队名(team name)

  • 路径: Zero Trust > Settings,页面上的 Team name 即你的团队名, 下面的 Team domain 是由它生成的注册域名(<团队名>.cloudflareaccess.com)
  • 脚本的 CF_TEAM 填 name 或完整 domain 都可以,内部自动归一化

第 2 步:创建 Service Token(拿 Client ID / Secret)

  1. 路径: Zero Trust > Access controls > Service credentials > Service Tokens
  2. 点 Create Service Token
  3. 起个名字(比如 vps-warp),选择 Service Token Duration(有效期)
    • 到期只是不能再注册新设备,已注册设备不受影响;建议选一年或更长
  4. 点 Generate token,得到:
    • Client ID,形如 88bf3b6d86161464f6509f7219099e57.access
    • Client Secret,64 位十六进制,只显示这一次,务必当场复制

第 3 步:允许用 Service Token 注册设备(关键,漏了会 403)

  1. 路径: Zero Trust > Team & Resources > Devices,切到 Management 标签页
  2. 找到 Device enrollment permissions,点 Manage
  3. 在 Policies 标签页点 Create new policy(会新开一个标签页):
    • Action 必须选 Service Auth —— ⚠️ 选成 Allow 是常见错误: Allow 是交互式身份登录策略,service token 永远拿不到注册 JWT, 脚本会报 no enrollment JWT returned (HTTP 302, Location: .../cdn-cgi/access/login/...)
    • 规则: Include → Selector 选 Service Token → Value 选你刚建的 token 名 (想省事也可以选 Any Access Service Token)
    • 保存策略
  4. 回到 Device enrollment permissions,把刚创建的策略添加进去,点 Save

第 4 步:在 VPS 上运行

交互式(推荐):

wget -qO- https://git.henhaoji.site/eli/vpsetup/raw/branch/main/setup_warp_zerotrust.sh | bash
# Zero Trust 团队名(回车跳过): myteam
# Service Token Client ID (形如 xxxx.access): 88bf...access
# Service Token Client Secret (输入不显示): ********

或非交互(环境变量齐全时不会有任何提问):

wget -qO /tmp/zt.sh https://git.henhaoji.site/eli/vpsetup/raw/branch/main/setup_warp_zerotrust.sh
CF_TEAM=myteam \
CF_ACCESS_CLIENT_ID=88bf...access \
CF_ACCESS_CLIENT_SECRET=bdd3... \
bash /tmp/zt.sh

凭据不全时自动回退免费版(会有明显提示)。


验证

  • 脚本结尾: IPv4 Exit 显示 Cloudflare 的出口 IP,IPv6 Exit 仍是 VPS 原生 IPv6,WARP status: warp=on
  • 仪表盘: Zero Trust > Team & Resources > Devices 列表里出现一台新设备, 身份为 non_identity@<团队名>.cloudflareaccess.com

常见问题

注册报 403 / 429? 403 = 你的 VPS IP 段被 Cloudflare 标记(机房 IP 常见),换 IP 或稍后再试; 429 = 注册限频,等几分钟。脚本会打印服务器原始返回,按 error code 判断。

报 no enrollment JWT returned? 脚本会打印 HTTP 状态码和重定向目标,对照排查: 0. HTTP 404 = 团队域名不存在 → 团队名拼写错误 (建议从 Zero Trust > Settings 页面复制,避免手打)

  1. 策略 Action 选成了 Allow(最常见)——必须是 Service Auth, 特征是 HTTP 302 且 Location 指向 /cdn-cgi/access/login/...
  2. 策略建好后没加进 Device enrollment permissions 列表(要点 Save)
  3. Client ID/Secret 复制不全,或 token 已过期(重建一个即可)

重复运行会重复占设备坑吗? 不会。凭据存在 /etc/wireguard/warp-account.json,重跑时对同一设备 PATCH 换新密钥;仅当模式切换(免费↔团队)时才重新注册。卸载并释放席位: ACTION=delete bash setup_warp_zerotrust.sh

纯 WireGuard 接入和官方客户端有什么区别?

  • 仪表盘的 split tunnel、设备配置文件不会下发——路由以本机 wg0.conf 的 AllowedIPs 为准(默认只接管 IPv4,不碰 IPv6)
  • Gateway 的 L3(网络层)策略正常生效;L7/HTTP 检查类策略需要官方客户端
  • 设备为非身份(non-identity)注册,基于用户身份的策略不适用

内核没有 WireGuard 模块(LXC/老内核)? 脚本自动探测并回退到用户态 wireguard-go(apt 官方源,约 1MB),速度略慢 但功能一致,开机自启同样生效;LXC 容器需先在面板开启 TUN/TAP。

安全说明

  • 只通过 apt 官方签名源装包,不添加第三方源,不下载/执行任何远程二进制
  • 外联仅 Cloudflare 官方域名;连通性检测用官方 cdn-cgi/trace
  • 私钥/凭据仅存本机 /etc/wireguard/(600 权限),只有公钥提交给注册接口
  • 安装期临时使用公共 DNS64(DNS64_SERVERS 可覆盖),结束后自动还原系统 DNS

参考实现: wgcf · Aether · fscarmen/warp


setup_caddy_xray_hysteria.sh

在 Debian/Ubuntu VPS 上一键部署 Caddy + Xray + Hysteria2 + nftables 端口跳跃。

架构图与端口表

flowchart LR
  subgraph CLI["客户端"]
    R["REALITY 客户端"]
    W["WS / XHTTP 客户端"]
    H["Hysteria2 客户端"]
  end
  subgraph VPS["VPS"]
    X443["Xray :443 TCP<br/>VLESS+REALITY"]
    FB{"REALITY 探测通过?"}
    CADDY["Caddy 127.0.0.1:8003<br/>伪装反代 (PROXY protocol)"]
    WS["Xray 127.0.0.1:54442<br/>WS 路径 /scilad"]
    XH["Xray 127.0.0.1:8080<br/>XHTTP 随机路径"]
    HY2["Hysteria2 :8443 UDP"]
    NFT["nftables 端口跳跃<br/>UDP 20000-30000 → 8443"]
    C80["Caddy :80"]
    ACME["hy2 ACME HTTP-01<br/>127.0.0.1:9180"]
  end
  R -->|"直连 443"| X443
  X443 --> FB
  FB -->|"是"| OK["VLESS 代理"]
  FB -->|"否: 回落"| CADDY
  W -->|"橙云 CDN 回源 443"| X443
  CADDY -->|"路径 /scilad"| WS
  CADDY -->|"随机路径"| XH
  H -->|"UDP 任意跳跃端口"| NFT
  NFT --> HY2
  LE["Let's Encrypt"] -->|"HTTP-01 到 80"| C80
  C80 -->|"反代 challenge"| ACME
  ACME -.->|"签 DOMAIN 证书"| HY2
组件 监听 说明
Xray 443/TCP VLESS+REALITY(xtls-rprx-vision),非 REALITY 流量回落到 Caddy
Xray 127.0.0.1:54442 VLESS+WS(路径 /scilad,经 Caddy 反代)
Xray 127.0.0.1:8080 VLESS+XHTTP(随机路径,mode auto,经 Caddy 反代)
Caddy 127.0.0.1:8003 收 REALITY 回落(PROXY protocol),伪装反代 + WS/XHTTP 入口
Hysteria2 8443/UDP 默认 HTTP-01 签 DOMAIN 证书(challenge 仅监听 127.0.0.1:9180,由 Caddy 80 端口反代验证);可选 DNS-01 签 *.主域 通配符
nftables UDP 20000-30000 redirect 到 8443(端口跳跃)

快速开始

wget -qO setup_caddy_xray_hysteria.sh \
  https://git.henhaoji.site/eli/vpsetup/raw/branch/main/setup_caddy_xray_hysteria.sh
sudo bash setup_caddy_xray_hysteria.sh        # 交互式:只需回答主域名
sudo ACTION=upgrade bash setup_caddy_xray_hysteria.sh     # 升级全部组件到最新版(沿用已存参数,不交互)
sudo ACTION=uninstall bash setup_caddy_xray_hysteria.sh   # 卸载(PURGE=1 连包一起删)

三个组件的安装方式本身就是幂等升级(Caddy 走 apt 仓库、Xray/hy2 官方脚本重跑即拉 最新版),所以重复运行安装也能升级;ACTION=upgrade 的区别是:未安装过会报错 而不是误装,且完全不交互。

必填:DOMAIN(灰云子域名,Caddy 站点、REALITY serverName、hy2 证书都用它)。

Cloudflare 回源设置指引(2026 年现行界面)

部署前后的域名与回源配置按此操作。核心原则:REALITY 域名必须灰云直连 (REALITY 原理上不能过 CDN),CDN 域名橙云代理(只承载 WS/XHTTP)。

A. 直连域名 DOMAIN(Caddy 站点 + REALITY + hy2,必配)

  1. DNS → Records:添加 A 记录(IPv4)或 AAAA 记录(IPv6-only VPS), 名称 = 你的子域名,Proxy status 保持灰色云(DNS only)——不要点亮。
  2. 无其他设置;REALITY 流量由客户端直连 443,Caddy 证书自己向 Let's Encrypt 签。

B. CDN 域名 CDN_DOMAIN(可选,VPS IP 被封后的备用通道)

  1. DNS → Records:同样指向 VPS IP,但点亮橙色云(Proxied)。
  2. SSL/TLS → Overview(概述):加密模式选 Full (strict)。 回源证书由 Caddy 用 Let's Encrypt 签(CF 透传 HTTP-01 challenge),满足 strict 校验。 不要用 Flexible(回源走 80 明文且与 hy2 的 HTTP-01 端口语义冲突)。
  3. Origin Rules:无需配置(全部 Preserve 即可)。CF 回源时 SNI/Host 默认 就是 CDN_DOMAIN:请求先到 Xray 443,REALITY serverNames 不匹配即原样回落 Caddy,Caddy 按 SNI 匹配到 CDN 站点块(WS/XHTTP/伪装反代),全链路不依赖 任何 Host/SNI 改写。注意 Origin Rules 的 Host Header / SNI / DNS record 改写 是 Enterprise 专属(免费版灰色不可选,仅剩 Destination Port 可用)—— 对本架构毫无影响,无需为此升级 plan。
  4. SSL/TLS → Edge Certificates(边缘证书):
    • Encrypted ClientHello (ECH):Free 版默认已开启且界面不显示开关 (官方:"ECH is enabled by default on Free zones"),无需操作。可用 dig +short HTTPS 你的CDN域名 验证返回含 ech=(本机 systemd-resolved stub 查不出 type65 时换 dig @1.1.1.1 或 DoH JSON 接口)。 开之后审查者只能看到"你连了 Cloudflare"(外层 SNI 统一为 cloudflare-ech.com),看不到 CDN_DOMAIN;客户端 Xray ≥25.7.26 内核, 在节点上启用 ECH(或链接加 ech 参数)。
    • Always Use HTTPS:建议打开。不会与源站 Caddy 的 80→443 跳转形成循环 ("too many redirects" 只在源站把 HTTPS 跳回 HTTP 时发生;Caddy 是 http→https 单向跳,方向一致)。打开后访客到边缘的 80 请求由 CF 直接 301, 不再回源 80;ACME challenge 走 /.well-known/ 路径不受影响。
    • Minimum TLS Version:改为 1.2(面板默认 1.0 是兼容上古客户端; 代理客户端全是 TLS 1.2+,收紧无影响)。
    • HTTP/3 (QUIC):开关在 Speed → Settings → Protocol Optimization (2026 面板位置,不在 Network 也不在边缘证书页),默认开启即可; 访客↔CF 边缘走 H3,回源仍是 TCP,与本架构无关。
    • Advanced Certificate Manager (ACM):付费插件(自定义边缘证书 CA/有效期、 Total TLS、边缘 cipher suites),与本架构无关,无需激活。
  5. Network → WebSockets:保持开启(默认开),WS 回源需要。 XHTTP 基于普通 HTTP 流式请求,无需额外开关。

C. 常见误区

  • 两个域名不能是同一条 DNS 记录切换灰/橙:REALITY 要灰、CDN 要橙,必须两个独立子域名。
  • 橙云域名不要再在客户端里配 REALITY——CF 终止 TLS 后源站收到的是普通 HTTPS, REALITY 客户端连不上(会回落成伪装网站,这就是它"看起来正常但代理不通"的原因)。
  • VPS 防火墙/安全组只需放行:443/TCP(Xray)、443/UDP(若想让 CF 边缘 H3 直连,可不开)、 8443/UDP(hy2)+ 20000-30000/UDP(端口跳跃)、80/TCP(HTTP-01 证书验证与跳转)。 8003、54442、8080、9180 全部只绑 127.0.0.1,无需放行。

hy2 证书模式

hy2 证书两种模式(HY2_CERT_MODE):

  • http(默认):hy2 内置 ACME(certmagic)走 HTTP-01,challenge 只监听 127.0.0.1:9180,由 Caddy 的 80 端口把 /.well-known/acme-challenge/ 反代过去 完成验证。签的是 DOMAIN 非通配证书,自动续期、热生效、无需重启, 不需要任何 DNS API Token,也不新开公网端口(TLS-ALPN 必须独占 443/TCP, 与 Xray 冲突,故不可用)。
  • dns:Cloudflare DNS-01 签 *.主域 通配符证书,需 CF_API_TOKEN (Zone.DNS 编辑权限)。域名不想指向本机 80 端口或想要通配符时用。

可选环境变量

全部留空则自动生成并保存到 /etc/caddy-xray-hy2/setup.env(600 权限), 重跑自动读取作默认值,不会重新生成导致已分发客户端失效:

变量 默认 用途
EMAIL admin@主域 ACME 注册邮箱
CDN_DOMAIN 空 橙云域名,独立站点块,仅承载 WS/XHTTP 回源(REALITY 不能过 CDN,二者必须分开)
CADDY_SITES_DIR /etc/caddy/sites 额外站点目录(升级安全的扩展点):脚本只渲染 import <目录>/*.caddy,目录内容归用户所有,详见下节
HY2_CERT_MODE http hy2 证书模式:http(默认,经 Caddy 80 验证)或 dns(CF DNS-01 通配符)
CF_API_TOKEN 空 仅 HY2_CERT_MODE=dns 时必填
HY2_ACME_PORT 9180 hy2 HTTP-01 challenge 的本机监听端口
XRAY_UUID / XRAY_WS_UUID / XRAY_XHTTP_UUID 随机 三个 inbound 的 UUID
XRAY_PRIVATE_KEY xray x25519 生成 REALITY 私钥(公钥自动推导)
XRAY_SHORT_ID 8 位 hex REALITY shortId
XRAY_VLESS_ENC 关 =1 启用 VLESS 后量子加密(见下方「可选增强」)
XHTTP_PATH / XHTTP_PORT /8位hex / 8080 XHTTP 路径与本地端口
XHTTP_PADDING 开 XHTTP extra padding 混淆(随机 header/key),服务端 extra 块与分享链接 extra= 参数(分享标准 4.3.19)带同一份键名。=0 关闭。注意:忽略 extra 的旧客户端会连不上(服务端 400),升级开启后旧链接作废、需重发
HY2_PASSWORD 32 位 hex Hysteria2 密码
HY2_MASQUERADE_URL https://engineersblog.net hy2 伪装反代目标
SKIP_CADDY / SKIP_XRAY / SKIP_HYSTERIA / SKIP_NFTABLES — =1 跳过对应组件
DIST_UPGRADE — =1 安装前 apt-get dist-upgrade
DRY_RUN — =1 干跑:所有配置渲染到 ./dry-run-output/,不做系统变更

安装结束自动打印 REALITY / WS / XHTTP /(可选 CDN)/ Hysteria2 的分享链接。

Caddy 官方 apt 源不可用时(2026-10 cloudsmith 402)

Caddy 的 apt 源托管在 Cloudsmith 的免费 OSS 额度上。2026-10-09 起该仓库的包投递配额被 CI 滥用打满,dl.cloudsmith.io/public/caddy/stable/deb/debian 下所有下载一律返回 402 Payment Required(上游 issue:caddyserver/dist#142; 2023、2024 年也各发生过一次,2024 那次约 8 天恢复)。apt-get update 因此必然失败。

脚本对此只做一种、且很窄的容错(apt_update_tolerant()):

  • 报错全部来自 caddy/cloudsmith 源、且本机已经装了 caddy → 打 [WARN] 继续: 本次跳过 caddy 二进制升级,但配置照常重新渲染 → caddy validate → 重载服务;
  • 报错牵涉任何别的仓库,或本机还没有 caddy → 依旧硬失败退出(不吞真问题)。

所以 402 期间 ACTION=upgrade 仍能跑完(caddy 版本停在已装的那个),只是拿不到新二进制。 若上游长期不恢复,可选 Caddy 官方 GitHub Release 的 .deb(同一份产物,带 Sigstore 签名与 校验和)——那是供应链口径的变更,需单独决定,脚本默认不做。

额外站点(升级安全的扩展点)

需要在同一台 VPS 上再挂自己的站点(反代内网服务、给别的项目当公网入口……)时, 不要手改 /etc/caddy/Caddyfile —— 脚本每次 install/upgrade 都整体重写它, 手改必丢。把你的站点块放进额外站点目录(CADDY_SITES_DIR,默认 /etc/caddy/sites):

# /etc/caddy/sites/netbird.caddy
*.netbird.example.com {
        bind 127.0.0.1 [::1]
        tls /etc/caddy/certs/netbird/fullchain.pem /etc/caddy/certs/netbird/key.pem
        tls { alpn h2 http/1.1 }
        reverse_proxy https://127.0.0.1:8444 {
                transport http { tls_insecure_skip_verify }
        }
}

改完自检并生效(reload 不会重启进程,连接不中断):

sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy

为什么它是升级安全的:脚本只在主 Caddyfile 末尾渲染一行 import /etc/caddy/sites/*.caddy,并对该目录只读 —— 只做两件事:建目录; 目录全空时补一个纯注释的 00-example.caddy。永不覆盖、永不删除你放进去的 任何文件,ACTION=uninstall 也不删。import 用的是 glob:Caddy 对"空匹配的 glob" 只警告不报错(v2.0→v2.11 一直如此),所以目录空着也不会让 caddy validate 失败。

四条硬性约束(00-example.caddy 里有一份同样的说明):

约束 原因
每个站点块必须 bind 127.0.0.1 [::1] 公网 443 归 Xray REALITY,Caddy 只在 127.0.0.1:8003 收回落流量(经 listener_wrappers 的 PROXY protocol + tls 包装)。没有 bind 的块会落到另一个未包装的 :8003 监听器上:配置合法、caddy 也能起来,但 443 的流量永远到不了它(静默不可达),还可能在其它网卡上多开一个端口。脚本会体检告警(不阻断)
建议沿用 tls { alpn h2 http/1.1 } 该监听器只收 TCP 回落流量,h3/QUIC 永远用不上;通告 h3 反而会让客户端在 TCP 上误协商 h3 而挂死
通配符证书必须自己喂 apt 版 caddy 没有 DNS 插件 → 签不了通配符。用 tls <fullchain.pem> <key.pem>(或 tls { load /dir })提供现成证书。普通单域名则可以自动签:Caddy 占着公网 80,且 ACME HTTP-01 的拦截先于 http:// 跳转块
换证书后必须 systemctl reload caddy Caddy 不 watch 证书文件:tls <cert> <key> / tls { load } 只在配置加载时读一次。ExecReload 已是 caddy reload --force(会重新 provisioning)

另注:这里不能写全局选项块({ ... })—— 它必须是主 Caddyfile 的第一个块。 需要全局设置(例如 on_demand_tls)请改脚本/setup.env。

风险联动:额外站点写错会让 caddy validate 失败,从而让 ACTION=upgrade 中止。中止本身是安全的(旧配置与在跑的服务不受影响、升级只是没继续),但要知道 这个联动 —— 所以改完先自己跑一次 caddy validate。

防火墙兼容性(Oracle Cloud 等)

脚本对 nftables 的处理遵循一条原则:只管自己的表(inet hysteria_nat 端口跳跃), 绝不整体执行 /etc/nftables.conf。因此在各种发行版/云镜像下都安全共存:

  • Oracle Cloud / Debian 云镜像:默认启用 iptables-persistent + /etc/iptables/rules.v4。 由于这些发行版的 iptables 实际是 iptables-nft,persistent 加载的规则活在 nft 的 table ip filter 里,与脚本的 table inet hysteria_nat 同内核、不同表,天然互不干扰。
  • 纯 nftables 机器:同理,脚本只追加 include 行和自己的表文件,不动其余规则。

为什么绝不能图省事 nft -f /etc/nftables.conf(或 systemctl reload nftables): 发行版 conf 首行的 flush ruleset 是无差别清空所有表,运行时执行会瞬间抹掉 主机防火墙(含 SSH 放行)和 Docker 的 NAT 表,而 netfilter-persistent 只在开机时补回—— 防火墙会一直裸奔到下次重启。开机时则没有这个顾虑:systemd 的硬依赖链保证 nftables.service(sysinit)先于 netfilter-persistent(multi-user)执行,flush 后 规则随即补回,且两者都 Before=network-pre.target,对外不可达的窗口不存在。

可选增强(默认关闭,按需开启)

VLESS 后量子加密(XRAY_VLESS_ENC=1):防"现在录制流量、未来量子计算机解密" (harvest-now-decrypt-later)。用 xray vlessenc 生成 ML-KEM-768 后量子密钥对, 写入三个 VLESS inbound 的 decryption,分享链接自动带上客户端 encryption= 参数。 对速度/延迟几乎无感(握手包大约 1-2KB),但要求所有客户端内核 Xray ≥25.8, 不支持的客户端会连不上。密钥持久化在 setup.env,重跑不换。防封锁/限流无帮助, 属保密性强化,客户端生态统一新版时才建议开。

TLS ECH(仅 CDN_DOMAIN 场景,纯客户端配置):走 CF CDN 的 XHTTP/WS 链路上,不开 ECH 时 审查者能看到明文 SNI=CDN_DOMAIN(你的私有域名,被标记就整域报废)。开启后外层 SNI 统一为 CF 公共 ECH 名(cloudflare-ech.com),真实域名加密——审查者只知道你连了 Cloudflare。ECH 在 CF 边缘终止,VPS 无需任何改动(Free 版默认已开,见上方 B 节; dig +short HTTPS CDN域名 应返回含 ech=)。客户端 Xray ≥25.7.26:分享链接加 &ech=<base64 ECHConfigList>(dig 返回里 ech= 后那串,URL 编码),或 JSON 出站 tlsSettings 里配 echConfigList(静态 base64,或 "域名+https://1.1.1.1/dns-query" 走内置 DoH 动态查询)与 echForceQuery(full/half/none,26.3.27 起默认 full)。 静态配置下 ECH 协商失败会直接报错而非回落明文 SNI,能连通即生效。 CF 轮换 ECH 密钥很勤(静态快照实测很快失效),所以脚本默认在两条 CDN 分享链接 内嵌动态查询形式 ech=CDN域名+udp://9.9.9.9——客户端 Xray 拨号时自查最新 配置并按 TTL 缓存刷新,轮换无感,无需手动更新。查询协议按 scheme 选择:

  • udp://9.9.9.9(默认):明文 UDP53,Quad9——8.8.8.8 偶发被断,9.9.9.9 实测 不被屏蔽、延迟略高;备选 149.112.112.112(Quad9 备用),或国内 223.5.5.5 / 119.29.29.29(最快最稳,查询记录国内可见——节点域名的 A 查询默认本来也走 国内 DNS,不多暴露什么)。报文理论上可被窃听/投毒,但配合 full 策略,伪造 记录只会导致断连、不会静默降级泄露 SNI,可接受。
  • 域名+https://8.8.8.8/dns-query:内置 DoH(h2 + chrome 指纹,最隐蔽),但 8.8.8.8:443 在墙内实测不稳——passwall 的"本机代理"会把路由器自身流量(含 Xray 的 DoH)劫进代理通道,代理一抖查询就失败。DoH 端点必须指到 /dns-query (RFC8484 wireformat),/resolve 是 JSON 调试接口、Xray 不认。tcp:// 不支持。 查询失败会硬失败(无明文 SNI 回落),不合适的网络用 ECH_CONFIG=off 或 ECH_CONFIG=<静态 base64> 快照(零 DNS 依赖但会过期)。 图形客户端(passwall 等):「ECH 配置」框填 CDN域名+udp://9.9.9.9(动态) 或静态 base64;「ECH 查询策略」动态形式选 full(最严,防降级)或 half (查询失败可回落连通),静态 base64 选 none。 直连的 REALITY 域名不需要(SNI 本来就是公开伪装域名);直连 Caddy 的 TLS 链路 用不上(Caddy 的 ECH 2026 年才落地且需自行发布 DNS HTTPS 记录,CDN 链路才是零改动方案)。

hy2 场景化武器(不集成,需要时手动改 /etc/hysteria/config.yaml): UDP 被选择性限速时试 obfs: salamander(不行再试实验性 gecko);UDP 整体被封的 网络用 mimic(XDP 伪装 TCP,需内核模块 + 客户端同开,与端口跳跃互斥)。 改完 systemctl restart hysteria-server。

安全说明

  • 与 setup_warp_zerotrust.sh 不同,本脚本按官方文档引入 cloudsmith apt 源 (Caddy 官方仓库),并执行 Xray / Hysteria2 官方安装脚本;除此之外不下载 任何二进制或远程代码
  • 凭据仅存本机 /etc/caddy-xray-hy2/setup.env(600 权限)

setup_cdn_bypass_tproxy.sh

适用场景与原理

Google Cloud 等厂商的 VPS 封锁(或对 CDN 出口计费)Cloudflare / Fastly / Akamai 三大 CDN 网段。影响最大的往往不是浏览网页,而是本机进程的自动化出站—— 例如 Hysteria2/Caddy 的 ACME 证书续期要访问 acme-v02.api.letsencrypt.org (Let's Encrypt 的 API 挂在 Cloudflare 上)和 api.cloudflare.com(DNS-01 验证), 被封后证书静默过期、代理服务瘫痪,这正是本脚本要解决的典型事故。

原理:本机发往 CDN 网段的流量由 nftables 打标 → 策略路由送进 lo → xray 的 dokodemo-door(tproxy)入站接管 → 经一个不限流上游节点出站。

flowchart LR
  APP["本机出站进程<br/>curl / hysteria ACME / apt"] --> OUT{"nft cdn_bypass_mark<br/>OUTPUT 链"}
  OUT -->|"目标是 CDN 段<br/>(287 段清单)"| MARK["打 mark 1"]
  OUT -->|"私网 / mark 255 / 其他"| DIRECT["main 表直连"]
  MARK --> RULE["ip rule fwmark 1<br/>lookup table 100"]
  RULE --> LO["local 0.0.0.0/0 dev lo<br/>包被路由进 lo"]
  LO --> TP["nft cdn_bypass_tproxy<br/>prerouting 链"]
  TP --> DKD["xray dokodemo-door<br/>127.0.0.1:54443 followRedirect"]
  DKD --> VLESS["vless 出站 (sockopt.mark=255 防回环)"]
  VLESS --> UP["上游不限流节点"]
  UP --> CDN["Cloudflare / Fastly / Akamai"]
  BOOT["cdn-bypass-route.service<br/>(oneshot, 开机补 ip rule/route)"] -.->|"持久化"| RULE

前提:VPS 已用官方脚本装好 xray 或 v2ray(service 存在即可,本脚本不装 xray)。 准备一个上游节点的 vless:// 分享链接(支持 reality/tls/tcp/ws 参数)。

用法

wget -qO setup_cdn_bypass_tproxy.sh \
  https://git.henhaoji.site/eli/vpsetup/raw/branch/main/setup_cdn_bypass_tproxy.sh

sudo VLESS_LINK='vless://uuid@host:443?security=reality&pbk=...&sid=...&sni=...&fp=chrome&flow=xtls-rprx-vision#name' \
     bash setup_cdn_bypass_tproxy.sh
# 不设 VLESS_LINK 时会从 /dev/tty 交互读取(wget|bash 管道场景也能粘贴)

sudo ACTION=uninstall bash setup_cdn_bypass_tproxy.sh   # 全部卸载
DRY_RUN=1 ...                                            # 只渲染到 ./dry-run-output/,不动系统

可选环境变量:TPROXY_PORT(54443)、ROUTE_TABLE(100)、MARK(1)、 XRAY_SERVICE(xray/v2ray,默认自动探测)、EXTRA_EXEMPT_CIDRS(额外不打标的段, 空格分隔,如 "8.8.8.8 119.29.29.29")。

安装末尾自动实测 https://www.cloudflare.com/cdn-cgi/trace(直连必超时, 通了即证明走了代理),并打印出口身份 IP 供对照。

实现结构

脚本只做四件事,全部幂等、可重复执行:

  1. xray confdir 注入(不碰原配置):解析 vless 链接生成 /etc/xray-cdn-bypass/cdn-bypass.json(tproxy 入站 + vless 出站 + routing 规则), 通过 systemd drop-in 给 xray 的 ExecStart 追加 -confdir——原 config.json 一个字节不动。重启前先 xray run -test 验证合并后的配置,失败则中止、 线上 xray 不受影响。若你的 xray 已在用 confdir,脚本会拒绝并提示手工合并。
  2. nft 两张表:/etc/nftables/cdn-bypass.nft(cdn_bypass_mark 打标 + cdn_bypass_tproxy 接管),向 nftables.conf 幂等追加 include。与主脚本同一约定: 运行时只 nft -f 自己的文件,绝不执行整份 conf。CDN 清单为内嵌的 287 段快照 (Cloudflare + Fastly + Akamai)——被封锁的机器在代理起来之前无法在线拉取,必须内嵌。
  3. 策略路由持久化:cdn-bypass-route.service(oneshot)开机补 ip rule fwmark 1 lookup 100 + ip route add local 0.0.0.0/0 dev lo table 100。 ip 命令是纯内核运行时状态,重启即失,必须由 service 持久化;运行时误删可用 systemctl restart cdn-bypass-route 恢复。
  4. 防回环:注入的 vless 出站带 sockopt.mark=255,nft 第一条规则豁免 mark 255——即使上游节点 IP 落在 CDN 段内也不会被重新劫回 xray 自身。

边界

  • 只处理 IPv4(table ip):若 VPS 后续获得全局 IPv6,CDN 的 v6 流量不经过本代理
  • 安装/卸载会重启 xray 一次(现有 REALITY 连接秒级中断)
  • 只代理"目标是 CDN 段"的流量,其余出站路径不变,不影响 xray 原有入站/出站逻辑
  • 卸载即全部还原(confdir 文件、drop-in、nft 表、route unit 一并清除)

migrate_existing_stack.sh

适用场景

老机器上是手动(或旧脚本)部署的 Caddy + Xray + Hysteria2:凭据散落在三份 配置里、没有 /etc/caddy-xray-hy2/setup.env,无法用 ACTION=upgrade 幂等升级。 本脚本只做读取 + 备份 + 生成 setup.env,不重启、不修改任何运行中的服务; 迁移后再跑一次 ACTION=upgrade 即完成全面升级,且 REALITY / WS / hysteria2 的存量客户端凭据不变、不断线。

flowchart LR
  OLD["旧配置<br/>Caddyfile / xray config.json<br/>hysteria config.yaml"] --> PARSE["解析提取凭据<br/>uuid / REALITY 密钥 / shortId<br/>WS 路径 / hy2 密码 / ACME 模式"]
  PARSE --> CONFIRM{"交互确认<br/>DOMAIN / EMAIL / WS_PATH"}
  CONFIRM --> ENV["写 setup.env (600)<br/>+ 备份旧配置"]
  CONFIRM --> EXTRA["非主域 Caddy 站点块<br/>→ 抽出 + 安装进额外站点目录(升级不再覆盖)"]
  CONFIRM --> NFT{"nft 体检"}
  NFT -->|"干净/与模板一致"| NFTOK["升级时幂等接管"]
  NFT -->|"有自定义规则"| NFTREF["生成参考配置<br/>提示自行处理,绝不插入 include"]
  ENV --> UPGRADE["ACTION=upgrade<br/>setup_caddy_xray_hysteria.sh"]
  UPGRADE --> DONE["全面升级完成<br/>旧客户端不断线"]

用法

wget -qO migrate_existing_stack.sh \
  https://git.henhaoji.site/eli/vpsetup/raw/branch/main/migrate_existing_stack.sh

sudo bash migrate_existing_stack.sh      # 交互式:默认值全部取自旧配置,逐项回车确认
DRY_RUN=1 bash migrate_existing_stack.sh # 干跑:读真实配置,输出到 ./dry-run-output/,不动系统

# 迁移完成后全面升级:
sudo ACTION=upgrade bash setup_caddy_xray_hysteria.sh

迁移行为细则

  • Xray:提取 REALITY 入站的 uuid / privateKey / shortId / serverName 和 WS 入站 uuid,写入 setup.env;公钥用 xray x25519 -i 现推。多个 shortId 只迁移第一个 (会警告)。旧 XHTTP 入站不迁移(部署脚本会生成新 XHTTP 凭据,旧 XHTTP 客户端 需换链接)。配置里的 // 注释自动剥离后解析。
  • WS 路径:旧部署若是 handle_path /vlessws 这类自定义路径,迁移会把 WS_PATH=/vlessws 写入 setup.env——部署脚本(WS_PATH 参数化后)按原路径渲染, 旧 WS 客户端无感。DOMAIN 与旧 REALITY serverName 不一致会显著警告 (旧客户端 sni 钉死旧域,换域即断)。
  • Hysteria2:password / masquerade / ACME 模式整体迁移。dns + cloudflare 模式连同 CF_API_TOKEN 一起写入,通配符证书形态保持不变(certmagic 复用 /etc/hysteria/acme_certs 里现有证书,不重签)。其他 DNS provider 会警告并 回退 http 模式(单域名证书,存量通配符 sni 的 hy2 客户端需人工确认)。
  • Caddy 额外站点:主域名之外的站点块verbatim 抽出,并自动安装进额外站点目录 (CADDY_SITES_DIR,默认 /etc/caddy/sites)里的 migrated-extra-sites.caddy—— 升级后由新 Caddyfile 末尾的 import <目录>/*.caddy 自动加载,不再需要人工合并, 也不会被后续升级覆盖(详见上文「额外站点」一节)。参考件仍留一份在迁移输出目录。 两种例外只留参考文件、不自动安装:① 块里含 dns <provider> 等定制插件指令 (如 xcaddy 编译的 duckdns/namecheap 插件)——官方 apt 的 caddy 不含该插件,装了会让 caddy validate 失败、反而挡住升级,需保留定制二进制或用 xcaddy 重编后自行放入; ② 装了 caddy 时 caddy adapt 预检不通过(只解析适配,不做 TLS 签发)。 另外,缺 bind 127.0.0.1 的块会告警:443 的 REALITY 回落流量到不了它。 目标文件已存在时会先备份为 .bak-<时间戳> 再覆盖。
  • nft:只体检不修改。hysteria_nat 表不存在或与部署脚本模板完全一致 → 干净,升级时幂等接管;检测到自定义/额外规则 → 生成参考配置 hysteria-reference.nft 并提示自行处理,绝不插入 nftables.conf 的 include。
  • 备份:三份旧配置原样复制到 /etc/caddy-xray-hy2/migration-backup-<时间戳>/。
  • 幂等防线:setup.env 已存在时直接拒绝运行(说明已在管理体系内,应直接用 ACTION=upgrade)。

开发与测试

bash -n setup_*.sh migrate_*.sh   # 语法检查
shellcheck setup_*.sh migrate_*.sh # lint
bash tests/run_tests.sh           # DRY_RUN 断言测试(含 migrate 的 fixtures 场景)
S
Description
用于vps开荒,加入各种常用配置脚本~
Readme 18 MiB
Languages
Shell 100%