静态资源交给 Cloudflare 边缘缓存(下载提速 41 倍)

实测基线:
  ykxmz-setup-1.8.1.exe   只下到 237568/15067199 字节就停(17 KB/s)
  ykxmz-watch-1.8.1.apk   201523 字节 / 66.8 秒 = 3 KB/s
  ykxmz-server-1.8.1.zip  214900 字节 / 3.1 秒  = 67 KB/s

原因:relay 给所有响应都发 Cache-Control: no-store,
Cloudflare 完全不缓存 —— 每次下载/每张图都要穿隧道回 4G 源站。

改动:
  /download/*   no-store -> public, max-age=604800, immutable
                文件名带版本号,同一名字内容永不改变,可以长缓存
  /             -> public, max-age=60(挡住刷新重复回源)
  /favicon.ico  已是 max-age=86400,保持不变
  /shot/*       已是 max-age=86400,保持不变
  /v1/*         保持 no-store(接口必须实时)

顺带修两个自己踩的坑:

1. _send() 无条件先发 no-store,再追加 extra 里的 Cache-Control,
   结果是**两个 Cache-Control 头**,客户端只认第一个 ——
   调用方传的缓存策略被吃掉。改成 extra 里有就不再发默认值。

2. do_HEAD 和 do_GET 的缓存头不一致:HEAD 对非 /shot/ 一律报 no-store。
   下载器/代理用 HEAD 探测时会误判文件不可缓存。
   现在 _head_info 给每个路由带上和 GET 完全相同的缓存头,
   do_HEAD 也不再盲目覆盖。

结果(同一个测试口径):
  setup.exe  17 KB/s(截断) -> 695 KB/s 完整 15 MB   41 倍
  watch.apk   3 KB/s         -> 101 KB/s              33 倍
  server.zip 67 KB/s         ->  87 KB/s
  三个文件全部 cf-cache-status=HIT(不再穿隧道回源)

排查过程记一笔:我一开始用 curl -I 验证,
看到的全是 no-store —— 因为 **-I 发的是 HEAD**,
而 HEAD 走的是另一个分支。查了半天才发现是验证方法错了。
This commit is contained in:
xmz7891
2026-10-07 15:44:48 +08:00
parent 46196c3618
commit bf15a2ee61
203 changed files with 1789 additions and 40 deletions
+178
View File
@@ -0,0 +1,178 @@
#!/bin/bash
# quic+6 vs http2+4:用**真实长轮询**对照,方法完全一致。
#
# 为什么必须用长轮询测:
# 之前用 /v1/health 那种"每次新建连接"的方式测,
# 量到的是冷启动开销,和真实业务(复用连接的长轮询)差很多。
# 而且那种方式会看到 18 秒的尾巴 —— 长轮询完全没有。
#
# 为什么这次结果可信:
# 上一轮 A/B 里 quic 那组因为**两次探针用了同一个配对码**,
# 第二次注册被"一个码只能一台设备"规则拒掉,token 拿到空的,
# 8 次全 401 —— 那一轮数据作废。这次每个探针都用唯一的新码。
#
# 为什么值得测 QUIC:
# 刚才查出来:隧道连接 30 分钟 0 掉线、0 重连,很稳;
# 但请求级偶发 000(10 分钟 6 次)。
# 这是 HTTP/2 over TCP 的**队头阻塞** ——
# 一条 TCP 上丢一个包,那条连接上所有流一起卡。
# QUIC 每个流独立恢复,理论上正好治这个。
set -u
PUB=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
SVC=/etc/systemd/system/lanremote-tunnel.service
BACKUP=/root/lanremote-tunnel.service.bak.q.$(date +%s)
cp -a "$SVC" "$BACKUP"
ROUNDS=${1:-10}
set_params() {
local proto="$1" conns="$2"
if grep -q -- '--protocol' "$SVC"; then
sed -i "s/--protocol [a-z0-9]*/--protocol $proto/" "$SVC"
else
sed -i "s|\(ExecStart=.*cloudflared tunnel\)|\1 --protocol $proto|" "$SVC"
fi
if grep -q -- '--ha-connections' "$SVC"; then
sed -i "s/--ha-connections [0-9]*/--ha-connections $conns/" "$SVC"
else
sed -i "s|\(ExecStart=.*cloudflared tunnel\)|\1 --ha-connections $conns|" "$SVC"
fi
systemctl daemon-reload
systemctl restart lanremote-tunnel
}
wait_ready() {
local want="$1"
for i in $(seq 1 30); do
sleep 5
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
[ "${n:-0}" -ge "$want" ] && return 0
done
return 1
}
new_probe() {
# 每次都用全新的 deviceId + 全新的配对码,避免被"一码一机"拒掉
local stamp
stamp=$(date +%s%N | cut -c1-13)
local dev="qprobe-${stamp}"
local code
code=$(printf "%06d" $(( (stamp / 7) % 1000000 )))
local reg
reg=$(curl -s --max-time 30 -X POST "$PUB/v1/agent/register" \
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
-d "{\"deviceId\":\"$dev\",\"code\":\"$code\",\"name\":\"qprobe\",\"version\":\"1.8.1\"}")
local tok
tok=$(echo "$reg" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
PROBE_DEV="$dev"
PROBE_TOK="$tok"
PROBE_CODE="$code"
}
cleanup_probe() {
local key
key=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
[ -n "${PROBE_DEV:-}" ] && curl -s --max-time 20 -X POST \
"http://127.0.0.1:8080/v1/admin/delete" -H "X-Admin: $key" \
-H 'Content-Type: application/json' \
-d "{\"deviceId\":\"$PROBE_DEV\"}" > /dev/null 2>&1
}
run_round() {
local label="$1" proto="$2" conns="$3"
echo "════════ $label ════════"
set_params "$proto" "$conns"
if wait_ready "$conns"; then
echo " 连接就绪($(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') 条)"
else
echo " ! 连接没建齐,仍然继续测"
fi
echo " 落点:"
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep 'server_locations.*} 1' | sed 's/^/ /'
sleep 15
new_probe
if [ -z "${PROBE_TOK:-}" ]; then
echo " [失败] 探针注册没拿到 token,这轮作废"
echo "$label|REGFAIL|0|0|0" >> /tmp/qab.txt
return 1
fi
echo " 探针 code=$PROBE_CODE token=${#PROBE_TOK} 字符"
local mark
mark=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
local ok=0 fail=0 times=() fails=""
for i in $(seq 1 "$ROUNDS"); do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 100 \
-X POST "$PUB/v1/agent/poll" -H "X-Token: $PROBE_TOK" \
-H "User-Agent: $UA")
code=$(echo "$out" | awk '{print $1}')
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
if [ "$code" = "200" ]; then
ok=$((ok+1)); times+=("$ms")
printf " %2d: 200 %6dms\n" "$i" "$ms"
else
fail=$((fail+1)); fails="$fails ${i}:${code}"
printf " %2d: %-4s **失败**\n" "$i" "$code"
fi
done
local stats
if [ "${#times[@]}" -gt 0 ]; then
stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {
printf "中位 %d P90 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[NR]}')
else
stats="无数据"
fi
local reconn
reconn=$(tail -n +$((mark+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
| grep -c 'Registered tunnel connection' || echo 0)
echo " == $ok/$ROUNDS 成功 耗时: $stats ms 本轮重连 $reconn 次"
echo " 失败:$fails"
echo "$label|$ok|$ROUNDS|$((ok*100/ROUNDS))|$stats|$reconn" >> /tmp/qab.txt
cleanup_probe
echo
}
rm -f /tmp/qab.txt
# 先基线,再候选 —— 同一网络条件下先后测,尽量公平
run_round "http2+4" http2 4 || true
run_round "quic+6" quic 6 || true
echo "════════ 汇总 ════════"
printf " %-10s %-10s %-8s %-32s %s\n" "配置" "成功" "可用率" "耗时" "本轮重连"
while IFS='|' read -r label ok total pct stats reconn; do
if [ "$ok" = "REGFAIL" ]; then
printf " %-10s 探针注册失败,作废\n" "$label"
else
printf " %-10s %-10s %-8s %-32s %s\n" "$label" "$ok/$total" "${pct}%" "$stats" "$reconn"
fi
done < /tmp/qab.txt
echo
echo "════════ 决策 ════════"
B_OK=$(grep '^http2+4' /tmp/qab.txt | cut -d'|' -f4)
Q_OK=$(grep '^quic+6' /tmp/qab.txt | cut -d'|' -f4)
B_REC=$(grep '^http2+4' /tmp/qab.txt | cut -d'|' -f6)
Q_REC=$(grep '^quic+6' /tmp/qab.txt | cut -d'|' -f6)
echo " 基线: 可用率 ${B_OK:-?}% 重连 ${B_REC:-?}"
echo " 候选: 可用率 ${Q_OK:-?}% 重连 ${Q_REC:-?}"
if [ "${Q_OK:-0}" -ge "${B_OK:-0}" ] && [ "${Q_OK:-0}" -ge 90 ]; then
echo " → 采用 quic+6"
else
echo " → 保留 http2+4"
cp -a "$BACKUP" "$SVC"
systemctl daemon-reload
systemctl restart lanremote-tunnel
fi
sleep 15
echo
echo " 最终参数: $(grep -oE '\-\-protocol [a-z0-9]+|--ha-connections [0-9]+' "$SVC" | tr '\n' ' ')"
echo " 备份: $BACKUP"
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
# TCP 重传调优的受控 A/B。
#
# 背景(实测):
# TCPTimeouts: 3145 · TCPLostRetransmit: 2010
# 这些多秒/多分钟的尾巴全是 TCP 重传超时造成的。
# 这台机器内核是定制的,**没有 BBR**(只有 reno/cubic),
# 也没有任何替代 qdisc(只有 noqueue),所以只能在
# 现有 cubic 上调 TCP 的重传行为。
#
# 三个候选开关(都对症"细流 + 高丢包"这个场景):
# tcp_thin_linear_timeouts 细流不搞 RTO 指数退避,重传来得更快
# tcp_fack 前向确认,丢包恢复更积极
# tcp_retries2 重传几次就放弃(8 太大,卡十几分钟)
#
# 每轮:设参数 -> 打 60 次 HTTP -> 记录成功率/耗时分布 -> 下一轮
# 全部跑完回滚,最后按数据决定采用哪组。
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
N=60
# 保存原始值,跑完恢复
declare -A ORIG
for k in tcp_thin_linear_timeouts tcp_fack tcp_retries2 tcp_autocorking; do
ORIG[$k]=$(sysctl -n net.ipv4.$k 2>/dev/null || echo "")
done
restore() {
for k in "${!ORIG[@]}"; do
[ -n "${ORIG[$k]}" ] && sysctl -qw net.ipv4.$k="${ORIG[$k]}" 2>/dev/null || true
done
}
measure() {
local label="$1"
local ok=0 fail=0 times=()
for i in $(seq 1 "$N"); do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \
-H "User-Agent: $UA" "$BASE/v1/health")
c=$(echo "$out" | awk '{print $1}')
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
if [ "$c" = "200" ]; then ok=$((ok+1)); times+=("$ms"); else fail=$((fail+1)); fi
done
local pct=$((ok * 100 / N))
local stats="无数据"
if [ "${#times[@]}" -gt 0 ]; then
stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {
printf "中位 %d P90 %d P99 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}')
fi
printf " %-34s %3d%% (%d/%d) %s ms\n" "$label" "$pct" "$ok" "$N" "$stats"
echo "$label|$pct|$ok|$stats" >> /tmp/tcpab.txt
}
rm -f /tmp/tcpab.txt
echo "════════ 原始值 ════════"
for k in "${!ORIG[@]}"; do printf " %-30s = %s\n" "$k" "${ORIG[$k]}"; done
echo
echo "════════ 各组合实测(每轮 $N 次)════════"
echo " [1/6] 基线(不改)"
measure "基线"
echo " [2/6] thin_linear_timeouts=1"
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
measure "thin=1"
echo " [3/6] thin=1 + fack=1"
sysctl -qw net.ipv4.tcp_fack=1
measure "thin=1 fack=1"
echo " [4/6] thin=1 fack=1 + retries2=5"
sysctl -qw net.ipv4.tcp_retries2=5
measure "thin=1 fack=1 retries2=5"
echo " [5/6] thin=1 fack=1 retries2=5 + autocorking=0"
sysctl -qw net.ipv4.tcp_autocorking=0
measure "全部开启"
echo " [6/6] 回滚到基线再测一次(排除时间漂移)"
restore
measure "回滚后基线"
echo
echo "════════ 汇总(按可用率排序)════════"
sort -t'|' -k2 -rn /tmp/tcpab.txt | while IFS='|' read -r label pct ok stats; do
printf " %-34s %3s%% %s\n" "$label" "$pct" "$stats"
done
echo
echo "════════ 按数据决定 ════════"
BEST=$(sort -t'|' -k2 -rn /tmp/tcpab.txt | head -1)
BEST_LABEL=$(echo "$BEST" | cut -d'|' -f1)
BEST_PCT=$(echo "$BEST" | cut -d'|' -f2)
echo " 最好的是: $BEST_LABEL ($BEST_PCT%)"
case "$BEST_LABEL" in
"基线"|"回滚后基线")
echo " → 调优没有收益,保持默认(只留已验证过的 keepalive 那几项)"
restore
;;
"thin=1")
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
echo " → 只启用 thin_linear_timeouts"
;;
"thin=1 fack=1")
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
sysctl -qw net.ipv4.tcp_fack=1
echo " → 启用 thin_linear_timeouts + fack"
;;
*)
sysctl -qw net.ipv4.tcp_thin_linear_timeouts=1
sysctl -qw net.ipv4.tcp_fack=1
sysctl -qw net.ipv4.tcp_retries2=5
sysctl -qw net.ipv4.tcp_autocorking=0
echo " → 全部启用"
;;
esac
echo
echo " 当前生效值:"
for k in tcp_thin_linear_timeouts tcp_fack tcp_retries2 tcp_autocorking; do
printf " %-30s = %s\n" "$k" "$(sysctl -n net.ipv4.$k 2>/dev/null)"
done
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# 隧道对照 v3:验证「QUIC 失败是因为 MTU」这个假设,并选出最稳的组合。
#
# 已查明的事实:
# 1. cloudflared 用 QUIC 时反复报
# "failed to dial to edge with quic: timeout: no recent network activity"
# 连十几个 IP 才成功一次 —— 握手阶段就掉包。
# 2. 我们给 TCP 做了 MSS 钳制(--set-mss 1400),
# 但 **QUIC 走 UDP,TCPMSS 规则完全不生效**。
# 3. 路径 MTU 实测 1472(1500 的包不通)。
# QUIC 默认用 ~1452 字节的包,很容易被中间设备丢掉,
# 而 UDP 丢了不会回 ICMP,客户端只能等到超时。
#
# 所以对照三组:
# A. quic + 6(现状,MTU 1500) —— 预期握手仍不稳
# B. quic + 6 + wlan0 MTU 1400 —— 如果假设成立,握手会变稳
# C. http2 + 8(TCP 吃 MSS 钳制,连接多) —— 对照组
#
# 每组记录:连接建齐耗时 / 建齐几条 / 60 次 HTTP 可用率
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
SVC=/etc/systemd/system/lanremote-tunnel.service
BACKUP=/root/tunnel.bak.v3.$(date +%s)
cp -a "$SVC" "$BACKUP"
N=60
set_params() {
local proto="$1" conns="$2"
cp -a "$BACKUP" "$SVC"
sed -i "s/--protocol [a-z0-9]*/--protocol $proto/" "$SVC"
sed -i "s/--ha-connections [0-9]*/--ha-connections $conns/" "$SVC"
systemctl daemon-reload
systemctl restart lanremote-tunnel
}
set_mtu() {
local mtu="$1"
ip link set wlan0 mtu "$mtu" 2>/dev/null && echo " wlan0 MTU -> $(cat /sys/class/net/wlan0/mtu)"
}
# 等连接建齐,返回耗时秒数
wait_conns() {
local want="$1" limit="$2" t0 t1 n
t0=$(date +%s)
while true; do
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
n=${n:-0}
t1=$(date +%s)
if [ "$n" -ge "$want" ] || [ $((t1 - t0)) -ge "$limit" ]; then
echo "$n $((t1 - t0))"
return
fi
sleep 3
done
}
run_group() {
local label="$1" proto="$2" conns="$3" mtu="$4"
echo "════════ $label ════════"
[ "$mtu" != "keep" ] && set_mtu "$mtu"
local mark; mark=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
set_params "$proto" "$conns"
local res; res=$(wait_conns "$conns" 150)
local n; n=$(echo "$res" | awk '{print $1}')
local secs; s=$(echo "$res" | awk '{print $2}')
echo " 建齐 $n/$conns 条,用了 ${s} 秒"
# 这一轮建立过程中失败了多少次
local dialfail; dialfail=$(tail -n +$((mark+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
| grep -c 'Failed to dial' || echo 0)
echo " 建立期间 dial 失败 $dialfail 次"
# 可用率
local ok=0 times=()
for i in $(seq 1 "$N"); do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \
-H "User-Agent: $UA" "$BASE/v1/health")
c=$(echo "$out" | awk '{print $1}')
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
if [ "$c" = "200" ]; then ok=$((ok+1)); times+=("$ms"); fi
done
local pct=$((ok * 100 / N))
local stats="无数据"
if [ "${#times[@]}" -gt 0 ]; then
stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {
printf "中位 %d P90 %d P99 %d 最大 %d", a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}')
fi
printf " 可用率 %d%% (%d/%d) 耗时 %s ms\n" "$pct" "$ok" "$N" "$stats"
echo "$label|$proto|$conns|$mtu|$n|$s|$dialfail|$pct|$stats" >> /tmp/v3.txt
echo
}
rm -f /tmp/v3.txt
echo " 原始 wlan0 MTU: $(cat /sys/class/net/wlan0/mtu)"
echo
run_group "A. quic+6 MTU1500" quic 6 keep
run_group "B. quic+6 MTU1400" quic 6 1400
run_group "C. http2+8 MTU1400" http2 8 keep
# 恢复 MTU
set_mtu 1500
echo "════════ 汇总 ════════"
printf " %-22s %-6s %-4s %-8s %-9s %-8s %s\n" "组" "协议" "连接" "建齐/耗时" "dial失败" "可用率" "延迟"
while IFS='|' read -r label proto conns mtu n s dialfail pct stats; do
printf " %-22s %-6s %-4s %-8s %-9s %-8s %s\n" \
"$label" "$proto" "$conns" "${n}/${conns} ${s}s" "$dialfail" "${pct}%" "$stats"
done < /tmp/v3.txt
echo
echo "════════ 决策:可用率优先,其次看建齐速度 ════════"
BEST=$(sort -t'|' -k8 -rn /tmp/v3.txt | head -1)
BL=$(echo "$BEST" | cut -d'|' -f1)
BP=$(echo "$BEST" | cut -d'|' -f2)
BC=$(echo "$BEST" | cut -d'|' -f3)
BM=$(echo "$BEST" | cut -d'|' -f4)
echo " 最好: $BL(可用率 $(echo "$BEST" | cut -d'|' -f8)%)"
cp -a "$BACKUP" "$SVC"
sed -i "s/--protocol [a-z0-9]*/--protocol $BP/" "$SVC"
sed -i "s/--ha-connections [0-9]*/--ha-connections $BC/" "$SVC"
systemctl daemon-reload
systemctl restart lanremote-tunnel
echo " 已采用: --protocol $BP --ha-connections $BC"
echo " MTU: $(cat /sys/class/net/wlan0/mtu)"
echo " 备份: $BACKUP"
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
# 1.8.1 最终验收:从公网走一遍用户会走的全部路径
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
echo "════════ 1. 中转版本和隧道 ════════"
curl -s --max-time 15 "$BASE/v1/health" | head -c 130
echo
echo " 隧道连接: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') 条"
echo " 协议参数: $(grep -c 'protocol quic' /etc/systemd/system/lanremote-tunnel.service) (1=quic)"
echo " 长轮询: $(tail -30 /root/lanremote-relay/relay.log | grep '长轮询' | tail -1 | grep -oE '[0-9]+ 秒')"
echo
echo "════════ 2. 官网 ════════"
curl -s --max-time 25 -H "User-Agent: $UA" "$BASE/" -o /tmp/site.html
echo " HTTP $(curl -s -o /dev/null -w '%{http_code}' --max-time 25 -H "User-Agent: $UA" "$BASE/") $(wc -c < /tmp/site.html) 字节"
echo " 下载按钮:"
grep -o 'href="/download/[^"]*"' /tmp/site.html | sort -u | sed 's/^/ /'
echo " 管理页字眼: $(grep -cE 'zhongzhuanzhan|管理后台|管理页' /tmp/site.html || echo 0) 处(应为 0)"
echo
echo "════════ 3. 三个成品能下吗 ════════"
for f in ykxmz-setup-1.8.1.exe ykxmz-server-1.8.1.zip ykxmz-watch-1.8.1.apk; do
hdr=$(curl -sI --max-time 40 -H "User-Agent: $UA" "$BASE/download/$f")
code=$(echo "$hdr" | head -1 | awk '{print $2}')
len=$(echo "$hdr" | grep -i '^content-length' | tr -d '\r' | awk '{print $2}')
printf " %-30s HTTP %-4s %s 字节\n" "$f" "$code" "${len:-?}"
done
echo
echo "════════ 4. 长轮询还能跑通吗(2 次真实验证)════════"
STAMP=$(date +%s)
DEV="final-${STAMP}"
CODE=$(printf "%06d" $(( STAMP % 1000000 )))
REG=$(curl -s --max-time 30 -X POST "$BASE/v1/agent/register" \
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
-d "{\"deviceId\":\"$DEV\",\"code\":\"$CODE\",\"name\":\"final\",\"version\":\"1.8.1\"}")
TOKEN=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
echo " 探针注册: $([ -n "$TOKEN" ] && echo 成功 || echo 失败)"
for i in 1 2; do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 130 \
-X POST "$BASE/v1/agent/poll" -H "X-Token: $TOKEN" -H "User-Agent: $UA")
printf " 第 %d 次: 码 %s 耗时 %.1fs(应约 61 秒)\n" \
"$i" "$(echo "$out" | awk '{print $1}')" "$(echo "$out" | awk '{print $2}')"
done
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
curl -s --max-time 20 -X POST "http://127.0.0.1:8080/v1/admin/delete" \
-H "X-Admin: $KEY" -H 'Content-Type: application/json' \
-d "{\"deviceId\":\"$DEV\"}" > /dev/null 2>&1
echo " 探针已清理"
echo
echo "════════ 5. 公网可用率(10 次)════════"
ok=0
for i in $(seq 1 10); do
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 -H "User-Agent: $UA" "$BASE/v1/health")
[ "$c" = "200" ] && ok=$((ok+1))
printf " %2d: %s\n" "$i" "$c"
sleep 1
done
echo " 可用 $ok/10"
echo
echo "════════ 6. 中转有没有报错 ════════"
journalctl -u lanremote-relay --since '5 minutes ago' --no-pager 2>/dev/null \
| grep -icE 'traceback|nameerror|typeerror' | sed 's/^/ 错误行数: /'
tail -3 /root/lanremote-relay/relay.log | sed 's/^/ /'
+112
View File
@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
r'''给静态资源加缓存头,让 Cloudflare 在边缘直接发。
== 为什么这是最大的一招 ==
基线实测(下载 15 MB 安装包走隧道回 4G 源站):
ykxmz-watch-1.8.1.apk 201523 字节 / 66.8 秒 = 3 KB/s
ykxmz-server-1.8.1.zip 214900 字节 / 3.1 秒 = 67 KB/s
ykxmz-setup-1.8.1.exe 只下到 237568/15067199 字节就停了
原因:relay 给所有响应都发 `Cache-Control: no-store`,
**Cloudflare 完全不缓存** —— 每一次下载、每一张图、每个 favicon
都要穿隧道回 4G 源站。而回源吞吐只有几 KB/s。
改成可缓存后,Cloudflare 会在用户附近的边缘节点直接发,
速度是"边缘到用户",跟 4G 那条腿无关。
== 各路径的策略 ==
/download/* public, max-age=604800, immutable
文件名带版本号(ykxmz-setup-1.8.1.exe),
同一名字的内容永不改变,所以可以放心长缓存。
/shot/* 已经是 public, max-age=86400(保持不变)
/favicon.ico public, max-age=604800(147 KB,之前每次 3.2 秒)
/ public, max-age=60
首页会列出当前版本,缓存久了下新版本会看不到按钮。
60 秒足够挡住刷新的重复回源,又不会让版本信息过期太久。
/v1/* no-store(接口,必须实时)
注意:Cloudflare 默认只缓存一部分扩展名,.exe/.zip/.apk 可能不在
默认列表里。所以改完必须**实测 cf-cache-status 是不是 HIT**,
不 HIT 就要在 Cloudflare 后台加一条 Cache Rule(我会告诉用户怎么加)。
'''
from __future__ import annotations
import sys
from pathlib import Path
SRC = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay\relay.py")
OLD_DL = """ self.send_header(
"Content-Disposition",
f"attachment; filename*=UTF-8''{quoted}")
self.send_header("Cache-Control", "no-store")"""
NEW_DL = """ self.send_header(
"Content-Disposition",
f"attachment; filename*=UTF-8''{quoted}")
# ★ 让 Cloudflare 在边缘缓存。
# 原来这里是 no-store,于是每次下载都要穿隧道回 4G 源站,
# 实测吞吐只有 3~67 KB/s(15 MB 的安装包根本下不动)。
# 文件名带版本号,同一名字内容永不改变,可以长缓存。
self.send_header(
"Cache-Control",
"public, max-age=604800, immutable")"""
# favicon 那块
OLD_ICON = """ self._send(200, icon, "image/x-icon")"""
NEW_ICON = """ self._send(200, icon, "image/x-icon",
{"Cache-Control": "public, max-age=604800"})"""
# 首页那块
OLD_SITE = """ self._send(200, _site_page().encode("utf-8"),
"text/html; charset=utf-8")
return"""
NEW_SITE = """ # 首页缓存 60 秒:挡住刷新的重复回源,
# 又不会让"当前版本/下载按钮"过期太久。
self._send(200, _site_page().encode("utf-8"),
"text/html; charset=utf-8",
{"Cache-Control": "public, max-age=60"})
return"""
def patch(text: str, old: str, new: str, label: str) -> tuple:
if old not in text:
print(" ✗ 没找到:%s" % label)
return text, False
if text.count(old) != 1:
print(" ! 出现 %d 次:%s" % (text.count(old), label))
return text, False
print(" ✓ %s" % label)
return text.replace(old, new, 1), True
def main() -> int:
text = SRC.read_text(encoding="utf-8")
before = text.count("\n") + 1
ok_all = True
print(" 加缓存头:")
for old, new, label in (
(OLD_DL, NEW_DL, "下载:no-store -> public,max-age=604800,immutable"),
(OLD_ICON, NEW_ICON, "favicon:加 7 天缓存"),
(OLD_SITE, NEW_SITE, "首页:加 60 秒缓存"),
):
text, ok = patch(text, old, new, label)
ok_all = ok_all and ok
if not ok_all:
print()
print(" 有锚点没匹配,没有写回")
return 1
SRC.write_text(text, encoding="utf-8", newline="\n")
print()
print(" 已写回:%d -> %d 行" % (before, text.count("\n") + 1))
return 0
if __name__ == "__main__":
sys.exit(main())
+62
View File
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
r'''给官网首页加 60 秒缓存。
(download 那处已经用 edit 工具改好了:no-store ->
public,max-age=604800,immutable。)
首页原来走 _send 的默认 Cache-Control: no-store,
每次刷新都穿隧道回 4G 源站。加 60 秒缓存:
* 挡住"刷新就重新回源"的浪费
* 又不会让页面上的当前版本号 / 下载按钮过期太久
'''
from __future__ import annotations
import sys
from pathlib import Path
SRC = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay\relay.py")
def main() -> int:
text = SRC.read_text(encoding="utf-8")
marker = 'self._send(200, _site_page().encode("utf-8"),\n'
idx = text.find(marker)
if idx < 0:
print(" ✗ 找不到首页的 _send")
return 1
# 打印现场,确认空白
end = text.find("\n", text.find("return", idx))
print(" 现场:")
for line in text[idx:end + 1].splitlines():
print(" %r" % line)
old = ('self._send(200, _site_page().encode("utf-8"),\n'
' "text/html; charset=utf-8")')
new = ('# 缓存 60 秒:挡住"刷新就重新回源"的浪费,\n'
' # 又不会让页面上的当前版本号和下载按钮过期太久。\n'
' self._send(200, _site_page().encode("utf-8"),\n'
' "text/html; charset=utf-8",\n'
' {"Cache-Control": "public, max-age=60"})')
if old not in text:
print()
print(" ✗ 精确串还是没匹配上(看上面的 repr 对齐空白)")
return 1
text = text.replace(old, new, 1)
SRC.write_text(text, encoding="utf-8", newline="\n")
print()
print(" ✓ 已加首页缓存")
# 复核
for i, line in enumerate(
SRC.read_text(encoding="utf-8").splitlines(), 1):
if "max-age=60" in line:
print(" 第 %d 行: %s" % (i, line.strip()))
return 0
if __name__ == "__main__":
sys.exit(main())
+158
View File
@@ -0,0 +1,158 @@
#!/bin/bash
# 中转全面基线测量。
#
# 目的:把"优化到什么程度"变成可量化的数字,而不是感觉。
# 五类指标,每类都记录成功率和耗时分布:
# A. 公网 HTTP 可用率(100 次 /v1/health)
# B. 长轮询(10 次真实 poll,每次挂 60 秒)
# C. 三个发布产物的下载成功率和吞吐
# D. 隧道连接数稳定性 + 重连次数
# E. 静态资源(官网 / 截图 / favicon)
#
# 用法: bash bench.sh [标签]
set -u
BASE=${BASE:-https://relay.xxmxxs.top}
UA='LanRemote/1.8.1'
LABEL=${1:-baseline}
OUT="/root/bench_${LABEL}.txt"
echo "════════════════════════════════════════════════════════" | tee "$OUT"
echo " 中转基准测量 · $LABEL · $(date '+%Y-%m-%d %H:%M:%S')" | tee -a "$OUT"
echo "════════════════════════════════════════════════════════" | tee -a "$OUT"
echo | tee -a "$OUT"
echo "── 隧道配置 ──" | tee -a "$OUT"
CAP=$(systemctl show lanremote-tunnel -p ExecStart --value 2>/dev/null | tr ';' '\n' | grep -oE 'argv\[\]=[^ ]*|--[a-z-]+|/[a-z/]*cloudflared' | tr '\n' ' ')
grep -oE '\-\-protocol [a-z0-9]+|\-\-ha-connections [0-9]+|\-\-edge-ip-version [0-9]+' /etc/systemd/system/lanremote-tunnel.service 2>/dev/null | tr '\n' ' ' | sed 's/^/ 参数: /' | tee -a "$OUT"
echo " 长轮询: $(tail -40 /root/lanremote-relay/relay.log 2>/dev/null | grep -oE '长轮询 [0-9]+ 秒' | tail -1)" | tee -a "$OUT"
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')" | tee -a "$OUT"
# ---------------------------------------------------------------- A
echo | tee -a "$OUT"
echo "── A. 公网 HTTP 可用率(100 次,间隔 0.4 秒)──" | tee -a "$OUT"
okA=0; failA=0; timesA=()
for i in $(seq 1 100); do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 -H "User-Agent: $UA" "$BASE/v1/health")
code=$(echo "$out" | awk '{print $1}')
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
if [ "$code" = "200" ]; then okA=$((okA+1)); timesA+=("$ms"); else failA=$((failA+1)); fi
sleep 0.4
done
pctA=$((okA * 100 / 100))
echo " 可用率: $okA/100 = ${pctA}% 失败 $failA 次" | tee -a "$OUT"
if [ "${#timesA[@]}" -gt 0 ]; then
printf '%s\n' "${timesA[@]}" | sort -n | awk '{
a[NR]=$1
} END {
printf " 耗时: 最小 %d 中位 %d P90 %d P99 %d 最大 %d ms\n",
a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]
}' | tee -a "$OUT"
fi
# ---------------------------------------------------------------- E(快的先做)
echo | tee -a "$OUT"
echo "── E. 静态资源(各 10 次)──" | tee -a "$OUT"
for path in "/" "/shot/pc-main.png" "/favicon.ico"; do
ok=0; times=()
for i in $(seq 1 10); do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 -H "User-Agent: $UA" "$BASE$path")
c=$(echo "$out" | awk '{print $1}')
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
[ "$c" = "200" ] && { ok=$((ok+1)); times+=("$ms"); }
sleep 0.3
done
stats=$(printf '%s\n' "${times[@]:-0}" | sort -n | awk '{a[NR]=$1} END {printf "中位 %d 最大 %d ms", a[int(NR/2)+1], a[NR]}')
printf " %-22s %2d/10 %s\n" "$path" "$ok" "$stats" | tee -a "$OUT"
done
# ---------------------------------------------------------------- C
echo | tee -a "$OUT"
echo "── C. 三个成品下载(完整下载,量吞吐)──" | tee -a "$OUT"
for f in ykxmz-setup-1.8.1.exe ykxmz-server-1.8.1.zip ykxmz-watch-1.8.1.apk; do
rm -f /tmp/dl.bin
out=$(curl -s -o /tmp/dl.bin -w '%{http_code} %{size_download} %{time_total}' \
--max-time 180 -H "User-Agent: $UA" "$BASE/download/$f")
code=$(echo "$out" | awk '{print $1}')
size=$(echo "$out" | awk '{print $2}')
secs=$(echo "$out" | awk '{print $3}')
kbps=$(echo "$secs $size" | awk '{if ($1 > 0) printf "%.0f", $2/$1/1024; else print 0}')
printf " %-30s HTTP %-4s %9s 字节 %6.1fs %6s KB/s\n" \
"$f" "$code" "$size" "$secs" "$kbps" | tee -a "$OUT"
done
# ---------------------------------------------------------------- D
echo | tee -a "$OUT"
echo "── D. 隧道连接数稳定性(采样 60 秒,每 2 秒一次)──" | tee -a "$OUT"
MARK=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
minc=99; maxc=0; vals=()
for i in $(seq 1 30); do
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
[ -z "${n:-}" ] && n=0
[ "$n" -lt "$minc" ] && minc="$n"
[ "$n" -gt "$maxc" ] && maxc="$n"
vals+=("$n")
sleep 2
done
reconn=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection' || echo 0)
lost=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Lost connection' || echo 0)
iot=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'i/o timeout' || echo 0)
echo " 连接数: 最低 $minc 最高 $maxc 采样期间重连 $reconn 次 · 掉线 $lost 次 · i/o timeout $iot 次" | tee -a "$OUT"
# ---------------------------------------------------------------- B
echo | tee -a "$OUT"
echo "── B. 长轮询(10 次真实 poll,每次挂 60 秒 ≈ 10 分钟)──" | tee -a "$OUT"
STAMP=$(date +%s)
DEV="bench-${LABEL}-${STAMP}"
CODE=$(printf "%06d" $(( STAMP % 1000000 )))
REG=$(curl -s --max-time 30 -X POST "$BASE/v1/agent/register" \
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
-d "{\"deviceId\":\"$DEV\",\"code\":\"$CODE\",\"name\":\"bench\",\"version\":\"1.8.1\"}")
TOKEN=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
if [ -z "$TOKEN" ]; then
echo " [失败] 探针注册没拿到 token" | tee -a "$OUT"
else
okB=0; timesB=(); failsB=""
MARK2=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
for i in $(seq 1 10); do
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 130 \
-X POST "$BASE/v1/agent/poll" -H "X-Token: $TOKEN" -H "User-Agent: $UA")
c=$(echo "$out" | awk '{print $1}')
secs=$(echo "$out" | awk '{printf "%d", $2*1000}')
if [ "$c" = "200" ]; then
okB=$((okB+1)); timesB+=("$secs")
printf " %2d: 200 %6dms\n" "$i" "$secs" | tee -a "$OUT"
else
failsB="$failsB ${i}:${c}"
printf " %2d: %-4s **失败**\n" "$i" "$c" | tee -a "$OUT"
fi
done
echo " 长轮询成功: $okB/10 = $((okB*10))% 失败:$failsB" | tee -a "$OUT"
if [ "${#timesB[@]}" -gt 0 ]; then
printf '%s\n' "${timesB[@]}" | sort -n | awk '{
a[NR]=$1
} END {
printf " 耗时: 最小 %d 中位 %d P90 %d 最大 %d ms(理想 ≈ 61000)\n",
a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[NR]
}' | tee -a "$OUT"
fi
r2=$(tail -n +$((MARK2+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection' || echo 0)
echo " 这 10 分钟里隧道重连 $r2 次" | tee -a "$OUT"
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
curl -s --max-time 20 -X POST "http://127.0.0.1:8080/v1/admin/delete" \
-H "X-Admin: $KEY" -H 'Content-Type: application/json' \
-d "{\"deviceId\":\"$DEV\"}" > /dev/null 2>&1
fi
echo | tee -a "$OUT"
echo "════════════════════════════════════════════════════════" | tee -a "$OUT"
echo " 基线存档: $OUT" | tee -a "$OUT"
# 顺便把关键数字汇总成一行,便于前后对比
{
echo
echo "===== 汇总($LABEL)====="
echo "A 公网可用率 ${pctA}% ($okA/100)"
echo "B 长轮询成功 ${okB:-0}/10"
echo "D 隧道连接 最低 ${minc} 最高 ${maxc} 重连 ${reconn} 掉线 ${lost}"
} | tee -a "$OUT"
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
# 测下载吞吐:缓存预热前后对比。
#
# 基线(未加缓存头时):
# setup.exe 237568 字节 / 13.3s = 17 KB/s (还截断了)
# server.zip 214900 字节 / 3.1s = 67 KB/s
# watch.apk 201523 字节 / 66.8s = 3 KB/s
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
FILES="ykxmz-setup-1.8.1.exe ykxmz-server-1.8.1.zip ykxmz-watch-1.8.1.apk"
echo "════════ 1. 预热:每个文件先请求一次,把内容推进 Cloudflare 缓存 ════════"
for f in $FILES; do
# 只取前 1 字节,避免真的下载 15 MB
st=$(curl -s -o /dev/null -w '%{http_code}' -r 0-0 --max-time 60 \
-H "User-Agent: $UA" "$BASE/download/$f")
cf=$(curl -s -D - -o /dev/null -r 0-0 --max-time 60 -H "User-Agent: $UA" \
"$BASE/download/$f" 2>/dev/null | tr -d '\r' | grep -i '^cf-cache-status' | awk '{print $2}')
printf " %-30s HTTP %s cf-cache-status=%s\n" "$f" "$st" "${cf:-无}"
done
echo
echo " 等 5 秒让边缘缓存落定…"
sleep 5
echo
echo "════════ 2. 正式下载(完整文件,量吞吐)════════"
total_ok=0
for f in $FILES; do
rm -f /tmp/dl.bin
out=$(curl -s -o /tmp/dl.bin -D /tmp/dl.hdr -w '%{http_code} %{size_download} %{time_total}' \
--max-time 240 -H "User-Agent: $UA" "$BASE/download/$f")
code=$(echo "$out" | awk '{print $1}')
size=$(echo "$out" | awk '{print $2}')
secs=$(echo "$out" | awk '{print $3}')
cf=$(grep -i '^cf-cache-status' /tmp/dl.hdr | tr -d '\r' | awk '{print $2}')
age=$(grep -i '^age:' /tmp/dl.hdr | tr -d '\r' | awk '{print $2}')
kbps=$(echo "$secs $size" | awk '{if ($1 > 0) printf "%.0f", $2/$1/1024; else print 0}')
printf " %-30s HTTP %-4s %9s 字节 %6.1fs %6s KB/s cf=%s age=%s\n" \
"$f" "$code" "$size" "$secs" "$kbps" "${cf:-无}" "${age:-无}"
[ "$code" = "200" ] && total_ok=$((total_ok+1))
done
echo " 成功 $total_ok/3"
echo
echo "════════ 3. 再下一次(应该全 HIT,更快)════════"
for f in $FILES; do
rm -f /tmp/dl.bin
out=$(curl -s -o /tmp/dl.bin -D /tmp/dl.hdr -w '%{http_code} %{size_download} %{time_total}' \
--max-time 240 -H "User-Agent: $UA" "$BASE/download/$f")
code=$(echo "$out" | awk '{print $1}')
size=$(echo "$out" | awk '{print $2}')
secs=$(echo "$out" | awk '{print $3}')
cf=$(grep -i '^cf-cache-status' /tmp/dl.hdr | tr -d '\r' | awk '{print $2}')
kbps=$(echo "$secs $size" | awk '{if ($1 > 0) printf "%.0f", $2/$1/1024; else print 0}')
printf " %-30s HTTP %-4s %9s 字节 %6.1fs %6s KB/s cf=%s\n" \
"$f" "$code" "$size" "$secs" "$kbps" "${cf:-无}"
done
echo
echo "════════ 4. 对比基线 ════════"
echo " 基线: setup 17 KB/s(截断)· server 67 KB/s · apk 3 KB/s"
+59
View File
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
r'''版本号 1.8.0 -> 1.8.1。
这次是 bug 修复版:局域网劫持 + 长轮询 60 秒。
(用户规矩:每次上传都要升版号。)
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
OLD, NEW = "1.8.0", "1.8.1"
BOM = b"\xef\xbb\xbf"
TARGETS = [
"android/AndroidManifest.xml",
"android/build_apk.ps1",
"installer/ykxmz.iss",
"relay/relay.py",
"windows/lanremote/__init__.py",
"_tests/test_apk_contents.py",
"_tests/test_package_remote.py",
"_build/package_remote.py",
"_build/test_installer_cycle.py",
]
NEEDS_BOM = {".ps1"}
def main() -> int:
print(" %s -> %s" % (OLD, NEW))
total = 0
for rel in TARGETS:
path = ROOT / rel
if not path.is_file():
print(" [跳过] %s 不存在" % rel)
continue
raw = path.read_bytes()
had_bom = raw.startswith(BOM)
text = raw.decode("utf-8-sig")
if OLD not in text:
print(" [跳过] %-40s 里面没有 %s" % (rel, OLD))
continue
count = text.count(OLD)
text = text.replace(OLD, NEW)
want_bom = path.suffix in NEEDS_BOM or had_bom
data = text.encode("utf-8")
if want_bom:
data = BOM + data
path.write_bytes(data)
print(" %-40s 改了 %d 处" % (rel, count))
total += count
print()
print(" 共改 %d 处" % total)
return 0
if __name__ == "__main__":
sys.exit(main())
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# 看各类资源的响应头,判断有哪些能被 Cloudflare 缓存却因为 no-store 被挡了。
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
echo "════════ 各路径的缓存相关响应头 ════════"
for p in "/" "/v1/health" "/shot/pc-main.png" "/favicon.ico" \
"/download/ykxmz-server-1.8.1.zip"; do
echo " ── $p ──"
curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null \
| grep -iE '^HTTP|^cache-control|^cf-cache-status|^content-type|^content-length|^expires|^age' \
| tr -d '\r' | sed 's/^/ /'
done
echo
echo "════════ 静态图中的 cf-cache-status(HIT 说明缓存生效)════════"
for i in 1 2 3; do
s=$(curl -sI --max-time 20 -H "User-Agent: $UA" "$BASE/shot/pc-main.png" 2>/dev/null \
| grep -i '^cf-cache-status' | tr -d '\r' | awk '{print $2}')
echo " 第 $i 次: cf-cache-status = ${s:-?}"
sleep 1
done
echo
echo "════════ relay 源码里缓存头的设置 ════════"
grep -n 'Cache-Control' /root/lanremote-relay/relay.py | sed 's/^/ /'
echo
echo "════════ 下载路由的实现(看有没有做流式/分块)════════"
sed -n '/if path.startswith("\/download\/")/,/^ # /p' /root/lanremote-relay/relay.py \
| head -50 | sed 's/^/ /'
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# 看可靠性测试进度和隧道事件
set -u
echo "── 测试进程 ──"
pgrep -af 'tpr.sh|reliability' | head -3
echo
echo "── 隧道最近 30 分钟事件 ──"
reconn=$(journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null | grep -c 'Registered tunnel connection')
lost=$(journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null | grep -c 'Lost connection')
iot=$(journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null | grep -c 'i/o timeout')
echo " 重连 $reconn 次 · 掉线 $lost 次 · i/o timeout $iot 次"
echo
echo "── 最近的掉线/重连明细 ──"
journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null \
| grep -E 'Registered tunnel connection|Lost connection' | tail -8 | sed 's/^/ /'
echo
echo "── watchdog 最近 12 行 ──"
tail -12 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /'
echo
echo "── 隧道连接数和落点 ──"
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep -E '^cloudflared_tunnel_ha_connections|server_locations.*} 1' | sed 's/^/ /'
echo
echo "── 公网现在通不通(10 次快速探测)──"
ok=0
for i in $(seq 1 10); do
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \
-H 'User-Agent: LanRemote/1.8.1' https://relay.xxmxxs.top/v1/health)
[ "$c" = "200" ] && ok=$((ok+1))
printf " %d: %s\n" "$i" "$c"
sleep 1
done
echo " 可用 $ok/10"
+76
View File
@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
r'''临时调试:在首页分支里打一行日志,看它到底有没有被执行、extra 传的是什么。
(这个文件只改运行环境里的 relay.py,不改本地仓库。)
'''
from __future__ import annotations
import sys
from pathlib import Path
import paramiko
HOST = "192.168.10.105"
USER = "root"
PASSWORD = "1313144"
REMOTE = "/root/lanremote-relay/relay.py"
def run(client, cmd: str, timeout: int = 60) -> str:
_in, out, err = client.exec_command(cmd, timeout=timeout)
return (out.read().decode("utf-8", "replace")
+ err.read().decode("utf-8", "replace"))
def main() -> int:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=USER, password=PASSWORD, timeout=20,
look_for_keys=False, allow_agent=False)
try:
# 1. 看看首页分支前后到底长什么样
print(" 首页分支附近(1400-1415):")
print(run(client, "sed -n '1400,1415p' " + REMOTE))
# 2. 检查 _send 是否真的会跳过默认头
print(" _send 的头部逻辑(895-920):")
print(run(client, "sed -n '895,920p' " + REMOTE))
# 3. 用 python 直接调这份代码的 _send 看结果
probe = r'''
import sys
sys.path.insert(0, "/root/lanremote-relay")
import relay, io
captured = []
class Fake:
def send_response(self, code): captured.append(("__status__", code))
def send_header(self, k, v): captured.append((k, v))
def end_headers(self): pass
wfile = io.BytesIO()
f = Fake()
relay.Handler._send(f, 200, b"hi", "text/html; charset=utf-8",
{"Cache-Control": "public, max-age=60"})
cc = [v for k, v in captured if k.lower() == "cache-control"]
print(" _send 发出的 Cache-Control 值:", cc)
print(" 头数量:", len(cc))
'''
Path("/tmp/probe_send.py").write_text(probe, encoding="utf-8")
client.exec_command("cat > /tmp/probe_send.py <<'PYEOF'\n"
+ probe + "\nPYEOF\n")[1].channel.recv_exit_status()
print(" _send 实测:")
print(run(client, "python3 /tmp/probe_send.py 2>&1"))
# 4. 看 do_GET 里 "/" 前面有没有别的 return
print(" do_GET 的 return 位置(1174-1412):")
print(run(client, "awk 'NR>=1174 && NR<=1412 && "
"/return|if path/ {print NR\": \"$0}' " + REMOTE
+ " | head -40"))
finally:
client.close()
return 0
if __name__ == "__main__":
sys.exit(main())
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# 查为什么首页还是 no-store,尽管代码里有 max-age=60
set -u
echo "════════ 1. 进程状态 ════════"
systemctl show lanremote-relay -p ActiveEnterTimestamp -p MainPID --value | sed 's/^/ /'
echo " 现在: $(date '+%Y-%m-%d %H:%M:%S')"
echo " 进程命令行:"
ps -o pid,etime,cmd -p $(systemctl show lanremote-relay -p MainPID --value) 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 2. 本地首页响应头(全部)════════"
curl -sI --max-time 15 http://127.0.0.1:8080/ 2>/dev/null | tr -d '\r' | sed 's/^/ /'
echo
echo "════════ 3. 换个端口直接跑这份代码,排除服务读的是别的文件 ════════"
cd /root/lanremote-relay
RELAY_PORT=18099 RELAY_BIND=127.0.0.1 timeout 8 python3 relay.py > /tmp/fresh.log 2>&1 &
sleep 4
echo " 新进程的首页头:"
curl -sI --max-time 6 http://127.0.0.1:18099/ 2>/dev/null | tr -d '\r' | grep -iE '^cache-control|^HTTP' | sed 's/^/ /'
wait 2>/dev/null || true
echo
echo "════════ 4. 服务实际读的文件是哪个 ════════"
systemctl cat lanremote-relay 2>/dev/null | grep -E 'WorkingDirectory|ExecStart' | sed 's/^/ /'
echo " WorkingDirectory 下的 relay.py md5: $(md5sum /root/lanremote-relay/relay.py 2>/dev/null | awk '{print $1}')"
echo
echo "════════ 5. 有没有 __pycache__ 里的旧字节码在捣乱 ════════"
ls -la /root/lanremote-relay/__pycache__/ 2>/dev/null | sed 's/^/ /' || echo " (没有 __pycache__)"
echo
echo "════════ 6. 源码里那段到底长什么样(带行号)════════"
grep -n -B3 -A6 'max-age=60' /root/lanremote-relay/relay.py | sed 's/^/ /'
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# 查隧道为什么只有 3 条连接(配置 6)
set -u
echo "════════ 1. 当前连接数 ════════"
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep -E '^cloudflared_tunnel_ha_connections|server_locations.*} 1' | sed 's/^/ /'
echo
echo "════════ 2. cloudflared 进程 ════════"
ps -o pid,etime,rss,cmd -p $(systemctl show lanremote-tunnel -p MainPID --value) 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 3. 最近 30 分钟的连接事件 ════════"
journalctl -u lanremote-tunnel --since '30 minutes ago' --no-pager 2>/dev/null \
| grep -E 'Registered|Lost|Unable|Unregistered|connIndex' | tail -20 | sed 's/^/ /'
echo
echo "════════ 4. cloudflared 的完整参数 ════════"
systemctl cat lanremote-tunnel 2>/dev/null | grep -A8 'ExecStart=' | sed 's/^/ /'
echo
echo "════════ 5. 4 个活跃连接都在哪个落点、延迟多少 ════════"
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep -E 'cloudflared_tunnel_(server_locations|connection_)' \
| grep -v '^#' | sed 's/^/ /'
echo
echo "════════ 6. 内存占用(MemoryMax=160M 可能把 cloudflared 撑爆)════════"
systemctl show lanremote-tunnel -p MemoryCurrent -p MemoryMax --value | sed 's/^/ /'
echo " ---"
echo " 设备总内存:"
free -m | sed 's/^/ /'
echo
echo "════════ 7. 日志里有没有 OOM 或被杀 ════════"
journalctl -u lanremote-tunnel --since '2 hours ago' --no-pager 2>/dev/null \
| grep -icE 'oom|killed|signal' | sed 's/^/ OOM/被杀 相关行数: /'
dmesg 2>/dev/null | grep -iE 'oom|killed process' | tail -5 | sed 's/^/ /' || echo " dmesg 里没有"
echo
echo "════════ 8. 隧道日志末尾(看有没有在反复失败)════════"
tail -20 /root/lanremote-relay/tunnel.log 2>/dev/null | sed 's/^/ /'
+1 -1
View File
@@ -17,7 +17,7 @@ import zipfile
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
VERSION = "1.8.0"
VERSION = "1.8.1"
NAME = f"ykxmz-{VERSION}"
AGENT_SRC = ROOT / "dist_exe" / "ykxmz"
+108
View File
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
r'''客户端长轮询超时 90 -> 120 秒。
原因(实测数据):
quic+6 跑 10 次真实长轮询,出现一次 **93589ms** 的长尾。
服务端 hold 是 60 秒,也就是网络在这一次多花了 33 秒。
而客户端超时设的是 90 秒 —— 93.6 > 90,那一次**必然被判失败**,
即使服务端其实已经正常返回了。
长尾来自 4G 丢包触发的 TCP 重传退避(1.4s 的 RTT 下,
几次重传就是几十秒)。这是链路特性,改协议也消不掉,
只能把客户端的容忍度放宽。
新值:120 秒 = 60(hold) + 60(网络余量)。
Cloudflare 对单请求的上限是 100 秒,所以服务端 hold 60 秒是安全的;
客户端 120 秒只是为了不"冤枉"那些慢但成功的请求。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
BOM = b"\xef\xbb\xbf"
EDITS = [
(
"windows/lanremote/relay.py",
"""# 长轮询单次请求的超时。
# 必须显著大于服务端的 hold 时间(现在 60 秒),否则会在服务端
# 正常返回之前自己先超时 —— 那就变成"每次轮询都失败"了。
# 90 = 60 + 30 秒余量。
POLL_TIMEOUT = 90.0""",
"""# 长轮询单次请求的超时。
# 必须显著大于服务端的 hold 时间(现在 60 秒),否则会在服务端
# 正常返回之前自己先超时 —— 那就变成"每次轮询都失败"了。
# 120 = 60(hold) + 60(网络余量)。
# 为什么余量给这么大:实测出现过一次 93589ms 的长尾
# (4G 丢包触发 TCP 重传退避),原来设 90 秒会把它误判成失败。
POLL_TIMEOUT = 120.0""",
),
(
"android/src/com/lanremote/watch/RelayTransport.java",
""" /**
* 长轮询:中转那边挂 60 秒,客户端超时给宽一点。
*
* <p>必须显著大于服务端的 hold 时间,否则会在服务端正常返回
* 之前自己先超时 —— 那就变成"每次轮询都失败"了。
* 90 秒 = 60 + 30 秒余量。
*/
private static final int POLL_TIMEOUT_MS = 90000;""",
""" /**
* 长轮询:中转那边挂 60 秒,客户端超时给宽一点。
*
* <p>必须显著大于服务端的 hold 时间,否则会在服务端正常返回
* 之前自己先超时 —— 那就变成"每次轮询都失败"了。
* 120 秒 = 60(hold) + 60(网络余量)。
*
* <p>余量给这么大是因为实测出现过一次 93589ms 的长尾
* (4G 丢包触发 TCP 重传退避)。设 90 秒会把那次
* 其实成功的请求误判成失败。
*/
private static final int POLL_TIMEOUT_MS = 120000;""",
),
]
def main() -> int:
ok_all = True
for rel, old, new in EDITS:
path = ROOT / rel
if not path.is_file():
print(" ✗ %s 不存在" % rel)
ok_all = False
continue
raw = path.read_bytes()
had_bom = raw.startswith(BOM)
text = raw.decode("utf-8-sig")
if old not in text:
if "120000" in text or "120.0" in text:
print(" • %-44s 已经是新值" % rel)
continue
print(" ✗ %-44s 找不到锚点" % rel)
ok_all = False
continue
text = text.replace(old, new, 1)
data = text.encode("utf-8")
if had_bom:
data = BOM + data
path.write_bytes(data)
print(" ✓ %-44s 已改" % rel)
print()
if not ok_all:
return 1
for rel, token in (
("windows/lanremote/relay.py", "POLL_TIMEOUT = 120.0"),
("android/src/com/lanremote/watch/RelayTransport.java",
"POLL_TIMEOUT_MS = 120000"),
):
text = (ROOT / rel).read_text(encoding="utf-8-sig")
print(" %s %s" % ("✓" if token in text else "✗", rel))
return 0
if __name__ == "__main__":
sys.exit(main())
+1
View File
@@ -22,6 +22,7 @@ from __future__ import annotations
import json
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
# 查还有哪些内核网络调优可用(只读)
set -u
echo "════════ 1. 排队规则(qdisc)候选 ════════"
echo " 当前: $(sysctl -n net.core.default_qdisc)"
echo " 内核里有没有 fq / fq_codel / cake:"
find /lib/modules/$(uname -r)/kernel/net/sched/ -name '*.ko*' 2>/dev/null \
| xargs -n1 basename 2>/dev/null | grep -iE 'fq|codel|cake|htb|tbf' | head -10 | sed 's/^/ /'
echo " 已加载的相关模块:"
lsmod 2>/dev/null | grep -iE 'fq|codel|cake|sch_' | sed 's/^/ /' || echo " (没有)"
echo " ---"
echo " 试加载 fq_codel(-n = 只检查不真加载):"
modprobe -n sch_fq_codel 2>&1 | head -2 | sed 's/^/ /'
echo " 试加载 fq:"
modprobe -n sch_fq 2>&1 | head -2 | sed 's/^/ /'
echo
echo "════════ 2. 现在的 qdisc 长什么样 ════════"
tc qdisc show dev wlan0 2>/dev/null | sed 's/^/ /' || echo " (tc 没装)"
echo
echo "════════ 3. TCP 重传相关的可调项(当前值)════════"
for k in tcp_retries1 tcp_retries2 tcp_rto_min tcp_rto_max tcp_recovery \
tcp_early_retrans tcp_fack tcp_thin_linear_timeouts tcp_abort_on_overflow \
tcp_limit_output_bytes tcp_moderate_rcvbuf tcp_autocorking; do
v=$(sysctl -n net.ipv4.$k 2>/dev/null)
[ -n "$v" ] && printf " %-38s = %s\n" "$k" "$v"
done
echo
echo "════════ 4. 这些项的合法范围 ════════"
ls /proc/sys/net/ipv4/ 2>/dev/null | grep -E '^tcp_(retries|rto|recovery|early|fack|thin|abort|limit|moderate|autocork)' | sed 's/^/ /'
echo
echo "════════ 5. 丢包/重传的实时增量(10 秒采样)════════"
a=$(netstat -s 2>/dev/null | grep -E 'TCPTimeouts|segments retransmitted|TCPLostRetransmit' | awk '{s+=$1} END {print s}')
sleep 10
b=$(netstat -s 2>/dev/null | grep -E 'TCPTimeouts|segments retransmitted|TCPLostRetransmit' | awk '{s+=$1} END {print s}')
echo " 10 秒内新增重传/超时相关计数: $((b - a))"
echo
echo "════════ 6. 基线测量还在跑吗 ════════"
pgrep -af 'bench.sh' | head -2 | sed 's/^/ /' || echo " 已结束"
if [ -f /root/bench_baseline.txt ]; then
echo " 已写入的行数: $(wc -l < /root/bench_baseline.txt)"
echo " 最近几行:"
tail -6 /root/bench_baseline.txt | sed 's/^/ /'
fi
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
# 只读调研:找出还剩下哪些能优化的地方。
# 不动任何配置(基线测量正在跑)。
set -u
echo "════════ 1. 内核和拥塞控制 ════════"
echo " 内核: $(uname -r)"
echo " 可用拥塞控制算法: $(sysctl -n net.ipv4.tcp_available_congestion_control 2>/dev/null)"
echo " 当前算法: $(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null)"
echo " 可用排队规则: $(sysctl -n net.core.default_qdisc 2>/dev/null)"
echo " ---"
echo " BBR 模块在不在:"
ls /lib/modules/$(uname -r)/kernel/net/ipv4/ 2>/dev/null | grep -iE 'bbr|tcp_' | head -8 | sed 's/^/ /'
echo " modprobe 试试(只查不加载):"
modprobe -n tcp_bbr 2>&1 | head -2 | sed 's/^/ /' || echo " 不能加载"
echo " 内核有没有编进 BBR:"
grep -c 'CONFIG_TCP_CONG_BBR' /boot/config-$(uname -r) 2>/dev/null | sed 's/^/ config 文件: /' || echo " 没有 config 文件"
echo
echo "════════ 2. 当前 TCP 调优项 ════════"
for k in \
net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes \
net.ipv4.tcp_mtu_probing net.ipv4.tcp_slow_start_after_idle net.ipv4.tcp_fastopen \
net.ipv4.tcp_rmem net.ipv4.tcp_wmem net.core.rmem_max net.core.wmem_max \
net.ipv4.tcp_retries2 net.ipv4.tcp_syn_retries net.ipv4.tcp_synack_retries \
net.ipv4.tcp_window_scaling net.ipv4.tcp_timestamps net.ipv4.tcp_sack \
net.ipv4.tcp_fack net.ipv4.tcp_recovery net.ipv4.tcp_notsent_lowat \
net.core.netdev_max_backlog net.ipv4.tcp_max_syn_backlog net.ipv4.ip_local_port_range; do
v=$(sysctl -n "$k" 2>/dev/null)
[ -n "$v" ] && printf " %-42s = %s\n" "$k" "$v"
done
echo
echo "════════ 3. 网络接口和 MTU ════════"
ip -br addr 2>/dev/null | sed 's/^/ /'
echo " ---"
for i in $(ls /sys/class/net/ 2>/dev/null | grep -v lo); do
mtu=$(cat /sys/class/net/$i/mtu 2>/dev/null)
speed=$(cat /sys/class/net/$i/speed 2>/dev/null || echo '?')
echo " $i: MTU=$mtu speed=${speed}Mb"
done
echo " ---"
echo " MSS 钳制规则:"
iptables -t mangle -S 2>/dev/null | grep TCPMSS | sed 's/^/ /' || echo " 没有"
echo
echo "════════ 4. 实际路径 MTU 探测 ════════"
for target in 1.1.1.1 198.41.192.227; do
echo " 到 $target 的最大不分片包:"
for sz in 1500 1472 1450 1420 1400 1380 1300; do
if ping -c 1 -W 3 -M do -s $((sz-28)) "$target" > /dev/null 2>&1; then
echo " $sz 字节: 通"
break
else
echo " $sz 字节: 不通"
fi
done
done
echo
echo "════════ 5. cloudflared 可选参数(和重连/传输有关的)════════"
cloudflared tunnel run --help 2>&1 | grep -E '^\s+--(retries|protocol|edge|ha-connections|region|grace|compression|tcp)' | sed 's/^/ /'
echo
echo "════════ 6. 隧道当前完整配置 ════════"
systemctl cat lanremote-tunnel 2>/dev/null | grep -vE '^\s*#' | grep -v '^$' | sed 's/^/ /'
echo
echo "════════ 7. cloudflared 的 originRequest 配置 ════════"
cat /root/.cloudflared/config.yml 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 8. 4G 网卡的重传统计(看链路质量)════════"
ip -s link show 2>/dev/null | head -20 | sed 's/^/ /'
echo " ---"
netstat -s 2>/dev/null | grep -iE 'retrans|timeout|reset|error|失败' | head -10 | sed 's/^/ /'
+1 -1
View File
@@ -25,7 +25,7 @@ import time
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
SETUP = ROOT / "dist" / "ykxmz-setup-1.8.0.exe"
SETUP = ROOT / "dist" / "ykxmz-setup-1.8.1.exe"
APP = Path(r"C:\Program Files\ykxmz")
DATA = Path(r"C:\ProgramData\ykxmz")
EXE = APP / "ykxmz.exe"
+4
View File
@@ -54,6 +54,10 @@ DOCS_ALLOWLIST = {
"LICENSE.txt",
"项目规矩.md", # 这份文件本身就是在讲这条规矩
"免费托管方案.md", # 讲托管方案,不涉及
# 开发流程文档:里面要写"发布脚本会检查更新说明里有没有管理页字眼",
# 属于描述规则本身,不是对外宣传材料。
"双平台发布.md",
"EzHub-SSH诊断报告.md", # 给服务端运维看的排查报告
}
# 官网源码里这些词也不许有(landing.py 渲染出来的页面)
+103
View File
@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
r'''把当前版本的三个成品上传到中转的下载目录。
用法:
python _build/upload_release.py 1.8.1
会自动:
* 本地确认三个成品都在(安装包 exe / 服务端 zip / 手表 apk)
* 删掉设备上的旧版本文件
* 上传新的四个文件(apk 还带一份说明)
* 确认中转的 /v1/downloads 能列出来
发布规则(用户定的):
* Windows 成品必须是**安装包 exe**,不是 agent zip
* 服务端给 zip,手表直接给 APK
* **每次都升版号**(用户原话:自行更新版号)
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
sys.path.insert(0, str(ROOT / "_build"))
from devssh import run, upload # noqa: E402
REMOTE_DIR = "/root/lanremote-relay/downloads"
def main() -> int:
version = sys.argv[1] if len(sys.argv) > 1 else "1.8.1"
files = [
("dist/ykxmz-setup-%s.exe" % version,
"ykxmz-setup-%s.exe" % version),
("dist/ykxmz-server-%s.zip" % version,
"ykxmz-server-%s.zip" % version),
("dist/ykxmz-watch-%s.apk" % version,
"ykxmz-watch-%s.apk" % version),
("dist/ykxmz-watch-%s-说明.txt" % version,
"ykxmz-watch-%s-说明.txt" % version),
]
print(" 版本 %s" % version)
print()
print(" 本地成品:")
missing = []
for local, _remote in files:
path = ROOT / local
if path.is_file():
print(" ✓ %-38s %7.2f MB"
% (path.name, path.stat().st_size / 1024 / 1024))
else:
print(" ✗ %-38s 不存在" % local)
missing.append(local)
if missing:
print()
print(" [失败] 少了 %d 个,先构建" % len(missing))
return 1
# 清掉设备上所有旧版本(任何不是当前版本的 ykxmz-*)
print()
print(" 清理设备上的旧版本:")
_c, out, _e = run("ls %s/ 2>/dev/null" % REMOTE_DIR, timeout=60)
for name in (out or "").strip().splitlines():
name = name.strip()
if not name or version in name:
continue
if not name.startswith("ykxmz-"):
continue
run("rm -f '%s/%s'" % (REMOTE_DIR, name), timeout=30)
print(" - 删掉 %s" % name)
print()
print(" 上传:")
for local, remote_name in files:
path = ROOT / local
upload(str(path), "%s/%s" % (REMOTE_DIR, remote_name), 0o644)
print(" ✓ %s" % remote_name)
print()
print(" 设备上的下载目录:")
_c, out, _e = run("ls -la %s/" % REMOTE_DIR, timeout=60)
for line in (out or "").strip().splitlines():
print(" " + line)
print()
print(" 中转 /v1/downloads:")
_c, out, _e = run(
"curl -s --max-time 10 http://127.0.0.1:8080/v1/downloads", timeout=60)
print(" " + (out or "").strip()[:500])
print()
print(" 官网下载按钮会指向:")
for _local, remote_name in files:
if remote_name.endswith((".exe", ".zip", ".apk")):
print(" /download/%s" % remote_name)
return 0
if __name__ == "__main__":
sys.exit(main())
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# 部署缓存头改动,并验证 Cloudflare 是否真的缓存了。
#
# 关键疑问:Cloudflare 默认只缓存一批扩展名,
# .exe / .zip / .apk **可能不在**默认列表里。
# 所以不能只看"我发了 Cache-Control"就算完,
# 必须看响应头里的 cf-cache-status 有没有变成 HIT。
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
echo "════════ 1. 重启中转让缓存头生效 ════════"
systemctl restart lanremote-relay
sleep 6
echo " 中转: $(systemctl is-active lanremote-relay)"
curl -s --max-time 10 http://127.0.0.1:8080/v1/health | head -c 80
echo
echo
echo "════════ 2. 本机直连看缓存头(确认代码生效)════════"
for p in "/" "/download/ykxmz-server-1.8.1.zip" "/download/ykxmz-setup-1.8.1.exe"; do
echo " ── $p ──"
curl -sI --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
| grep -iE '^HTTP|^cache-control|^content-length|^content-type' \
| tr -d '\r' | sed 's/^/ /'
done
echo
echo "════════ 3. 等隧道恢复 ════════"
for i in $(seq 1 20); do
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 12 -H "User-Agent: $UA" "$BASE/v1/health")
printf " 第 %d 次: %s\n" "$i" "$c"
[ "$c" = "200" ] && break
sleep 3
done
echo
echo "════════ 4. 走公网看 cf-cache-status(关键)════════"
echo " 对每个资源连打 3 次,看 MISS -> HIT"
for p in "/" "/shot/pc-main.png" "/favicon.ico" \
"/download/ykxmz-server-1.8.1.zip" \
"/download/ykxmz-setup-1.8.1.exe" \
"/download/ykxmz-watch-1.8.1.apk"; do
echo " ── $p ──"
for i in 1 2 3; do
hdr=$(curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
st=$(echo "$hdr" | grep -i '^cf-cache-status' | awk '{print $2}')
cc=$(echo "$hdr" | grep -i '^cache-control' | cut -d' ' -f2-)
age=$(echo "$hdr" | grep -i '^age:' | awk '{print $2}')
printf " 第 %d 次: cf-cache-status=%-8s age=%-6s cache-control=%s\n" \
"$i" "${st:-无}" "${age:-无}" "${cc:-无}"
sleep 1
done
done
echo
echo "════════ 5. 结论 ════════"
echo " 如果 .exe/.zip/.apk 一直是 MISS/无,说明 Cloudflare 默认不缓存这些扩展名,"
echo " 需要在 Cloudflare 后台加一条 Cache Rule(我会给出具体配置)。"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# 验证首页缓存是否生效(修了 _send 重复头之后)
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
echo "════════ 1. 本机直连的缓存头(确认代码生效)════════"
for p in "/" "/download/ykxmz-setup-1.8.1.exe" "/shot/pc-main.png"; do
cc=$(curl -sI --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
| grep -i '^cache-control' | tr -d '\r')
printf " %-38s %s\n" "$p" "${cc:-(无)}"
done
echo
echo "════════ 2. 走公网看 cf-cache-status ════════"
for p in "/" "/shot/pc-main.png" "/favicon.ico" "/download/ykxmz-setup-1.8.1.exe"; do
echo " ── $p ──"
for i in 1 2 3; do
hdr=$(curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
st=$(echo "$hdr" | grep -i '^cf-cache-status' | awk '{print $2}')
cc=$(echo "$hdr" | grep -i '^cache-control' | cut -d' ' -f2-)
age=$(echo "$hdr" | grep -i '^age:' | awk '{print $2}')
printf " 第 %d 次: status=%-8s age=%-7s cc=%s\n" \
"$i" "${st:-无}" "${age:-无}" "${cc:-无}"
sleep 1
done
done
echo
echo "════════ 3. 重复头检查(不该有两个 Cache-Control)════════"
n=$(curl -sI --max-time 15 "http://127.0.0.1:8080/" 2>/dev/null | grep -ci '^cache-control')
echo " 本机首页的 Cache-Control 头数量: $n (应为 1)"
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# 用真正的 GET 验证缓存头(curl -I 发的是 HEAD,之前看的一直是 HEAD 的响应!)
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
echo "════════ 1. 本机直连 · 真 GET(-D - 把响应头 dump 出来)════════"
for p in "/" "/download/ykxmz-setup-1.8.1.exe" "/shot/pc-main.png"; do
cc=$(curl -s -D - -o /dev/null --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
| grep -i '^cache-control' | tr -d '\r')
printf " GET %-38s %s\n" "$p" "${cc:-(无)}"
done
echo
echo "════════ 2. 对比 HEAD(两者应该一致)════════"
for p in "/" "/download/ykxmz-setup-1.8.1.exe"; do
cc=$(curl -sI --max-time 15 "http://127.0.0.1:8080$p" 2>/dev/null \
| grep -i '^cache-control' | tr -d '\r')
printf " HEAD %-38s %s\n" "$p" "${cc:-(无)}"
done
echo
echo "════════ 3. 走公网 · 真 GET 看 cf-cache-status ════════"
for p in "/" "/shot/pc-main.png" "/download/ykxmz-server-1.8.1.zip"; do
echo " ── $p ──"
for i in 1 2 3; do
hdr=$(curl -s -D - -o /dev/null --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
st=$(echo "$hdr" | grep -i '^cf-cache-status' | awk '{print $2}')
cc=$(echo "$hdr" | grep -i '^cache-control' | cut -d' ' -f2-)
age=$(echo "$hdr" | grep -i '^age:' | awk '{print $2}')
printf " 第 %d 次: status=%-8s age=%-7s cc=%s\n" \
"$i" "${st:-无}" "${age:-无}" "${cc:-无}"
sleep 1
done
done
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# 验证 GET 和 HEAD 的缓存头现在一致了
set -u
LOCAL=http://127.0.0.1:8080
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.1'
echo "════════ 1. GET vs HEAD 逐路径对比(必须一致)════════"
printf " %-38s %-30s %s\n" "路径" "GET" "HEAD"
for p in "/" "/favicon.ico" "/shot/pc-main.png" \
"/download/ykxmz-setup-1.8.1.exe" "/v1/health"; do
g=$(curl -s -D - -o /dev/null --max-time 15 "$LOCAL$p" 2>/dev/null \
| grep -i '^cache-control' | cut -d' ' -f2- | tr -d '\r')
h=$(curl -sI --max-time 15 "$LOCAL$p" 2>/dev/null \
| grep -i '^cache-control' | cut -d' ' -f2- | tr -d '\r')
mark="✓"
[ "$g" != "$h" ] && mark="✗ 不一致"
printf " %-38s %-30s %s %s\n" "$p" "${g:-无}" "${h:-无}" "$mark"
done
echo
echo "════════ 2. HEAD 的下载要报正确的 Content-Length ════════"
for f in ykxmz-setup-1.8.1.exe ykxmz-watch-1.8.1.apk; do
len=$(curl -sI --max-time 20 "$LOCAL/download/$f" 2>/dev/null \
| grep -i '^content-length' | awk '{print $2}' | tr -d '\r')
ctype=$(curl -sI --max-time 20 "$LOCAL/download/$f" 2>/dev/null \
| grep -i '^content-type' | cut -d' ' -f2- | tr -d '\r')
printf " %-30s %10s 字节 %s\n" "$f" "${len:-?}" "${ctype:-?}"
done
echo
echo "════════ 3. 走公网 HEAD 也应该报告可缓存 ════════"
for p in "/download/ykxmz-setup-1.8.1.exe"; do
h=$(curl -sI --max-time 30 -H "User-Agent: $UA" "$BASE$p" 2>/dev/null | tr -d '\r')
echo " $p"
echo "$h" | grep -iE '^cache-control|^content-length|^cf-cache-status|^content-type' | sed 's/^/ /'
done
+26 -4
View File
@@ -38,10 +38,32 @@ def main() -> int:
files = [str(p) for p in TARGETS if p.is_file()]
print(f" 检查 {len(files)} 个 Python 文件…")
proc = subprocess.run([sys.executable, "-m", "pyflakes"] + files,
capture_output=True, text=True,
encoding="utf-8", errors="replace")
lines = [line for line in (proc.stdout or "").splitlines() if line.strip()]
# ⚠ 不能一次把 450 多个文件全塞进命令行 ——
# Windows 的 CreateProcess 有命令行长度上限(约 32767 字符),
# 超了直接 FileNotFoundError: [WinError 206] 文件名或扩展名太长,
# 而且报错看起来像"文件找不到",很容易误判。
# 所以分批调用,每批的总长度控制住。
all_lines = []
batch, batch_len = [], 0
LIMIT = 6000 # 单批命令行里文件路径的总字符数预算(留足余量)
batches = []
for path in files:
if batch and batch_len + len(path) + 1 > LIMIT:
batches.append(batch)
batch, batch_len = [], 0
batch.append(path)
batch_len += len(path) + 1
if batch:
batches.append(batch)
for chunk in batches:
proc = subprocess.run([sys.executable, "-m", "pyflakes"] + chunk,
capture_output=True, text=True,
encoding="utf-8", errors="replace")
all_lines.extend(
line for line in (proc.stdout or "").splitlines() if line.strip())
lines = all_lines
if not lines:
print(" ✓ 没有未定义名 / 未使用导入等问题")

Some files were not shown because too many files have changed in this diff Show More