服务端 zip 加一键部署脚本(cloudflared 安装 + 部署 + 公网,全自动)

用户要求:服务器端文件里放一个 .sh,双击就能检查 cloudflared 装没装、
自动部署、配好公网,以后全自动。

== 新增 ==

relay/deploy.sh        一键部署主脚本,7 步全自动:
                       环境检查 -> 装 cloudflared -> 部署程序 -> 网络调优
                       -> 写 4 个 systemd 服务 -> 配隧道 -> 启动自检打公网地址
relay/deploy-tune.sh   网络调优,可单独重跑:
                       keepalive 30/10/3 + 路径 MTU 探测 + MSS 1360
                       + 接口 MTU 1400 + mask networkd-wait-online
                       每一项都写明了实测依据

用法:
    bash deploy.sh                           复用已有凭据,全自动
    TUNNEL_TOKEN=eyJh... bash deploy.sh      Token 模式
    bash deploy.sh --hostname relay.你的域名  换域名
    bash deploy.sh --skip-tunnel             只装中转
    bash deploy.sh --help                    全部参数

重跑 = 升级;已有数据/配对码/管理密码/凭据/手改过的 config.yml 都不动。

== 自动化到什么程度 ==

* cloudflared 不在就自动下载装上(并校验能执行)
* 隧道支持两种模式:已有凭据文件、或 TUNNEL_TOKEN
* **不传 --hostname 且没有 Token 时明确提示要填**,不会静默用一个写死的值
  (用户明确说过:这个中转站是他自己用的,不对外开放,
   绝不能把私人域名烧成默认值)
* deploy-tune.sh 的 MTU 取 1400:路径 MTU 实测 1472,
  而 MSS 钳制只管 TCP、QUIC 走 UDP 完全不受保护;
  接口 MTU 降到 1400 后 QUIC 建齐 10 条连接从 28 秒缩到 12 秒

== 顺带修掉两个真 bug ==

1. write_tunnel_unit 写的文件名漏了 .service 后缀 ——
   文件写下去了但 systemd 完全看不见,enable 还会安静地失败。
2. 发布包一直缺 site_shots/(老 bug):
   package_remote.py 用 iterdir() + is_file() 收文件,目录进不去。
   新机器装完官网截图全是 404。只有从 zip 解压真跑一遍才暴露。
   同时把 deploy.sh 里 site_shots 的检查降级成警告(缺截图不该让部署失败)。

另外给 deploy-tune.sh 清旧 MSS 规则的循环加了 10 次上限 ——
原写法在 -D 报成功但规则仍在时会死循环把整个部署卡死。

== 验证 ==

* _build/test_deploy_sh.sh        沙箱 65 项,全通过
* _build/test_zip_deploy.sh       从发布 zip 解压再跑,30 项,全通过
* _build/test_deploy_real.sh      **真机 Debian 上实测**(新增):
    zip 解出 17 个文件 -> 语法/LF/无 BOM 通过
    --help 退出码 0 且无副作用、未知参数退出码 2
    沙箱安装成功、幂等重跑哨兵不被冲
    现有三个中转服务全程 active,自检 200
This commit is contained in:
xmz7891
2026-10-07 17:28:09 +08:00
parent d4f335b8f9
commit facb156aca
10 changed files with 2331 additions and 89 deletions
+130 -41
View File
@@ -1,6 +1,10 @@
#!/bin/bash
# 网络调优:修 MTU 问题(这次找到的**真正根因**)。
# 网络调优:修路径 MTU 问题 + 给 QUIC 隧道留出安全包长。
#
# 这个脚本是 **MSS / 接口 MTU / 路径 MTU 探测** 的唯一出处。
# deploy-tune.sh 会调它;不要在别处再设一遍 ——
# "到底哪个值生效" 一旦有两处来源就会变得很难查。
#
# ════════════════════════════════════════════════════════════════
# 证据链
# ════════════════════════════════════════════════════════════════
@@ -17,63 +21,148 @@
# ════════════════════════════════════════════════════════════════
# 不夹 MSS(1500) 87%
# MSS 1300 100%
# MSS 1360 100%
# MSS 1400 100% <- 采用(离路径上限还有余量)
# MSS 1360 100% <- 采用(和 MTU 1400 配套)
# MSS 1400 100%
# MSS 1440 100%
#
# 15 分钟长测(180 次):见安装日志
# ════════════════════════════════════════════════════════════════
# 为什么接口 MTU 也要压到 1400(早期结论是错的,这里更正)
# ════════════════════════════════════════════════════════════════
# 早先这里写的是「不要改接口 MTU,实测降到 1400 反而掉到 50%」。
# 那个结论只考虑了 TCP,**漏掉了 QUIC**:
#
# ════════════════════════════════════════════════════════════════
# 注意:**不要改接口 MTU**
# ════════════════════════════════════════════════════════════════
# 把 wlan0 的 MTU 降到 1400 反而掉到 50% ——
# 链路本身能过 1448 的包,压小接口 MTU 是过度限制,
# 包变小、来回变多,命中丢包窗口的概率反而更高。
# MSS 只限制 TCP 段,不动链路层,更精准。
# * MSS 钳制走的是 iptables 的 TCPMSS 目标,**只对 TCP 生效**;
# * 隧道用 QUIC 时跑在 UDP 上,整套 TCPMSS 规则对它毫无作用。
#
# QUIC 的包一旦超过路径 MTU 就会被中间设备丢掉,而 UDP 丢包
# 不会回 ICMP,客户端只能干等到握手超时 —— cloudflared 日志里就是
# "failed to dial to edge with quic: timeout: no recent network activity"
#
# 把 wlan0 MTU 从 1500 降到 1400 之后(实测):
# 建齐 6 条 QUIC 连接 28 秒 -> 13 秒
# 建齐 10 条 QUIC 连接 28 秒 -> 12 秒
# 建连期间 dial 失败 2 次 -> 1 次
#
# 【重要】MTU 和 MSS 必须配套:
# MTU 1400 => TCP 段最大 1360(1400 - 20 IP头 - 20 TCP头)
# 如果 MTU 压到 1400 却还把 MSS 设成 1400,
# TCP 段(1440) 就超过接口 MTU(1400),包在本地就被分片,等于白改。
# 两个值在本脚本里一起设,改一个必须改另一个。
set -u
MSS_VALUE=${MSS_VALUE:-1400}
MSS_VALUE=${MSS_VALUE:-1360}
MTU_VALUE=${MTU_VALUE:-1400}
# ────────────────────────────────────────────────────────────────
# 找对外的那张网卡
# ────────────────────────────────────────────────────────────────
# 以前这里写死 wlan0。写死的问题是换机器(或者用 usb0 上网)就错了,
# 而 MTU 设错网卡是没有任何提示的 —— 改了等于没改。
IFACE=${IFACE:-}
if [ -z "$IFACE" ] && command -v ip >/dev/null 2>&1; then
IFACE=$(ip -4 route show default 2>/dev/null \
| awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' \
| head -1)
fi
if [ -z "$IFACE" ]; then
for cand in /sys/class/net/*; do
name=$(basename "$cand")
case "$name" in lo|pan0|docker*|veth*|br-*) continue ;; esac
[ -r "$cand/operstate" ] || continue
[ "$(cat "$cand/operstate" 2>/dev/null)" = "up" ] || continue
IFACE="$name"
break
done
fi
[ -z "$IFACE" ] && IFACE=wlan0
echo " 1) 打开路径 MTU 探测"
sysctl -w net.ipv4.tcp_mtu_probing=1 >/dev/null
echo " tcp_mtu_probing = $(cat /proc/sys/net/ipv4/tcp_mtu_probing)"
if [ -w /proc/sys/net/ipv4/tcp_mtu_probing ]; then
sysctl -w net.ipv4.tcp_mtu_probing=1 >/dev/null
echo " tcp_mtu_probing = $(cat /proc/sys/net/ipv4/tcp_mtu_probing)"
else
echo " [跳过] 读不到 /proc(不是 Linux 环境)"
fi
echo " 2) 夹 MSS 到 $MSS_VALUE(只限制 TCP 段,不改链路层)"
for chain in OUTPUT POSTROUTING; do
# 先清掉所有旧值,避免重启后越积越多
for old in 1300 1360 1400 1440; do
while iptables -t mangle -C "$chain" -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss "$old" 2>/dev/null; do
iptables -t mangle -D "$chain" -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss "$old" 2>/dev/null || break
if command -v iptables >/dev/null 2>&1; then
for chain in OUTPUT POSTROUTING; do
# 先清掉所有旧值,避免重启后越积越多。
# ⚠ 这个 while 必须有次数上限:
# 正常情况下 -D 删掉之后 -C 就返回非 0,循环自然结束。
# 但如果哪天 -D 报成功而规则还在(被包了一层、或 iptables 出异常),
# 原写法就是死循环,把整个部署卡死。
# 实测踩过:桩命令一律返回 0,脚本真的转不出来了。
for old in 1300 1360 1400 1440 1460; do
guard=0
while iptables -t mangle -C "$chain" -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss "$old" 2>/dev/null; do
guard=$((guard + 1))
if [ "$guard" -gt 10 ]; then
echo " ⚠ 清理旧 MSS 规则($old) 超过 10 次仍报存在,停手"
break
fi
iptables -t mangle -D "$chain" -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss "$old" 2>/dev/null || break
done
done
iptables -t mangle -A "$chain" -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss "$MSS_VALUE" 2>/dev/null
done
iptables -t mangle -A "$chain" -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss "$MSS_VALUE" 2>/dev/null
done
iptables -t mangle -L -n 2>/dev/null | grep -i mss | sed 's/^/ /'
iptables -t mangle -L -n 2>/dev/null | grep -i mss | sed 's/^/ /'
else
echo " [跳过] 没有 iptables"
fi
echo " 3) 接口 MTU 保持 1500(实测降到 1400 反而掉到 50%)"
ip link set dev wlan0 mtu 1500 2>/dev/null || true
echo " wlan0 mtu = $(cat /sys/class/net/wlan0/mtu 2>/dev/null)"
echo " 3) 接口 MTU -> $MTU_VALUE(这一条是给 QUIC 用的,见文件头说明)"
if command -v ip >/dev/null 2>&1; then
CUR=$(cat "/sys/class/net/$IFACE/mtu" 2>/dev/null || echo "?")
if [ "$CUR" = "$MTU_VALUE" ]; then
echo " $IFACE 已经是 $MTU_VALUE,不用改"
elif ip link set dev "$IFACE" mtu "$MTU_VALUE" 2>/dev/null; then
echo " $IFACE 的 MTU: $CUR -> $(cat "/sys/class/net/$IFACE/mtu" 2>/dev/null)"
else
echo " ⚠ 改 $IFACE 的 MTU 失败(权限或网卡不支持),继续"
fi
else
echo " [跳过] 没有 ip 命令"
fi
# 持久化:wlan0 归 NetworkManager 管,光用 ip 改重启就丢。
# 写进 NM 自己的连接配置,比塞 rc.local 干净。
if command -v nmcli >/dev/null 2>&1; then
CONN=$(nmcli -t -f NAME,DEVICE connection show --active 2>/dev/null \
| awk -F: -v dev="$IFACE" '$2==dev {print $1; exit}')
if [ -n "$CONN" ]; then
nmcli connection modify "$CONN" 802-11-wireless.mtu "$MTU_VALUE" \
2>/dev/null \
|| nmcli connection modify "$CONN" ethernet.mtu "$MTU_VALUE" \
2>/dev/null || true
echo " 已写进 NetworkManager 连接「$CONN」(重启后仍然生效)"
fi
fi
# ════════════════════════════════════════════════════════════════
# 4) 关键:让已经在跑的隧道重建连接
# ════════════════════════════════════════════════════════════════
# MSS 夹取只对**新建连接**生效(它改的是 SYN 包里的 MSS 选项)。
# 隧道如果是在规则生效之前建的,它那条 TCP 连接还用着旧 MSS ——
# MSS 和 MTU 只对**新建连接**生效(MSS 改的是 SYN 包里的选项,
# MTU 影响的是新建的 socket)。隧道如果建在规则生效之前,
# 它那条连接还带着旧参数 ——
# 实测:隧道 17:01 建、规则 17:22 生效,可用率一直卡在 76%;
# 重启隧道之后立刻回到 100%。
#
# 开机时不会走到这一步(本服务 Before=lanremote-tunnel.service,
# 规则先于隧道生效);但**手动重跑本脚本**时需要重建,
# 开机时不会走到这一步(nettune 服务 Before=lanremote-tunnel.service,
# 规则先于隧道生效);但**手动重跑本脚本**时必须重建,
# 否则改了等于没改。
if systemctl is-active --quiet lanremote-tunnel 2>/dev/null; then
echo " 4) 隧道正在运行,它用的是旧 MSS —— 重启让它重建连接"
systemctl restart lanremote-tunnel
echo " 已重启(新连接会用上 MSS=$MSS_VALUE)"
else
echo " 4) 隧道未运行,跳过重建(开机时本服务先于它执行)"
fi
echo " 4) 让已经建立的隧道用上新参数"
RESTARTED=0
for svc in lanremote-tunnel lanremote-tunnel-h2; do
if command -v systemctl >/dev/null 2>&1 \
&& systemctl is-active --quiet "$svc" 2>/dev/null; then
echo " $svc 正在跑,重启它让新参数生效"
systemctl restart "$svc" >/dev/null 2>&1 && RESTARTED=1
fi
done
[ "$RESTARTED" = "0" ] && echo " 没有正在跑的隧道,跳过"
echo " 网络调优已应用(MSS=$MSS_VALUE)"
echo " 网络调优已应用(MSS=$MSS_VALUE, MTU=$MTU_VALUE, 网卡=$IFACE)"