#!/bin/bash # ════════════════════════════════════════════════════════════════ # 真机沙箱测试:在**真实的 Debian/aarch64 设备**上跑 deploy.sh, # 但把所有写入关进 /root/_dtest/sandbox,并且用桩命令挡住 # systemd / iptables / ip / sysctl —— 绝不能碰正在服务的线上环境。 # ════════════════════════════════════════════════════════════════ # # 为什么要单独在真机上测: # 开发机是 Windows/Git Bash。它能验证语法、参数、幂等、 # 生成的 unit 内容,但验不了: # · 真实 sed/awk/grep 的行为(GNU vs BSD 有差异) # · 真实的 /proc 读取、python3 -m py_compile # · 真实文件权限、路径长度、换行处理 # 这一轮把这些补上。 # # 为什么必须用桩挡 systemd/iptables/ip/sysctl: # --prefix 只隔离**文件写入**,不隔离**命令副作用**。 # 如果不挡,`systemctl restart lanremote-relay` 会真的把 # 线上中转重启一遍,`iptables` 会真的改线上规则。 # # 用法:把这个脚本和 relay/ 目录一起放在 /root/_dtest/ 下, # 然后 bash /root/_dtest/test_deploy_device.sh # ════════════════════════════════════════════════════════════════ set -u BASE=/root/_dtest SRC="$BASE/relay" BIN="$BASE/bin" SANDBOX="$BASE/sandbox" LOG="$BASE/stub.log" 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 " 真机沙箱测试 · $(uname -m) · $(uname -r)" echo "============================================================" echo " 系统: $(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME")" echo " bash: $(bash --version | head -1)" [ -f "$SRC/deploy.sh" ] || { echo " 找不到 $SRC/deploy.sh"; exit 1; } # ──────────────────────────────────────────────────────────── echo echo "── 0. 先记录线上现状(测完要确认没被碰过)──" # ⚠ 必须用**绝对路径**抓真实命令。 # 后面会把桩目录插到 PATH 最前面,那时 `systemctl` / `iptables` # 就都变成桩了;如果第 8 节还按名字调用,比的就是 # "真实系统 vs 桩",必然对不上(真踩过)。 REAL_SYSTEMCTL=$(command -v systemctl 2>/dev/null || echo systemctl) REAL_IPTABLES=$(command -v iptables 2>/dev/null || echo iptables) echo " 真实 systemctl: $REAL_SYSTEMCTL" echo " 真实 iptables: $REAL_IPTABLES" LIVE_RELAY_ACTIVE=$("$REAL_SYSTEMCTL" is-active lanremote-relay 2>/dev/null | head -1 || echo "?") LIVE_TUNNEL_ACTIVE=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel 2>/dev/null | head -1 || echo "?") LIVE_TUNNEL2_ACTIVE=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel-h2 2>/dev/null | head -1 || echo "?") LIVE_MTU=$(cat /sys/class/net/wlan0/mtu 2>/dev/null || echo "?") LIVE_MSS=$(("$REAL_IPTABLES" -t mangle -S OUTPUT 2>/dev/null || true) | grep -c TCPMSS | head -1) LIVE_MSS=${LIVE_MSS:-0} LIVE_KEEPALIVE=$(cat /proc/sys/net/ipv4/tcp_keepalive_time 2>/dev/null || echo "?") echo " lanremote-relay = $LIVE_RELAY_ACTIVE" echo " lanremote-tunnel = $LIVE_TUNNEL_ACTIVE" echo " lanremote-tunnel-h2 = $LIVE_TUNNEL2_ACTIVE" echo " wlan0 MTU = $LIVE_MTU" echo " MSS 规则数 = $LIVE_MSS" echo " keepalive_time = $LIVE_KEEPALIVE" # ──────────────────────────────────────────────────────────── echo echo "── 1. 搭桩命令 ──" mkdir -p "$BIN" "$SANDBOX" : > "$LOG" export STUB_LOG="$LOG" cat > "$BIN/systemctl" <<'EOF' #!/bin/bash echo "systemctl $*" >> "$STUB_LOG" STATE="$STUB_LOG.state" touch "$STATE" SVC="" for a in "$@"; do case "$a" in -*) continue ;; is-active|is-enabled|start|stop|restart|reload|enable|disable|status|daemon-reload|mask|unmask) continue ;; *) SVC="$a" ;; esac done case "$1" in is-active) if [ -n "$SVC" ] && grep -qxF "$SVC" "$STATE" 2>/dev/null; then echo "active"; exit 0 fi echo "inactive"; exit 3 ;; is-enabled) echo "enabled"; exit 0 ;; start|restart) [ -n "$SVC" ] && { grep -qxF "$SVC" "$STATE" 2>/dev/null || echo "$SVC" >> "$STATE"; } exit 0 ;; *) exit 0 ;; esac EOF cat > "$BIN/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 cat > "$BIN/ip" <<'EOF' #!/bin/bash echo "ip $*" >> "$STUB_LOG" [ "${1:-}" = "-4" ] && echo "default via 192.168.10.1 dev wlan0 proto dhcp metric 600" exit 0 EOF cat > "$BIN/sysctl" <<'EOF' #!/bin/bash echo "sysctl $*" >> "$STUB_LOG" exit 0 EOF cat > "$BIN/nmcli" <<'EOF' #!/bin/bash echo "nmcli $*" >> "$STUB_LOG" exit 0 EOF chmod +x "$BIN"/* echo " 桩命令: $(ls "$BIN" | tr '\n' ' ')" echo " (没有桩 curl —— 健康检查走真实 curl,只读无副作用)" export PATH="$BIN:$PATH" echo " PATH 首位已切到桩目录" # ──────────────────────────────────────────────────────────── echo echo "── 2. 语法检查(真机 bash)──" for f in deploy.sh deploy-tune.sh net-tune.sh; do if bash -n "$SRC/$f" 2>/tmp/synerr; then ok "$f 语法正确" else bad "$f 语法错误: $(head -2 /tmp/synerr)" fi done # ──────────────────────────────────────────────────────────── echo echo "── 3. --help(真机跑)──" H=$(bash "$SRC/deploy.sh" --help 2>&1) check "--help 退出码 0" "$?" "0" echo "$H" | grep -q 'quic / http2 / both' \ && ok "--help 讲清了 --protocol 三个取值" || bad "--help 没讲 --protocol" echo "$H" | grep -q '8 条' \ && ok "--help 写明了配比" || bad "--help 没写配比" echo "$H" | grep -q '没有任何默认域名' \ && ok "--help 声明了没有默认域名" || bad "--help 没声明默认域名策略" # ──────────────────────────────────────────────────────────── echo echo "── 4. 第一次部署(沙箱 + --skip-tunnel)──" printf 'lanremote-relay\n' > "$STUB_LOG.state" OUT1="$BASE/run1.log" bash "$SRC/deploy.sh" --prefix "$SANDBOX" --skip-tunnel > "$OUT1" 2>&1 check "第一次退出码 0" "$?" "0" REL="$SANDBOX/root/lanremote-relay" UNIT="$SANDBOX/etc/systemd/system" for f in relay.py admin.py landing.py showcase.py favicon.ico deploy-tune.sh net-tune.sh; do [ -f "$REL/$f" ] && ok "部署了 $f" || bad "没部署 $f" 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 " ── 真机生成的隧道单元 ExecStart ──" grep -E '^ExecStart=' "$UNIT/lanremote-tunnel.service" | sed 's/^/ /' grep -E '^ExecStart=' "$UNIT/lanremote-tunnel-h2.service" | sed 's/^/ /' if grep -q -- '--protocol quic' "$UNIT/lanremote-tunnel.service" \ && grep -q -- '--ha-connections 8' "$UNIT/lanremote-tunnel.service" \ && grep -q -- '--metrics 127.0.0.1:20241' "$UNIT/lanremote-tunnel.service"; then ok "主隧道 = quic + 8 + metrics 20241" else bad "主隧道参数不对" fi if grep -q -- '--protocol http2' "$UNIT/lanremote-tunnel-h2.service" \ && grep -q -- '--ha-connections 2' "$UNIT/lanremote-tunnel-h2.service" \ && grep -q -- '--metrics 127.0.0.1:20242' "$UNIT/lanremote-tunnel-h2.service"; then ok "副本隧道 = http2 + 2 + metrics 20242" else bad "副本隧道参数不对" fi # ──────────────────────────────────────────────────────────── echo echo "── 5. 幂等:塞哨兵再跑第二遍 ──" echo '{"allowNew":false,"codes":[{"deviceId":"x","code":"789113"}]}' \ > "$REL/data/relay_state.json" S1=$(md5sum "$REL/data/relay_state.json" | awk '{print $1}') echo "keepme" > "$REL/data/devices.json" : > "$STUB_LOG" printf 'lanremote-relay\n' > "$STUB_LOG.state" bash "$SRC/deploy.sh" --prefix "$SANDBOX" --skip-tunnel > "$BASE/run2.log" 2>&1 check "第二次退出码 0" "$?" "0" S2=$(md5sum "$REL/data/relay_state.json" | awk '{print $1}') check "relay_state.json 没被改(allowNew 保住)" "$S2" "$S1" check "devices.json 没被冲掉" "$(cat "$REL/data/devices.json")" "keepme" grep -qxF 'systemctl restart lanremote-relay' "$STUB_LOG" 2>/dev/null \ && bad "无变化却重启了中转" || ok "无变化时没有重启中转" # ──────────────────────────────────────────────────────────── echo echo "── 6. 双隧道默认都启用 ──" CFD="$SANDBOX/root/.cloudflared" mkdir -p "$CFD" echo '{"TunnelID":"deadbeef-0000-1111-2222-333344445555"}' \ > "$CFD/deadbeef-0000-1111-2222-333344445555.json" : > "$STUB_LOG" printf 'lanremote-relay\n' > "$STUB_LOG.state" bash "$SRC/deploy.sh" --prefix "$SANDBOX" --hostname relay.test.example \ > "$BASE/run3.log" 2>&1 check "带域名部署退出码 0" "$?" "0" grep -qxF 'systemctl enable lanremote-tunnel' "$STUB_LOG" \ && ok "启用了主隧道" || bad "没启用主隧道" grep -qxF 'systemctl enable lanremote-tunnel-h2' "$STUB_LOG" \ && ok "启用了副本隧道" || bad "没启用副本隧道(应该双跑)" echo echo "── 6b. --protocol http2 只跑副本 ──" : > "$STUB_LOG" printf 'lanremote-relay\n' > "$STUB_LOG.state" bash "$SRC/deploy.sh" --prefix "$SANDBOX" --protocol http2 \ > "$BASE/run4.log" 2>&1 check "--protocol http2 退出码 0" "$?" "0" grep -qxF 'systemctl enable lanremote-tunnel-h2' "$STUB_LOG" \ && ok "启用了副本" || bad "没启用副本" grep -qxF 'systemctl disable --now lanremote-tunnel' "$STUB_LOG" \ && ok "停用了主隧道" || bad "没停用主隧道" # ──────────────────────────────────────────────────────────── echo echo "── 7. 真机健康检查(真 curl,不是桩)──" if curl -s --max-time 8 http://127.0.0.1:8080/v1/health 2>/dev/null | grep -q '"ok"'; then ok "线上中转的 /v1/health 通" else bad "线上中转没响应" fi # ──────────────────────────────────────────────────────────── echo echo "── 8. 确认线上环境一点没被碰(用真实命令查,不用桩)──" NOW_RELAY=$("$REAL_SYSTEMCTL" is-active lanremote-relay 2>/dev/null | head -1 || echo "?") NOW_TUNNEL=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel 2>/dev/null | head -1 || echo "?") NOW_TUNNEL2=$("$REAL_SYSTEMCTL" is-active lanremote-tunnel-h2 2>/dev/null | head -1 || echo "?") NOW_MTU=$(cat /sys/class/net/wlan0/mtu 2>/dev/null || echo "?") NOW_MSS=$(("$REAL_IPTABLES" -t mangle -S OUTPUT 2>/dev/null || true) | grep -c TCPMSS | head -1) NOW_MSS=${NOW_MSS:-0} NOW_KA=$(cat /proc/sys/net/ipv4/tcp_keepalive_time 2>/dev/null || echo "?") echo " 现在: relay=$NOW_RELAY tunnel=$NOW_TUNNEL h2=$NOW_TUNNEL2 MTU=$NOW_MTU MSS=$NOW_MSS KA=$NOW_KA" check "lanremote-relay 状态没变" "$NOW_RELAY" "$LIVE_RELAY_ACTIVE" check "lanremote-tunnel 状态没变" "$NOW_TUNNEL" "$LIVE_TUNNEL_ACTIVE" check "lanremote-tunnel-h2 状态没变" "$NOW_TUNNEL2" "$LIVE_TUNNEL2_ACTIVE" check "wlan0 MTU 没变" "$NOW_MTU" "$LIVE_MTU" check "MSS 规则数没变" "$NOW_MSS" "$LIVE_MSS" check "keepalive 没变" "$NOW_KA" "$LIVE_KEEPALIVE" # ──────────────────────────────────────────────────────────── echo echo "── 9. 清理 ──" rm -rf "$BASE" echo " 已删除 $BASE" echo " 线上中转再确认: $(curl -s --max-time 8 http://127.0.0.1:8080/v1/health 2>/dev/null | head -c 70)" echo echo "============================================================" echo " 结果: 通过 $PASS 项,失败 $FAIL 项" echo "============================================================" [ "$FAIL" = "0" ] && exit 0 || exit 1