mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 11:20:43 +00:00
用户要求:服务器端文件里放一个 .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
222 lines
8.0 KiB
Bash
222 lines
8.0 KiB
Bash
#!/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
|