diff --git a/_build/ab_quic.sh b/_build/ab_quic.sh new file mode 100644 index 0000000..1e78fd0 --- /dev/null +++ b/_build/ab_quic.sh @@ -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" diff --git a/_build/ab_tcp.sh b/_build/ab_tcp.sh new file mode 100644 index 0000000..43f10cc --- /dev/null +++ b/_build/ab_tcp.sh @@ -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 diff --git a/_build/ab_v3.sh b/_build/ab_v3.sh new file mode 100644 index 0000000..9d540d1 --- /dev/null +++ b/_build/ab_v3.sh @@ -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" diff --git a/_build/accept_181.sh b/_build/accept_181.sh new file mode 100644 index 0000000..5b13c79 --- /dev/null +++ b/_build/accept_181.sh @@ -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/^/ /' diff --git a/_build/add_cache_headers.py b/_build/add_cache_headers.py new file mode 100644 index 0000000..63debf1 --- /dev/null +++ b/_build/add_cache_headers.py @@ -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()) diff --git a/_build/add_site_cache.py b/_build/add_site_cache.py new file mode 100644 index 0000000..9de48b2 --- /dev/null +++ b/_build/add_site_cache.py @@ -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()) diff --git a/_build/bench.sh b/_build/bench.sh new file mode 100644 index 0000000..3932de9 --- /dev/null +++ b/_build/bench.sh @@ -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" diff --git a/_build/bench_download.sh b/_build/bench_download.sh new file mode 100644 index 0000000..867154d --- /dev/null +++ b/_build/bench_download.sh @@ -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" diff --git a/_build/bump_181.py b/_build/bump_181.py new file mode 100644 index 0000000..dea51c0 --- /dev/null +++ b/_build/bump_181.py @@ -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()) diff --git a/_build/check_cache.sh b/_build/check_cache.sh new file mode 100644 index 0000000..a13744c --- /dev/null +++ b/_build/check_cache.sh @@ -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/^/ /' diff --git a/_build/check_progress.sh b/_build/check_progress.sh new file mode 100644 index 0000000..e72c5d4 --- /dev/null +++ b/_build/check_progress.sh @@ -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" diff --git a/_build/dbg_cache_path.py b/_build/dbg_cache_path.py new file mode 100644 index 0000000..9dd2809 --- /dev/null +++ b/_build/dbg_cache_path.py @@ -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()) diff --git a/_build/diag_cache.sh b/_build/diag_cache.sh new file mode 100644 index 0000000..a52bcd7 --- /dev/null +++ b/_build/diag_cache.sh @@ -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/^/ /' diff --git a/_build/diag_tunnel_conns.sh b/_build/diag_tunnel_conns.sh new file mode 100644 index 0000000..468524f --- /dev/null +++ b/_build/diag_tunnel_conns.sh @@ -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/^/ /' diff --git a/_build/package_remote.py b/_build/package_remote.py index bd66fbc..baba459 100644 --- a/_build/package_remote.py +++ b/_build/package_remote.py @@ -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" diff --git a/_build/poll120_patch.py b/_build/poll120_patch.py new file mode 100644 index 0000000..6413f1d --- /dev/null +++ b/_build/poll120_patch.py @@ -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 秒,客户端超时给宽一点。 + * + *

必须显著大于服务端的 hold 时间,否则会在服务端正常返回 + * 之前自己先超时 —— 那就变成"每次轮询都失败"了。 + * 90 秒 = 60 + 30 秒余量。 + */ + private static final int POLL_TIMEOUT_MS = 90000;""", + """ /** + * 长轮询:中转那边挂 60 秒,客户端超时给宽一点。 + * + *

必须显著大于服务端的 hold 时间,否则会在服务端正常返回 + * 之前自己先超时 —— 那就变成"每次轮询都失败"了。 + * 120 秒 = 60(hold) + 60(网络余量)。 + * + *

余量给这么大是因为实测出现过一次 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()) diff --git a/_build/release_all.py b/_build/release_all.py index 6983dcb..561617d 100644 --- a/_build/release_all.py +++ b/_build/release_all.py @@ -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 diff --git a/_build/survey_qdisc.sh b/_build/survey_qdisc.sh new file mode 100644 index 0000000..8c2bff7 --- /dev/null +++ b/_build/survey_qdisc.sh @@ -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 diff --git a/_build/survey_tuning.sh b/_build/survey_tuning.sh new file mode 100644 index 0000000..a6f727e --- /dev/null +++ b/_build/survey_tuning.sh @@ -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/^/ /' diff --git a/_build/test_installer_cycle.py b/_build/test_installer_cycle.py index 4ec2ce1..accd14c 100644 --- a/_build/test_installer_cycle.py +++ b/_build/test_installer_cycle.py @@ -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" diff --git a/_build/test_public_clean.py b/_build/test_public_clean.py index 8796a57..d39cda2 100644 --- a/_build/test_public_clean.py +++ b/_build/test_public_clean.py @@ -54,6 +54,10 @@ DOCS_ALLOWLIST = { "LICENSE.txt", "项目规矩.md", # 这份文件本身就是在讲这条规矩 "免费托管方案.md", # 讲托管方案,不涉及 + # 开发流程文档:里面要写"发布脚本会检查更新说明里有没有管理页字眼", + # 属于描述规则本身,不是对外宣传材料。 + "双平台发布.md", + "EzHub-SSH诊断报告.md", # 给服务端运维看的排查报告 } # 官网源码里这些词也不许有(landing.py 渲染出来的页面) diff --git a/_build/upload_release.py b/_build/upload_release.py new file mode 100644 index 0000000..31f8085 --- /dev/null +++ b/_build/upload_release.py @@ -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()) diff --git a/_build/verify_cache.sh b/_build/verify_cache.sh new file mode 100644 index 0000000..5c3b0e0 --- /dev/null +++ b/_build/verify_cache.sh @@ -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(我会给出具体配置)。" diff --git a/_build/verify_cache2.sh b/_build/verify_cache2.sh new file mode 100644 index 0000000..6feddb6 --- /dev/null +++ b/_build/verify_cache2.sh @@ -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)" diff --git a/_build/verify_get_cache.sh b/_build/verify_get_cache.sh new file mode 100644 index 0000000..1a08e03 --- /dev/null +++ b/_build/verify_get_cache.sh @@ -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 diff --git a/_build/verify_head_get.sh b/_build/verify_head_get.sh new file mode 100644 index 0000000..343e0c2 --- /dev/null +++ b/_build/verify_head_get.sh @@ -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 diff --git a/_tests/audit_python_names.py b/_tests/audit_python_names.py index 7c3fecf..9e2bed6 100644 --- a/_tests/audit_python_names.py +++ b/_tests/audit_python_names.py @@ -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(" ✓ 没有未定义名 / 未使用导入等问题") diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$10.class b/_tests/out_app/com/lanremote/watch/MainActivity$10.class index bb22ba5..70e81b0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$10.class and b/_tests/out_app/com/lanremote/watch/MainActivity$10.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$100.class b/_tests/out_app/com/lanremote/watch/MainActivity$100.class index 43d0e53..a728478 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$100.class and b/_tests/out_app/com/lanremote/watch/MainActivity$100.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class index 7554c8e..2f632ce 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$101$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$101.class b/_tests/out_app/com/lanremote/watch/MainActivity$101.class index 7a26ecf..5a9911a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$101.class and b/_tests/out_app/com/lanremote/watch/MainActivity$101.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class index 55f4d63..6a21e38 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$102$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$102.class b/_tests/out_app/com/lanremote/watch/MainActivity$102.class index b693210..8d6390d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$102.class and b/_tests/out_app/com/lanremote/watch/MainActivity$102.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$103.class b/_tests/out_app/com/lanremote/watch/MainActivity$103.class index aa6d28e..7427312 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$103.class and b/_tests/out_app/com/lanremote/watch/MainActivity$103.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$104.class b/_tests/out_app/com/lanremote/watch/MainActivity$104.class index 3095b3e..e826df0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$104.class and b/_tests/out_app/com/lanremote/watch/MainActivity$104.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class index 04e130a..ae5994d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$105$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$105.class b/_tests/out_app/com/lanremote/watch/MainActivity$105.class index cfb7b80..f327f37 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$105.class and b/_tests/out_app/com/lanremote/watch/MainActivity$105.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class index 7b90509..72b9454 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$106$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class index 23d129e..52c9b9f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$106$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$106.class b/_tests/out_app/com/lanremote/watch/MainActivity$106.class index 0f212de..d27f169 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$106.class and b/_tests/out_app/com/lanremote/watch/MainActivity$106.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class index 37a8c26..c90da55 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$107$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class index 558b38f..2838f77 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$107$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$107.class b/_tests/out_app/com/lanremote/watch/MainActivity$107.class index 62565c8..cd9a46e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$107.class and b/_tests/out_app/com/lanremote/watch/MainActivity$107.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$108.class b/_tests/out_app/com/lanremote/watch/MainActivity$108.class index 0938cb0..f3384cb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$108.class and b/_tests/out_app/com/lanremote/watch/MainActivity$108.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$109.class b/_tests/out_app/com/lanremote/watch/MainActivity$109.class index f4ba85f..3a09909 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$109.class and b/_tests/out_app/com/lanremote/watch/MainActivity$109.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$11.class b/_tests/out_app/com/lanremote/watch/MainActivity$11.class index d36c0e4..ad96f36 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$11.class and b/_tests/out_app/com/lanremote/watch/MainActivity$11.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$110.class b/_tests/out_app/com/lanremote/watch/MainActivity$110.class index cb5481c..5d427d3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$110.class and b/_tests/out_app/com/lanremote/watch/MainActivity$110.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class index 8c43673..3df3aee 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$111$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class index f9e8c35..a1c573a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$111$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$111.class b/_tests/out_app/com/lanremote/watch/MainActivity$111.class index abeae8f..1644331 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$111.class and b/_tests/out_app/com/lanremote/watch/MainActivity$111.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class index 686542c..a986f76 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$112$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$112.class b/_tests/out_app/com/lanremote/watch/MainActivity$112.class index 82ed9d5..862087a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$112.class and b/_tests/out_app/com/lanremote/watch/MainActivity$112.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$113.class b/_tests/out_app/com/lanremote/watch/MainActivity$113.class index fecafd2..c46cfbb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$113.class and b/_tests/out_app/com/lanremote/watch/MainActivity$113.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$114.class b/_tests/out_app/com/lanremote/watch/MainActivity$114.class index f5b8860..d61b746 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$114.class and b/_tests/out_app/com/lanremote/watch/MainActivity$114.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class index 9309105..d557bf6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$115$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$115.class b/_tests/out_app/com/lanremote/watch/MainActivity$115.class index 771f4cd..1f52ab7 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$115.class and b/_tests/out_app/com/lanremote/watch/MainActivity$115.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class index d40fa9b..8489aa3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$116$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class index 3b894a9..33e4de6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$116$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$116.class b/_tests/out_app/com/lanremote/watch/MainActivity$116.class index 1fee8d5..8d5b530 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$116.class and b/_tests/out_app/com/lanremote/watch/MainActivity$116.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class index 6659c13..fd2bb4a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$117$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$117.class b/_tests/out_app/com/lanremote/watch/MainActivity$117.class index 6811eba..738bd36 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$117.class and b/_tests/out_app/com/lanremote/watch/MainActivity$117.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$118.class b/_tests/out_app/com/lanremote/watch/MainActivity$118.class index bdd0212..5791643 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$118.class and b/_tests/out_app/com/lanremote/watch/MainActivity$118.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$119.class b/_tests/out_app/com/lanremote/watch/MainActivity$119.class index efd80a1..0135574 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$119.class and b/_tests/out_app/com/lanremote/watch/MainActivity$119.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$12.class b/_tests/out_app/com/lanremote/watch/MainActivity$12.class index 5a72f4d..099ea1b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$12.class and b/_tests/out_app/com/lanremote/watch/MainActivity$12.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$120.class b/_tests/out_app/com/lanremote/watch/MainActivity$120.class index f9d66e4..6c7a76c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$120.class and b/_tests/out_app/com/lanremote/watch/MainActivity$120.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$121.class b/_tests/out_app/com/lanremote/watch/MainActivity$121.class index 16b2d7d..b3bdbb9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$121.class and b/_tests/out_app/com/lanremote/watch/MainActivity$121.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class index edf1273..350d182 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$122$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$122.class b/_tests/out_app/com/lanremote/watch/MainActivity$122.class index a0eb3d8..31f536f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$122.class and b/_tests/out_app/com/lanremote/watch/MainActivity$122.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$123.class b/_tests/out_app/com/lanremote/watch/MainActivity$123.class index d334155..d569473 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$123.class and b/_tests/out_app/com/lanremote/watch/MainActivity$123.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$124.class b/_tests/out_app/com/lanremote/watch/MainActivity$124.class index d751028..7df4222 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$124.class and b/_tests/out_app/com/lanremote/watch/MainActivity$124.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$125.class b/_tests/out_app/com/lanremote/watch/MainActivity$125.class index 5b574a9..7288323 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$125.class and b/_tests/out_app/com/lanremote/watch/MainActivity$125.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$126.class b/_tests/out_app/com/lanremote/watch/MainActivity$126.class index 93c3139..47e3946 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$126.class and b/_tests/out_app/com/lanremote/watch/MainActivity$126.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class index 0487185..3370e71 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$127$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class index 5494560..7985694 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class and b/_tests/out_app/com/lanremote/watch/MainActivity$127$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$127.class b/_tests/out_app/com/lanremote/watch/MainActivity$127.class index 7b8d566..740de24 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$127.class and b/_tests/out_app/com/lanremote/watch/MainActivity$127.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$128.class b/_tests/out_app/com/lanremote/watch/MainActivity$128.class index 5d854ea..4c11cad 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$128.class and b/_tests/out_app/com/lanremote/watch/MainActivity$128.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$129.class b/_tests/out_app/com/lanremote/watch/MainActivity$129.class index 958533d..d4152e0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$129.class and b/_tests/out_app/com/lanremote/watch/MainActivity$129.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$13.class b/_tests/out_app/com/lanremote/watch/MainActivity$13.class index 4a2a083..8b4544b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$13.class and b/_tests/out_app/com/lanremote/watch/MainActivity$13.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$14.class b/_tests/out_app/com/lanremote/watch/MainActivity$14.class index ad21b38..2fae3d1 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$14.class and b/_tests/out_app/com/lanremote/watch/MainActivity$14.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$15.class b/_tests/out_app/com/lanremote/watch/MainActivity$15.class index ff38e4a..7f37e61 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$15.class and b/_tests/out_app/com/lanremote/watch/MainActivity$15.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$16.class b/_tests/out_app/com/lanremote/watch/MainActivity$16.class index 12fc8b4..b99b9f6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$16.class and b/_tests/out_app/com/lanremote/watch/MainActivity$16.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$17.class b/_tests/out_app/com/lanremote/watch/MainActivity$17.class index 1a260ea..7a28841 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$17.class and b/_tests/out_app/com/lanremote/watch/MainActivity$17.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$18.class b/_tests/out_app/com/lanremote/watch/MainActivity$18.class index db5c152..5959eee 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$18.class and b/_tests/out_app/com/lanremote/watch/MainActivity$18.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$19.class b/_tests/out_app/com/lanremote/watch/MainActivity$19.class index c3358e8..418d9bb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$19.class and b/_tests/out_app/com/lanremote/watch/MainActivity$19.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$20.class b/_tests/out_app/com/lanremote/watch/MainActivity$20.class index 1da613a..4c273dc 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$20.class and b/_tests/out_app/com/lanremote/watch/MainActivity$20.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$21.class b/_tests/out_app/com/lanremote/watch/MainActivity$21.class index f159b62..caa67d6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$21.class and b/_tests/out_app/com/lanremote/watch/MainActivity$21.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$22.class b/_tests/out_app/com/lanremote/watch/MainActivity$22.class index cbeb1b6..f71a311 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$22.class and b/_tests/out_app/com/lanremote/watch/MainActivity$22.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$23.class b/_tests/out_app/com/lanremote/watch/MainActivity$23.class index 9c46b70..dac8961 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$23.class and b/_tests/out_app/com/lanremote/watch/MainActivity$23.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$24.class b/_tests/out_app/com/lanremote/watch/MainActivity$24.class index 8bf5675..87cc064 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$24.class and b/_tests/out_app/com/lanremote/watch/MainActivity$24.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$25.class b/_tests/out_app/com/lanremote/watch/MainActivity$25.class index b23d366..4b128c5 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$25.class and b/_tests/out_app/com/lanremote/watch/MainActivity$25.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$26.class b/_tests/out_app/com/lanremote/watch/MainActivity$26.class index 5474f2c..a085ae5 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$26.class and b/_tests/out_app/com/lanremote/watch/MainActivity$26.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$27.class b/_tests/out_app/com/lanremote/watch/MainActivity$27.class index 8a547a3..bbe18d4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$27.class and b/_tests/out_app/com/lanremote/watch/MainActivity$27.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$28.class b/_tests/out_app/com/lanremote/watch/MainActivity$28.class index b22c60d..0336e8b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$28.class and b/_tests/out_app/com/lanremote/watch/MainActivity$28.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$29.class b/_tests/out_app/com/lanremote/watch/MainActivity$29.class index 0d25f07..76c20ba 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$29.class and b/_tests/out_app/com/lanremote/watch/MainActivity$29.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$30.class b/_tests/out_app/com/lanremote/watch/MainActivity$30.class index 415523e..5d7da84 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$30.class and b/_tests/out_app/com/lanremote/watch/MainActivity$30.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$31.class b/_tests/out_app/com/lanremote/watch/MainActivity$31.class index 15419df..df011c4 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$31.class and b/_tests/out_app/com/lanremote/watch/MainActivity$31.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$32.class b/_tests/out_app/com/lanremote/watch/MainActivity$32.class index c41ba9a..d9ce704 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$32.class and b/_tests/out_app/com/lanremote/watch/MainActivity$32.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$33.class b/_tests/out_app/com/lanremote/watch/MainActivity$33.class index ae5e689..5a2171a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$33.class and b/_tests/out_app/com/lanremote/watch/MainActivity$33.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$34.class b/_tests/out_app/com/lanremote/watch/MainActivity$34.class index b73de35..7f1f5d2 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$34.class and b/_tests/out_app/com/lanremote/watch/MainActivity$34.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$35.class b/_tests/out_app/com/lanremote/watch/MainActivity$35.class index 25d42ad..b0ffbe3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$35.class and b/_tests/out_app/com/lanremote/watch/MainActivity$35.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$36.class b/_tests/out_app/com/lanremote/watch/MainActivity$36.class index a6d9c6a..136708c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$36.class and b/_tests/out_app/com/lanremote/watch/MainActivity$36.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$37.class b/_tests/out_app/com/lanremote/watch/MainActivity$37.class index fffbc66..fffd179 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$37.class and b/_tests/out_app/com/lanremote/watch/MainActivity$37.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$38.class b/_tests/out_app/com/lanremote/watch/MainActivity$38.class index 05a68b9..0dfdeb6 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$38.class and b/_tests/out_app/com/lanremote/watch/MainActivity$38.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$39.class b/_tests/out_app/com/lanremote/watch/MainActivity$39.class index cd86486..c443f68 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$39.class and b/_tests/out_app/com/lanremote/watch/MainActivity$39.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$40.class b/_tests/out_app/com/lanremote/watch/MainActivity$40.class index b06760e..72a0479 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$40.class and b/_tests/out_app/com/lanremote/watch/MainActivity$40.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$41.class b/_tests/out_app/com/lanremote/watch/MainActivity$41.class index b044b7a..f237493 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$41.class and b/_tests/out_app/com/lanremote/watch/MainActivity$41.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$42.class b/_tests/out_app/com/lanremote/watch/MainActivity$42.class index 04cb4be..255ad65 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$42.class and b/_tests/out_app/com/lanremote/watch/MainActivity$42.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$43.class b/_tests/out_app/com/lanremote/watch/MainActivity$43.class index 7fc69ac..8a9d186 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$43.class and b/_tests/out_app/com/lanremote/watch/MainActivity$43.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$44.class b/_tests/out_app/com/lanremote/watch/MainActivity$44.class index f94a03d..877890a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$44.class and b/_tests/out_app/com/lanremote/watch/MainActivity$44.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$45.class b/_tests/out_app/com/lanremote/watch/MainActivity$45.class index 1a68904..a751be7 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$45.class and b/_tests/out_app/com/lanremote/watch/MainActivity$45.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$46.class b/_tests/out_app/com/lanremote/watch/MainActivity$46.class index 2e732e9..447ba90 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$46.class and b/_tests/out_app/com/lanremote/watch/MainActivity$46.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$47.class b/_tests/out_app/com/lanremote/watch/MainActivity$47.class index 833b21c..9f2c40d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$47.class and b/_tests/out_app/com/lanremote/watch/MainActivity$47.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$48.class b/_tests/out_app/com/lanremote/watch/MainActivity$48.class index 421adea..fa89cdb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$48.class and b/_tests/out_app/com/lanremote/watch/MainActivity$48.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$49.class b/_tests/out_app/com/lanremote/watch/MainActivity$49.class index d9b384f..2c9dd6b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$49.class and b/_tests/out_app/com/lanremote/watch/MainActivity$49.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$50.class b/_tests/out_app/com/lanremote/watch/MainActivity$50.class index 0183dee..6698573 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$50.class and b/_tests/out_app/com/lanremote/watch/MainActivity$50.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$51.class b/_tests/out_app/com/lanremote/watch/MainActivity$51.class index cc2776a..dda304b 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$51.class and b/_tests/out_app/com/lanremote/watch/MainActivity$51.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$52.class b/_tests/out_app/com/lanremote/watch/MainActivity$52.class index 9fdea66..7843d99 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$52.class and b/_tests/out_app/com/lanremote/watch/MainActivity$52.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$53.class b/_tests/out_app/com/lanremote/watch/MainActivity$53.class index 8ba8eae..eca2cce 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$53.class and b/_tests/out_app/com/lanremote/watch/MainActivity$53.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$54.class b/_tests/out_app/com/lanremote/watch/MainActivity$54.class index 343215b..5ee0ead 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$54.class and b/_tests/out_app/com/lanremote/watch/MainActivity$54.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$55.class b/_tests/out_app/com/lanremote/watch/MainActivity$55.class index d5bcd91..95a9372 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$55.class and b/_tests/out_app/com/lanremote/watch/MainActivity$55.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$56.class b/_tests/out_app/com/lanremote/watch/MainActivity$56.class index c7e296a..067f8bb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$56.class and b/_tests/out_app/com/lanremote/watch/MainActivity$56.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$57.class b/_tests/out_app/com/lanremote/watch/MainActivity$57.class index 7c28722..5f6ee79 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$57.class and b/_tests/out_app/com/lanremote/watch/MainActivity$57.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class index 1676c0f..3a28694 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$58$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$58.class b/_tests/out_app/com/lanremote/watch/MainActivity$58.class index 0020652..da39319 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$58.class and b/_tests/out_app/com/lanremote/watch/MainActivity$58.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$59.class b/_tests/out_app/com/lanremote/watch/MainActivity$59.class index 3aab4ae..18d9e66 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$59.class and b/_tests/out_app/com/lanremote/watch/MainActivity$59.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$60.class b/_tests/out_app/com/lanremote/watch/MainActivity$60.class index 8e595a8..ac6b26a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$60.class and b/_tests/out_app/com/lanremote/watch/MainActivity$60.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$61.class b/_tests/out_app/com/lanremote/watch/MainActivity$61.class index 98f040f..b6b4c6d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$61.class and b/_tests/out_app/com/lanremote/watch/MainActivity$61.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$62.class b/_tests/out_app/com/lanremote/watch/MainActivity$62.class index 1a1d0a6..c5a2ac7 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$62.class and b/_tests/out_app/com/lanremote/watch/MainActivity$62.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$63.class b/_tests/out_app/com/lanremote/watch/MainActivity$63.class index c88e4dc..5f6d985 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$63.class and b/_tests/out_app/com/lanremote/watch/MainActivity$63.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class index b9a807b..830b048 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$64$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class index eea71fd..15c13f0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class and b/_tests/out_app/com/lanremote/watch/MainActivity$64$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$64.class b/_tests/out_app/com/lanremote/watch/MainActivity$64.class index 681a19b..6e91325 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$64.class and b/_tests/out_app/com/lanremote/watch/MainActivity$64.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$65.class b/_tests/out_app/com/lanremote/watch/MainActivity$65.class index 5cd7553..bbbdbd5 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$65.class and b/_tests/out_app/com/lanremote/watch/MainActivity$65.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$66.class b/_tests/out_app/com/lanremote/watch/MainActivity$66.class index d882b5c..51656ff 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$66.class and b/_tests/out_app/com/lanremote/watch/MainActivity$66.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$67.class b/_tests/out_app/com/lanremote/watch/MainActivity$67.class index d548e41..0af9859 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$67.class and b/_tests/out_app/com/lanremote/watch/MainActivity$67.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class index 5d57835..6e9d96e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$68$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class b/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class index ad9d279..08d0d0d 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class and b/_tests/out_app/com/lanremote/watch/MainActivity$68$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$68.class b/_tests/out_app/com/lanremote/watch/MainActivity$68.class index 1db3494..8ab658c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$68.class and b/_tests/out_app/com/lanremote/watch/MainActivity$68.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$69.class b/_tests/out_app/com/lanremote/watch/MainActivity$69.class index 761f493..91602ab 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$69.class and b/_tests/out_app/com/lanremote/watch/MainActivity$69.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$70.class b/_tests/out_app/com/lanremote/watch/MainActivity$70.class index f170b8a..9801c99 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$70.class and b/_tests/out_app/com/lanremote/watch/MainActivity$70.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$71.class b/_tests/out_app/com/lanremote/watch/MainActivity$71.class index f6d0435..2164ea3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$71.class and b/_tests/out_app/com/lanremote/watch/MainActivity$71.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$72.class b/_tests/out_app/com/lanremote/watch/MainActivity$72.class index e5dbcd3..afc7407 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$72.class and b/_tests/out_app/com/lanremote/watch/MainActivity$72.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$73.class b/_tests/out_app/com/lanremote/watch/MainActivity$73.class index 1486d7b..2102303 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$73.class and b/_tests/out_app/com/lanremote/watch/MainActivity$73.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$74.class b/_tests/out_app/com/lanremote/watch/MainActivity$74.class index 1c4bcc8..47eaad3 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$74.class and b/_tests/out_app/com/lanremote/watch/MainActivity$74.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$75.class b/_tests/out_app/com/lanremote/watch/MainActivity$75.class index 2f8af4d..4564955 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$75.class and b/_tests/out_app/com/lanremote/watch/MainActivity$75.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class index c833c43..6531ee2 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$76$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class index 6d01d7b..bc86074 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$76$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$76.class b/_tests/out_app/com/lanremote/watch/MainActivity$76.class index dc0b0c9..f0bfb2c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$76.class and b/_tests/out_app/com/lanremote/watch/MainActivity$76.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$77.class b/_tests/out_app/com/lanremote/watch/MainActivity$77.class index 4973a18..9cb8a47 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$77.class and b/_tests/out_app/com/lanremote/watch/MainActivity$77.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class index 51caca4..2ed5c3f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$78$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$78.class b/_tests/out_app/com/lanremote/watch/MainActivity$78.class index 6f05dbb..0b55743 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$78.class and b/_tests/out_app/com/lanremote/watch/MainActivity$78.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class index b4ae574..5195bda 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$79$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class index b30cf9c..50e66fb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$79$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$79.class b/_tests/out_app/com/lanremote/watch/MainActivity$79.class index b509168..085fb8f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$79.class and b/_tests/out_app/com/lanremote/watch/MainActivity$79.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class index 1dc91a3..57af34e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$80$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class index 4e3f601..0b16ff9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$80$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$80.class b/_tests/out_app/com/lanremote/watch/MainActivity$80.class index a7865c2..7a8e975 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$80.class and b/_tests/out_app/com/lanremote/watch/MainActivity$80.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$81.class b/_tests/out_app/com/lanremote/watch/MainActivity$81.class index 85e0755..9de41f8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$81.class and b/_tests/out_app/com/lanremote/watch/MainActivity$81.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$82.class b/_tests/out_app/com/lanremote/watch/MainActivity$82.class index 5eb075b..de1b94a 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$82.class and b/_tests/out_app/com/lanremote/watch/MainActivity$82.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$83.class b/_tests/out_app/com/lanremote/watch/MainActivity$83.class index 7f5dd94..3ae1497 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$83.class and b/_tests/out_app/com/lanremote/watch/MainActivity$83.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$84.class b/_tests/out_app/com/lanremote/watch/MainActivity$84.class index 7ee047c..b901e5f 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$84.class and b/_tests/out_app/com/lanremote/watch/MainActivity$84.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$85.class b/_tests/out_app/com/lanremote/watch/MainActivity$85.class index 4fe9553..94d7ec0 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$85.class and b/_tests/out_app/com/lanremote/watch/MainActivity$85.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class index 5ae0dd4..cbc51f9 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$86$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$86.class b/_tests/out_app/com/lanremote/watch/MainActivity$86.class index ccc9673..568be19 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$86.class and b/_tests/out_app/com/lanremote/watch/MainActivity$86.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$87.class b/_tests/out_app/com/lanremote/watch/MainActivity$87.class index 3257b86..f1fe9eb 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$87.class and b/_tests/out_app/com/lanremote/watch/MainActivity$87.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$88.class b/_tests/out_app/com/lanremote/watch/MainActivity$88.class index dbeaabe..3732d3e 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$88.class and b/_tests/out_app/com/lanremote/watch/MainActivity$88.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$89.class b/_tests/out_app/com/lanremote/watch/MainActivity$89.class index c648502..305cbac 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$89.class and b/_tests/out_app/com/lanremote/watch/MainActivity$89.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$90.class b/_tests/out_app/com/lanremote/watch/MainActivity$90.class index 3506d21..c429f91 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$90.class and b/_tests/out_app/com/lanremote/watch/MainActivity$90.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class index dad9562..ce1bd9c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$91$1$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class index f4a522f..98205ca 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$91$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$91.class b/_tests/out_app/com/lanremote/watch/MainActivity$91.class index 36053c9..55b1829 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$91.class and b/_tests/out_app/com/lanremote/watch/MainActivity$91.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class index 4195a82..af75842 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$92$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$92.class b/_tests/out_app/com/lanremote/watch/MainActivity$92.class index fd3af80..e099e82 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$92.class and b/_tests/out_app/com/lanremote/watch/MainActivity$92.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$93.class b/_tests/out_app/com/lanremote/watch/MainActivity$93.class index 3506fa4..f1dc583 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$93.class and b/_tests/out_app/com/lanremote/watch/MainActivity$93.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class b/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class index 14097f2..bfc8dfa 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class and b/_tests/out_app/com/lanremote/watch/MainActivity$94$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$94.class b/_tests/out_app/com/lanremote/watch/MainActivity$94.class index d010e17..a25516c 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$94.class and b/_tests/out_app/com/lanremote/watch/MainActivity$94.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$95.class b/_tests/out_app/com/lanremote/watch/MainActivity$95.class index 8dc5a4c..b267628 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$95.class and b/_tests/out_app/com/lanremote/watch/MainActivity$95.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$96.class b/_tests/out_app/com/lanremote/watch/MainActivity$96.class index e4f4da1..d21cbe8 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$96.class and b/_tests/out_app/com/lanremote/watch/MainActivity$96.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$97.class b/_tests/out_app/com/lanremote/watch/MainActivity$97.class index 9b0e28b..c857848 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$97.class and b/_tests/out_app/com/lanremote/watch/MainActivity$97.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$98.class b/_tests/out_app/com/lanremote/watch/MainActivity$98.class index 335bd45..faedad1 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$98.class and b/_tests/out_app/com/lanremote/watch/MainActivity$98.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$99.class b/_tests/out_app/com/lanremote/watch/MainActivity$99.class index ad946f6..e447094 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$99.class and b/_tests/out_app/com/lanremote/watch/MainActivity$99.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$Act.class b/_tests/out_app/com/lanremote/watch/MainActivity$Act.class index 69ee296..62e48d5 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$Act.class and b/_tests/out_app/com/lanremote/watch/MainActivity$Act.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class b/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class index 63c1f03..ae3ee30 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class and b/_tests/out_app/com/lanremote/watch/MainActivity$TabDef.class differ diff --git a/_tests/out_app/com/lanremote/watch/MainActivity.class b/_tests/out_app/com/lanremote/watch/MainActivity.class index 7d26a69..a4b82ae 100644 Binary files a/_tests/out_app/com/lanremote/watch/MainActivity.class and b/_tests/out_app/com/lanremote/watch/MainActivity.class differ diff --git a/_tests/out_app/com/lanremote/watch/RelayTransport$1.class b/_tests/out_app/com/lanremote/watch/RelayTransport$1.class index 0a88b8a..28c7808 100644 Binary files a/_tests/out_app/com/lanremote/watch/RelayTransport$1.class and b/_tests/out_app/com/lanremote/watch/RelayTransport$1.class differ diff --git a/_tests/out_app/com/lanremote/watch/RelayTransport$2.class b/_tests/out_app/com/lanremote/watch/RelayTransport$2.class index af05f9f..c6a5704 100644 Binary files a/_tests/out_app/com/lanremote/watch/RelayTransport$2.class and b/_tests/out_app/com/lanremote/watch/RelayTransport$2.class differ diff --git a/_tests/out_app/com/lanremote/watch/RelayTransport$3.class b/_tests/out_app/com/lanremote/watch/RelayTransport$3.class index 17078f3..ae46af6 100644 Binary files a/_tests/out_app/com/lanremote/watch/RelayTransport$3.class and b/_tests/out_app/com/lanremote/watch/RelayTransport$3.class differ diff --git a/_tests/out_app/com/lanremote/watch/RelayTransport$4.class b/_tests/out_app/com/lanremote/watch/RelayTransport$4.class index 7979f4a..3dc329e 100644 Binary files a/_tests/out_app/com/lanremote/watch/RelayTransport$4.class and b/_tests/out_app/com/lanremote/watch/RelayTransport$4.class differ diff --git a/_tests/out_app/com/lanremote/watch/RelayTransport$HttpResult.class b/_tests/out_app/com/lanremote/watch/RelayTransport$HttpResult.class index f7cfeee..214d537 100644 Binary files a/_tests/out_app/com/lanremote/watch/RelayTransport$HttpResult.class and b/_tests/out_app/com/lanremote/watch/RelayTransport$HttpResult.class differ diff --git a/_tests/out_app/com/lanremote/watch/RelayTransport.class b/_tests/out_app/com/lanremote/watch/RelayTransport.class index 5de8d1e..8da9d94 100644 Binary files a/_tests/out_app/com/lanremote/watch/RelayTransport.class and b/_tests/out_app/com/lanremote/watch/RelayTransport.class differ diff --git a/_tests/shots/1-屏幕中间.png b/_tests/shots/1-屏幕中间.png index 04bcb0b..b5a055c 100644 Binary files a/_tests/shots/1-屏幕中间.png and b/_tests/shots/1-屏幕中间.png differ diff --git a/_tests/shots/3-自定义640x400.png b/_tests/shots/3-自定义640x400.png index d0daadc..d404904 100644 Binary files a/_tests/shots/3-自定义640x400.png and b/_tests/shots/3-自定义640x400.png differ diff --git a/_tests/test_apk_contents.py b/_tests/test_apk_contents.py index 9da0112..5f363e8 100644 --- a/_tests/test_apk_contents.py +++ b/_tests/test_apk_contents.py @@ -23,7 +23,7 @@ def _latest_apk() -> Path: (ROOT / "dist").glob("*watch*.apk"), key=lambda p: p.stat().st_mtime, reverse=True) return candidates[0] if candidates else \ - ROOT / "dist" / "ykxmz-watch-1.8.0.apk" + ROOT / "dist" / "ykxmz-watch-1.8.1.apk" APK = _latest_apk() diff --git a/_tests/test_package_remote.py b/_tests/test_package_remote.py index be9a256..2db82f5 100644 --- a/_tests/test_package_remote.py +++ b/_tests/test_package_remote.py @@ -26,9 +26,9 @@ ROOT = Path(__file__).resolve().parent.parent # 被控端 -> 一个 zip # 服务器端 -> 一个 zip # 控制端 -> 直接给 APK(不打 zip) -AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.8.0.zip" -SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.8.0.zip" -WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.8.0.apk" +AGENT_ZIP = ROOT / "dist" / "ykxmz-agent-1.8.1.zip" +SERVER_ZIP = ROOT / "dist" / "ykxmz-server-1.8.1.zip" +WATCH_APK = ROOT / "dist" / "ykxmz-watch-1.8.1.apk" TOOLCHAIN = Path(r"C:\Users\shu\Desktop\deepseekagent\starklink\toolchain") JAVAC = TOOLCHAIN / "jdk17" / "bin" / "javac.exe" JAVA = TOOLCHAIN / "jdk17" / "bin" / "java.exe" @@ -89,7 +89,7 @@ def main() -> int: for label, path in (("被控端", agent_dir / "README-agent.txt"), ("服务器端", relay_dir / "README-server.txt"), ("控制端(旁边那份)", - ROOT / "dist" / "ykxmz-watch-1.8.0-说明.txt")): + ROOT / "dist" / "ykxmz-watch-1.8.1-说明.txt")): raw = path.read_bytes() if path.is_file() else b"" check(f"{label}说明有 UTF-8 BOM", raw[:3] == b"\xef\xbb\xbf", raw[:3]) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 103f525..30ca628 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="1.8.1"> diff --git a/android/build_apk.ps1 b/android/build_apk.ps1 index 001ba42..369b980 100644 --- a/android/build_apk.ps1 +++ b/android/build_apk.ps1 @@ -27,7 +27,7 @@ $d8 = Join-Path $bt "d8.bat" $work = Join-Path $android "build" $outDir = Join-Path $root "dist" -$apkName = "ykxmz-watch-1.8.0.apk" +$apkName = "ykxmz-watch-1.8.1.apk" function Step($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan } @@ -65,7 +65,7 @@ $unsigned = Join-Path $work "app-unsigned.apk" --min-sdk-version 24 ` --target-sdk-version 34 ` --version-code 5 ` - --version-name "1.8.0" ` + --version-name "1.8.1" ` --auto-add-overlay if ($LASTEXITCODE -ne 0) { throw "aapt2 link 失败" } diff --git a/android/src/com/lanremote/watch/RelayTransport.java b/android/src/com/lanremote/watch/RelayTransport.java index fae896a..0bbcdbc 100644 --- a/android/src/com/lanremote/watch/RelayTransport.java +++ b/android/src/com/lanremote/watch/RelayTransport.java @@ -41,9 +41,13 @@ public class RelayTransport implements Transport { * *

必须显著大于服务端的 hold 时间,否则会在服务端正常返回 * 之前自己先超时 —— 那就变成"每次轮询都失败"了。 - * 90 秒 = 60 + 30 秒余量。 + * 120 秒 = 60(hold) + 60(网络余量)。 + * + *

余量给这么大是因为实测出现过一次 93589ms 的长尾 + * (4G 丢包触发 TCP 重传退避)。设 90 秒会把那次 + * 其实成功的请求误判成失败。 */ - private static final int POLL_TIMEOUT_MS = 90000; + private static final int POLL_TIMEOUT_MS = 120000; /** 发送/接收的单次请求超时 */ private static final int SEND_TIMEOUT_MS = 30000; /** 网络出错后的退避 */ diff --git a/installer/ykxmz.iss b/installer/ykxmz.iss index 6f3b671..fe0c168 100644 --- a/installer/ykxmz.iss +++ b/installer/ykxmz.iss @@ -27,7 +27,7 @@ ; ============================================================================ #define MyAppName "ykxmz" -#define MyAppVersion "1.8.0" +#define MyAppVersion "1.8.1" #define MyAppPublisher "小麦子" #define MyAppExeName "ykxmz.exe" @@ -149,7 +149,7 @@ Filename: "{app}\{#MyAppExeName}"; Parameters: "--autostart on"; \ ; 代价是安装器和它是父子关系:用"等整个进程树"的方式调安装器会卡住 ; (PowerShell 的 Start-Process -Wait 就是)。 ; 学校批量部署请用 cmd: -; cmd /c ykxmz-setup-1.8.0.exe /VERYSILENT /CODE=123456 +; cmd /c ykxmz-setup-1.8.1.exe /VERYSILENT /CODE=123456 ; cmd 只等安装器本身,不等孙子进程,不会卡。 Filename: "{app}\{#MyAppExeName}"; Parameters: "--silent"; \ Description: "立即启动(后台运行,不显示窗口)"; \ diff --git a/relay/relay.py b/relay/relay.py index 242b371..58eaf18 100644 --- a/relay/relay.py +++ b/relay/relay.py @@ -59,7 +59,7 @@ from collections import deque from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path -VERSION = "1.8.0" +VERSION = "1.8.1" # ---------------- 参数(都能用环境变量覆盖)---------------- @@ -901,8 +901,15 @@ class Handler(BaseHTTPRequestHandler): self.send_response(code) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(body))) - self.send_header("Cache-Control", "no-store") - for name, value in (extra or {}).items(): + # ⚠ 默认 no-store,但**如果调用方在 extra 里给了 Cache-Control, + # 就不要再发默认的了** —— 否则会有两个 Cache-Control 头, + # 客户端/Cloudflare 只看第一个,调用方的设置就被吃掉了。 + # (这个坑真踩了:首页明明传了 public,max-age=60, + # 响应里却还是 no-store,Cloudflare 一直 DYNAMIC 不缓存。) + extra = extra or {} + if not any(name.lower() == "cache-control" for name in extra): + self.send_header("Cache-Control", "no-store") + for name, value in extra.items(): self.send_header(name, value) self.end_headers() if body: @@ -1061,13 +1068,16 @@ class Handler(BaseHTTPRequestHandler): self.send_response(info["status"]) self.send_header("Content-Type", info["ctype"]) self.send_header("Content-Length", str(info["length"])) - for name, value in (info.get("extra") or {}).items(): - self.send_header(name, value) - # 静态图可以缓存,跟 GET 那边保持一致 - if path.startswith("/shot/"): - self.send_header("Cache-Control", "public, max-age=86400") - else: + extra = info.get("extra") or {} + # ⚠ HEAD 的响应头必须和 GET 一致。 + # 否则下载器/代理用 HEAD 探测时会拿到 no-store 和错误的长度, + # 进而误判文件不可缓存、甚至误判成空文件。 + # (这个不一致坑过一次:我拿 curl -I 验证 GET 的缓存改动, + # 看到的全是 no-store,白查了半天 —— 因为 -I 发的是 HEAD。) + if not any(name.lower() == "cache-control" for name in extra): self.send_header("Cache-Control", "no-store") + for name, value in extra.items(): + self.send_header(name, value) self.end_headers() def _as_bytes(self, value) -> bytes: @@ -1102,9 +1112,16 @@ class Handler(BaseHTTPRequestHandler): return {"status": status, "ctype": "text/plain; charset=utf-8", "length": 0, "extra": {}} + # 各路由必须带上**和 GET 完全一样**的缓存头,否则用 HEAD 探测的 + # 下载器/代理会拿到 no-store,误判成不可缓存。 + CACHE_SITE = {"Cache-Control": "public, max-age=60"} + CACHE_ICON = {"Cache-Control": "public, max-age=86400"} + CACHE_SHOT = {"Cache-Control": "public, max-age=86400"} + CACHE_DL = {"Cache-Control": "public, max-age=604800, immutable"} + if path == "/": body = self._as_bytes(_site_page()) - return ok("text/html; charset=utf-8", len(body)) + return ok("text/html; charset=utf-8", len(body), CACHE_SITE) if path in ("/v1/health", "/v1/downloads"): return ok("application/json", 2) if path in ("/url", "/v1/url"): @@ -1112,7 +1129,7 @@ class Handler(BaseHTTPRequestHandler): if path in ("/favicon.ico", "/shot/favicon.ico"): icon = Path(__file__).resolve().parent / "favicon.ico" if icon.is_file(): - return ok("image/x-icon", icon.stat().st_size) + return ok("image/x-icon", icon.stat().st_size, CACHE_ICON) return no() if path == ADMIN_PATH or path == "/admin" or path == "/login": # 注意:_login_page() 返回的**已经是 bytes**(它的签名是 -> bytes), @@ -1134,7 +1151,7 @@ class Handler(BaseHTTPRequestHandler): (".png", ".jpg", ".jpeg", ".webp")): ctype = ("image/png" if name.lower().endswith(".png") else "image/jpeg") - return ok(ctype, target.stat().st_size) + return ok(ctype, target.stat().st_size, CACHE_SHOT) return no() # ---- 下载区 ---- @@ -1158,9 +1175,10 @@ class Handler(BaseHTTPRequestHandler): else: ctype = "application/octet-stream" quoted = urllib.parse.quote(safe) - return ok(ctype, target.stat().st_size, { - "Content-Disposition": - "attachment; filename*=UTF-8''%s" % quoted}) + headers = dict(CACHE_DL) + headers["Content-Disposition"] = ( + "attachment; filename*=UTF-8''%s" % quoted) + return ok(ctype, target.stat().st_size, headers) return no() @@ -1283,7 +1301,13 @@ class Handler(BaseHTTPRequestHandler): self.send_header( "Content-Disposition", f"attachment; filename*=UTF-8''{quoted}") - self.send_header("Cache-Control", "no-store") + # ★ 让 Cloudflare 在边缘缓存这个文件。 + # 原来这里是 no-store,于是每次下载都要穿隧道回 4G 源站 —— + # 实测吞吐只有 3~67 KB/s(15 MB 的安装包根本下不动)。 + # 文件名带版本号(ykxmz-setup-1.8.1.exe),同一名字内容 + # 永不改变,所以可以放心长缓存。 + self.send_header("Cache-Control", + "public, max-age=604800, immutable") self.end_headers() # 分块流式发送,不要一次性 read_bytes 再 write —— @@ -1391,8 +1415,11 @@ class Handler(BaseHTTPRequestHandler): # ---- 官网首页(公开,不用登录)---- if path == "/": + # 缓存 60 秒:挡住"刷新就重新回源"的浪费, + # 又不会让页面上的当前版本号和下载按钮过期太久。 self._send(200, _site_page().encode("utf-8"), - "text/html; charset=utf-8") + "text/html; charset=utf-8", + {"Cache-Control": "public, max-age=60"}) return # ---- 管理后台 ---- diff --git a/windows/lanremote/__init__.py b/windows/lanremote/__init__.py index fe336bd..11be5c2 100644 --- a/windows/lanremote/__init__.py +++ b/windows/lanremote/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- """局域网遥控 · Windows 被控端。""" -__version__ = "1.8.0" +__version__ = "1.8.1" APP_NAME = "ykxmz" diff --git a/windows/lanremote/autostart.py b/windows/lanremote/autostart.py index 3318cd8..c12f0ea 100644 --- a/windows/lanremote/autostart.py +++ b/windows/lanremote/autostart.py @@ -5,7 +5,6 @@ """ from __future__ import annotations -import os import subprocess import sys from pathlib import Path diff --git a/windows/lanremote/netcheck.py b/windows/lanremote/netcheck.py index ad70917..bff330a 100644 --- a/windows/lanremote/netcheck.py +++ b/windows/lanremote/netcheck.py @@ -16,7 +16,6 @@ """ from __future__ import annotations -import os import socket import subprocess import sys diff --git a/windows/lanremote/relay.py b/windows/lanremote/relay.py index 886ac39..bf18419 100644 --- a/windows/lanremote/relay.py +++ b/windows/lanremote/relay.py @@ -34,8 +34,10 @@ import urllib.request # 长轮询单次请求的超时。 # 必须显著大于服务端的 hold 时间(现在 60 秒),否则会在服务端 # 正常返回之前自己先超时 —— 那就变成"每次轮询都失败"了。 -# 90 = 60 + 30 秒余量。 -POLL_TIMEOUT = 90.0 +# 120 = 60(hold) + 60(网络余量)。 +# 为什么余量给这么大:实测出现过一次 93589ms 的长尾 +# (4G 丢包触发 TCP 重传退避),原来设 90 秒会把它误判成失败。 +POLL_TIMEOUT = 120.0 # 单次请求体上限(一个 256KB 分块 Base64 后约 350KB,给足余量) MAX_BODY = 8 * 1024 * 1024 # 网络断了之后的退避