Files
ykxmz/_build/test_zip_deploy.sh
T
xmz7891 facb156aca 服务端 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
2026-10-07 17:28:09 +08:00

222 lines
8.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ════════════════════════════════════════════════════════════════
# 端到端验证:拿**真正打包出来的**服务器端 zip 解压,在里面跑 deploy.sh
# ════════════════════════════════════════════════════════════════
#
# 为什么还要单独测这个:
# 前面的 test_deploy_sh.sh 是拿仓库里的 relay/ 目录跑的。
# 但用户拿到的是 zip —— 有没有可能打包漏了文件、
# 或者 zip 里权限/换行变了?只有真的解压出来跑一遍才知道。
#
# 用法: bash _build/test_zip_deploy.sh [版本号]
# ════════════════════════════════════════════════════════════════
set -u
REPO=$(cd "$(dirname "$0")/.." && pwd)
VERSION=${1:-1.8.2}
ZIP="$REPO/dist/ykxmz-server-$VERSION.zip"
PASS=0
FAIL=0
ok() { PASS=$((PASS + 1)); printf " [PASS] %s\n" "$1"; }
bad() { FAIL=$((FAIL + 1)); printf " [FAIL] %s\n" "$1"; }
check(){ if [ "$2" = "$3" ]; then ok "$1"; else bad "$1(期望 $3,实际 $2)"; fi; }
echo "============================================================"
echo " 从发布 zip 端到端验证 deploy.sh v$VERSION"
echo "============================================================"
echo " zip: $ZIP"
if [ ! -f "$ZIP" ]; then
echo " 找不到 zip —— 先跑 python _build/package_remote.py"
exit 1
fi
echo " 大小: $(stat -c %s "$ZIP" 2>/dev/null || echo '?') 字节"
TMP=$(mktemp -d)
echo " 解压到: $TMP"
# ────────────────────────────────────────────────────────────
echo
echo "── 1. zip 里必须有一键部署脚本 ──"
python - "$ZIP" <<'PYEOF'
import sys, zipfile
from pathlib import Path
z = zipfile.ZipFile(sys.argv[1])
names = z.namelist()
need = ["deploy.sh", "deploy-tune.sh", "relay.py", "admin.py",
"landing.py", "showcase.py", "favicon.ico",
"site_shots/pc-main.png", "watchdog.sh", "net-tune.sh",
"README-server.txt"]
bad = 0
for n in need:
hit = [x for x in names if x.endswith("/" + n) or x.endswith(n)]
if hit:
print(" [PASS] zip 含 %s" % n)
else:
print(" [FAIL] zip 缺 %s" % n)
bad += 1
print(" ── 共 %d 个条目 ──" % len(names))
sys.exit(1 if bad else 0)
PYEOF
if [ $? -eq 0 ]; then PASS=$((PASS + 11)); else FAIL=$((FAIL + 1)); fi
# ────────────────────────────────────────────────────────────
echo
echo "── 2. 解压 ──"
if command -v unzip >/dev/null 2>&1; then
unzip -q "$ZIP" -d "$TMP"
else
python -c "import zipfile,sys; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])" \
"$ZIP" "$TMP"
fi
SRV=$(find "$TMP" -maxdepth 2 -type d -name "ykxmz-server-*" | head -1)
if [ -z "$SRV" ]; then
bad "解压后找不到 ykxmz-server-* 目录"
exit 1
fi
ok "解压出目录 $(basename "$SRV")"
# ────────────────────────────────────────────────────────────
echo
echo "── 3. 解压出来的 .sh 换行符 / 权限 ──"
python - "$SRV" <<'PYEOF'
import sys
from pathlib import Path
d = Path(sys.argv[1])
for name in ("deploy.sh", "deploy-tune.sh"):
p = d / name
if not p.is_file():
print(" [FAIL] 没有 %s" % name); continue
raw = p.read_bytes()
crlf = raw.count(b"\r\n")
bom = raw.startswith(b"\xef\xbb\xbf")
if crlf == 0 and not bom:
print(" [PASS] %s 是 LF 且无 BOM" % name)
else:
print(" [FAIL] %s CRLF=%d BOM=%s" % (name, crlf, bom))
PYEOF
if [ $? -eq 0 ]; then PASS=$((PASS + 2)); fi
# ────────────────────────────────────────────────────────────
echo
echo "── 4. 从解压目录跑 --help ──"
HELP=$(cd "$SRV" && bash deploy.sh --help 2>&1)
check "--help 退出码为 0" "$?" "0"
if echo "$HELP" | grep -q '用法'; then ok "--help 有用法说明"; else bad "--help 没用法"; fi
if echo "$HELP" | grep -q 'v1\.8\.'; then ok "--help 显示了版本号(从 relay.py 读的)"; else bad "--help 版本号没读出来"; fi
# ────────────────────────────────────────────────────────────
echo
echo "── 5. 从解压目录做一次沙箱部署 ──"
STUB="$TMP/stub"
SANDBOX="$TMP/sandbox"
STUB_LOG="$TMP/stub.log"
mkdir -p "$STUB"; : > "$STUB_LOG"
export STUB_LOG
REAL_PY=$(command -v python 2>/dev/null || true)
mk() { cat > "$STUB/$1"; chmod +x "$STUB/$1"; }
mk systemctl <<'EOF'
#!/bin/bash
echo "systemctl $*" >> "$STUB_LOG"
case "$1" in is-active) echo active;; is-enabled) echo enabled;; esac
exit 0
EOF
mk ip <<'EOF'
#!/bin/bash
echo "ip $*" >> "$STUB_LOG"
[ "$1" = "-4" ] && echo "default via 192.168.1.1 dev wlan0 proto dhcp"
exit 0
EOF
mk iptables <<'EOF'
#!/bin/bash
echo "iptables $*" >> "$STUB_LOG"
RULES="$STUB_LOG.rules"; touch "$RULES"
if [ "${1:-}" = "-t" ] && [ "${3:-}" = "-L" ]; then
while read -r r; do [ -n "$r" ] && echo "TCPMSS tcp -- $r"; done < "$RULES"
exit 0
fi
chain=""; mss=""; action=""; prev=""
for a in "$@"; do
case "$prev" in -C|-D|-A) chain="$a";; --set-mss) mss="$a";; esac
case "$a" in -C|-D|-A) [ -z "$action" ] && action="$a";; esac
prev="$a"
done
key="$chain:$mss"
case "$action" in
-C) grep -qxF "$key" "$RULES" 2>/dev/null && exit 0 || exit 1 ;;
-A) grep -qxF "$key" "$RULES" 2>/dev/null || echo "$key" >> "$RULES"; exit 0 ;;
-D) grep -qxF "$key" "$RULES" 2>/dev/null || exit 1
grep -vxF "$key" "$RULES" > "$RULES.t" 2>/dev/null; mv "$RULES.t" "$RULES"; exit 0 ;;
esac
exit 0
EOF
mk cloudflared <<'EOF'
#!/bin/bash
[ "$1" = "--version" ] && { echo "cloudflared version 2026.9.3 (stub)"; exit 0; }
echo "cloudflared $*" >> "$STUB_LOG"; exit 0
EOF
mk curl <<'EOF'
#!/bin/bash
for a in "$@"; do
case "$a" in *v1/health*) echo '{"ok": true}'; exit 0;; esac
done
exit 0
EOF
mk free <<'EOF'
#!/bin/bash
echo " total used free"
echo "Mem: 460 131 144"
EOF
mk nmcli <<'EOF'
#!/bin/bash
exit 0
EOF
mk getent <<'EOF'
#!/bin/bash
exit 0
EOF
[ -n "$REAL_PY" ] && mk python3 <<EOF
#!/bin/bash
exec "$REAL_PY" "\$@"
EOF
export PATH="$STUB:$PATH"
OUT="$TMP/run.log"
(cd "$SRV" && bash deploy.sh --prefix "$SANDBOX" --skip-tunnel) > "$OUT" 2>&1
RC=$?
check "沙箱部署退出码为 0" "$RC" "0"
if [ "$RC" != "0" ]; then tail -20 "$OUT" | sed 's/^/ /'; fi
REL="$SANDBOX/root/lanremote-relay"
UNIT="$SANDBOX/etc/systemd/system"
for f in relay.py admin.py landing.py showcase.py favicon.ico; do
if [ -f "$REL/$f" ]; then ok "部署了 $f"; else bad "没部署 $f"; fi
done
[ -f "$REL/site_shots/pc-main.png" ] && ok "部署了截图" || bad "没部署截图"
for u in lanremote-relay lanremote-nettune lanremote-tunnel lanremote-tunnel-h2; do
[ -f "$UNIT/$u.service" ] && ok "生成了 $u.service" || bad "缺 $u.service"
done
echo
echo "── 6. 再跑一遍(幂等)──"
echo "keep" > "$REL/data/sentinel.txt"
(cd "$SRV" && bash deploy.sh --prefix "$SANDBOX" --skip-tunnel) > "$TMP/run2.log" 2>&1
check "第二次退出码为 0" "$?" "0"
check "哨兵文件还在" "$(cat "$REL/data/sentinel.txt" 2>/dev/null)" "keep"
echo
echo "============================================================"
echo " 结果: 通过 $PASS 项,失败 $FAIL 项"
echo "============================================================"
if [ "$FAIL" = "0" ]; then
rm -rf "$TMP"
exit 0
else
echo " 临时目录保留: $TMP"
exit 1
fi