服务端 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
+82
View File
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
r'''把新增的 .sh 规范化成「LF + UTF-8 无 BOM」,并做静态检查。
为什么必须检查:
仓库里所有 .sh 都是 LF + 无 BOM。一旦混进 CRLF,
在 Linux 上跑会报 `bash: $'\r': command not found`,
而且报错位置会很莫名(通常是 `#!/bin/bash\r` 那一行)。
.bat 才用 GBK+CRLF,别搞混。
用法:
python _build/fix_sh_format.py # 检查并修
python _build/fix_sh_format.py --check # 只检查不改
'''
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
TARGETS = [
ROOT / "relay" / "deploy.sh",
ROOT / "relay" / "deploy-tune.sh",
]
BASH = r"C:\Program Files\Git\bin\bash.exe"
def main() -> int:
check_only = "--check" in sys.argv
bad = 0
for path in TARGETS:
if not path.is_file():
print(" [缺] %s" % path.name)
bad += 1
continue
raw = path.read_bytes()
crlf = raw.count(b"\r\n")
lone_cr = raw.count(b"\r") - crlf
bom = raw.startswith(b"\xef\xbb\xbf")
print(" %-18s CRLF=%-4d 单独CR=%-3d BOM=%s"
% (path.name, crlf, lone_cr, "有" if bom else "无"))
if crlf or lone_cr or bom:
if check_only:
print(" -> 需要修")
bad += 1
else:
data = raw.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
if bom:
data = data[3:]
path.write_bytes(data)
print(" -> 已修成 LF + 无 BOM")
else:
print(" -> 格式正确")
print()
print(" 语法检查(bash -n):")
for path in TARGETS:
if not path.is_file():
continue
proc = subprocess.run(
[BASH, "-n", str(path).replace("\\", "/")],
capture_output=True, text=True, encoding="utf-8",
errors="replace")
if proc.returncode == 0:
print(" ✓ %s" % path.name)
else:
print(" ✗ %s" % path.name)
print(" " + (proc.stderr or "").strip()[:400])
bad += 1
print()
if bad:
print(" 有 %d 个问题" % bad)
return 1
print(" 全部通过")
return 0
if __name__ == "__main__":
sys.exit(main())