服务端 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
+96 -48
View File
@@ -241,7 +241,7 @@ AGENT_README = f"""========================================================
RELAY_README = f"""========================================================
服务器端(随身 WiFi / Linux) 版本 {VERSION}
========================================================
========================================================
作用:让**不在同一个网络**的电脑和手表连上。
@@ -253,22 +253,45 @@ RELAY_README = f"""========================================================
内存占用约 20MB,加 cloudflared 共约 60MB —— 512MB 的小机器跑得动。
【装】
【装:一条命令,全自动】
把本文件夹拷到设备上,然后:
bash install.sh
bash deploy.sh
它会:
1. 检查 python3 / cloudflared(没有就下载)
2. 装两个 systemd 服务并设为开机自启:
lanremote-relay 中转服务
lanremote-tunnel Cloudflare 隧道
3. 打印公网地址和管理页地址
它会一口气做完这些,不用你一步步敲:
1. 检查并安装 cloudflared(已经装了就用现成的)
2. 把中转程序装到 /root/lanremote-relay/
3. 网络调优(keepalive / MSS / MTU / 开机加速)
4. 注册并启动 4 个 systemd 服务(开机自启)
5. 配好 Cloudflare 隧道
6. 自检,然后把**公网地址**打印出来
── 三种常见情形 ──
① 机器上已经有隧道凭据(/root/.cloudflared/*.json)
直接: bash deploy.sh
② 有 Cloudflare 给的隧道 Token
TUNNEL_TOKEN=eyJhIjoi... bash deploy.sh
(用环境变量而不是 --token,Token 就不会留在 shell 历史里)
③ 换域名 / 只装中转不要隧道
bash deploy.sh --hostname relay.你的域名
bash deploy.sh --skip-tunnel
看全部参数: bash deploy.sh --help
**重跑 deploy.sh = 升级**,不会把已有配置冲掉:
设备数据、配对码、管理密码、隧道凭据、手工改过的 config.yml
都会原样保留。
【常用命令】
lanremote-status 看整体状态(服务 / 端口 / 公网 / 内存)
lanremote-url 打印当前公网地址
lanremote-key 打印管理钥匙
systemctl status lanremote-relay
@@ -276,76 +299,86 @@ RELAY_README = f"""========================================================
journalctl -u lanremote-relay -f
【关于 install.sh(旧版脚本,仍在包里)】
装匿名快速隧道,公网地址每次重启都变。
新机器建议用 deploy.sh(具名隧道,地址固定)。
install.sh 保留是为了兼容已经用它装过的机器。
【地址老变怎么办?记住一个局域网地址就行】
匿名隧道的公网地址每次重启都会变。查当前地址:
浏览器打开 http://<随身WiFi的IP>:8080/url
比如 http://192.168.2.156:8080/url
它会直接返回当前的公网地址,纯文本,不用登录。
下面还有两个命令(SSH 上去用):
lanremote-url 打印当前公网地址
systemctl restart lanremote-tunnel 换个地址(超时的时候用)
具名隧道(deploy.sh 装的)地址是固定的,不用管这个。
【管理】
管理页: <公网地址>/?key=<lanremote-key 的输出>
管理页顶部也会大字显示"当前公网地址"。
管理密码:120830(存在 data/admin_password.txt,可改)
官网: <公网地址>/
管理页: <公网地址>/?key=<lanremote-key 的输出>
管理密码: 120830(存在 data/admin_password.txt,可改)
能看到:设备列表、在线状态、配对码、流量、会话数
能操作:踢会话、抹掉配对码绑定
【重要:这几个参数都是实测调出来的】
【网络调优:这些参数都是实测调出来的】
install.sh 里给 cloudflared 加了:
--edge-ip-version 4 只走 IPv4
--ha-connections 1 并发连接数
--retries 5 连边缘失败时多重试
deploy-tune.sh 会做这五件事,改动之前先看它开头的说明:
为什么(都是踩出来的,不是拍脑袋):
① TCP keepalive 30/10/3
这条 4G 线路会**静默丢连接**(不发明文 FIN/RST)。
Linux 默认要 7200 秒才发现死链,表现就是"过几分钟突然 502"。
改成 30 秒首发探测之后,最快 60 秒就能发现并重建。
① IPv6:这条线路**分配了 IPv6 地址但实际不通**,
而 Cloudflare 边缘默认优先 IPv6,会导致建隧道一直超时
(日志里是 context deadline exceeded)。
② tcp_mtu_probing = 1
接口写着 MTU 1500,但实际路径扛不住(1472 通、1500 全丢)。
不打开探测的话,新建 TLS 连接约 30% 失败。
② 协议:**用默认的 QUIC,不要写 --protocol http2**。
实测这条线路把 HTTP/2 用的 TCP 7844 挡了:
TLS handshake with edge error: read tcp ...:7844: i/o timeout
而 QUIC 走 UDP 7844 是通的。
(早期版本这里误写成 --protocol http2,隧道完全起不来。)
③ MSS 钳制到 1360
只限制 TCP 段,不动链路层。
实测:不夹 87%,夹到 1360/1400 都是 100%。
③ 连接数:用 1(默认是 4)。这条线路对并发 UDP 很敏感:
链路好时 ha=4 只成功注册 1 条(9/10),ha=2 是 10/10;
链路退化时 ha=2 掉到 0/10(一条失败重试把另一条也拖下水),
而 ha=1 是 15/15 + 10/10(100%)。
多一条连接不是多一份冗余,而是多一份争抢。
冗余交给看门狗(每 30 秒查一次,断了就重启)更靠谱。
④ 接口 MTU 1400
**这一条是专门给 QUIC 隧道的**:MSS 钳制走 iptables 的
TCPMSS 目标,只对 TCP 生效;QUIC 跑 UDP,完全不受它保护。
QUIC 的包超过路径 MTU 就会被丢,而 UDP 丢包不回 ICMP,
只能干等握手超时(日志里那句
"failed to dial to edge with quic: timeout: no recent network activity")。
实测把 MTU 从 1500 降到 1400:建齐 6 条连接 28 秒 -> 13 秒。
换了网络环境可以试着去掉这些参数用默认值。
⑤ mask 掉 systemd-networkd-wait-online
这台机器 wlan0 归 NetworkManager 管,
systemd-networkd-wait-online 傻等一个不归它的网络,
干等 2 分钟超时,把 relay / tunnel 全拖住。
实测:掉电重启后公网恢复 2 分 10 秒 -> 24 秒。
【关于隧道地址会变】
【隧道的两套配置】
默认用的是 Cloudflare **匿名快速隧道**,地址每次重启会变。
想固定地址要用**具名隧道**(需要你自己的域名):
deploy.sh 会装两份隧道服务,但**只启用一份** ——
两条隧道同时跑会互相争抢,实测反而更不稳。
cloudflared tunnel login # 浏览器点授权
cloudflared tunnel create lanremote
cloudflared tunnel route dns lanremote relay.你的域名
# 然后把 lanremote-tunnel.service 的 ExecStart 换成:
# cloudflared tunnel --no-autoupdate run lanremote
lanremote-tunnel 主用(默认 quic)
lanremote-tunnel-h2 备用(http2)
具名隧道不受匿名隧道限流,稳定性完全不是一个级别。
想切换:
systemctl disable --now lanremote-tunnel
systemctl enable --now lanremote-tunnel-h2
为什么默认 quic:尾部延迟好得多(实测 P99 2.99 秒 vs 13.9 秒)。
什么时候换 http2:这条线路对 UDP 特别不友好的时候
(日志里大量 "Failed to dial a quic connection")。
【省内存】
默认给两个服务各设了 MemoryMax=128M。
默认给服务设了 MemoryMax(relay 128M / tunnel 160M)。
要再省就把用不上的服务停掉:
systemctl stop mariadb php8.2-fpm apache2
systemctl disable mariadb php8.2-fpm apache2
@@ -490,11 +523,25 @@ def main() -> int:
key=lambda f: f.name)
for src in wanted:
shutil.copy2(src, server_dir / src.name)
# ⚠ 产品截图是个**目录**,上面那个 iterdir() 只收文件、而且不递归,
# 所以 site_shots/ 一直没被打进服务端包 ——
# 装到新机器上官网的截图全是 404。
# 这个是拿「从 zip 解压出来真跑一遍」的端到端测试才发现的。
shots_src = RELAY_SRC / "site_shots"
if shots_src.is_dir():
shutil.copytree(shots_src, server_dir / "site_shots")
else:
print(" [警告] relay/site_shots 不存在,官网截图会缺失")
write_readme(server_dir / "README-server.txt", RELAY_README)
_copy_docs(server_dir)
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh"):
if not (server_dir / must).is_file():
print(f" [警告] 服务器端少了 {must}")
# 一键部署脚本必须进包 —— 用户拿到的 zip 里要有它才能双击/一条命令跑起来
for must in ("deploy.sh", "deploy-tune.sh"):
if not (server_dir / must).is_file():
print(f" [失败] 服务器端少了一键部署脚本 {must}")
return 1
server_zip = dist / f"{server_name}.zip"
n = _zip_dir(server_zip, server_dir, server_name)
results.append(("服务器端", server_zip, n))
@@ -544,7 +591,8 @@ def main() -> int:
return 1
with zipfile.ZipFile(results[1][1]) as archive:
names = archive.namelist()
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh"):
for must in ("relay.py", "install.sh", "watchdog.sh", "net-tune.sh",
"deploy.sh", "deploy-tune.sh"):
if not any(must in n for n in names):
print(f" [失败] 服务器端包里没有 {must}")
return 1