#!/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 < "$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