mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 23:00:44 +00:00
== 功能一:允许新设备接入开关 ==
关掉之后只有已经登记过的电脑能继续上线,没见过的新设备一律拒绝。
状态存在 relay_state.json 里,重启不丢。
判断新设备用的是 deviceId,不是配对码 ——
这一点很关键:管理员给老设备改过码之后,
那台设备必须仍然能进来;按码判断的话它会被自己的开关挡在外面。
测试里专门有一条 A3b 守这个行为。
被拒时回 blocked=True,让客户端能区分被管理员挡了
和配对码冲突,而不是去匹配中文错误文案(文案一改就坏)。
== 功能二:修改任意设备的配对码 ==
管理页每台设备一个「改码」按钮,填 6 位数字即生效。
要点:
* 新旧码冲突检查
* 旧码映射必须删掉,否则变成孤儿条目,
管理页配对码占用的数字会虚高
* 改码踢掉该设备当前会话(会话绑在码上)
* 立刻持久化
== 顺手修掉两个真 bug ==
1. 改码功能一上线就 500:Session 用了 __slots__,
根本没有 session_id 属性(会话是以 token 为 key 存在 dict 里的),
写 s.session_id 直接 AttributeError。
改成遍历 sessions.items() 拿 key。
2. snapshot() 里有同样的错(清理超时设备那段):
平时 stale 列表是空的所以从没炸过,一旦真有设备超过 6 小时
没上报就会 500。这次一起修了。
== 管理页 UI ==
* 设备小节加了「允许新设备接入」勾选框
* 每行加「改码」按钮(走 prompt,不引入弹窗层)
* 改码按钮用 data-recode 而不是 data-device ——
现有单台删除按 button.danger[data-device] 绑,
带上 data-device 会被当成删除按钮,点改码变删设备。
测试 C6 专门断言这一点。
== 测试 ==
新增 _tests/test_admin_device_control.py(23 项),
已注册进 _tests/verify_all.ps1 第 3e 阶段。
129 lines
5.5 KiB
Bash
129 lines
5.5 KiB
Bash
#!/bin/bash
|
|
# 双协议隧道:同时跑 QUIC 和 HTTP/2 两个 cloudflared 实例。
|
|
#
|
|
# == 为什么 ==
|
|
#
|
|
# 实测两种协议各有各的病:
|
|
# QUIC 尾部延迟好(P90 ~2.5 秒),但 DialContext 会
|
|
# "no recent network activity" 超时,建连慢、偶发失败
|
|
# HTTP/2 建连可靠(TCP + MSS 钳制保护),但尾部延迟差(P90 14 秒,
|
|
# TCP 队头阻塞:一条连接上丢一个包,所有流一起卡)
|
|
#
|
|
# 同一个隧道 ID 可以起**多个副本**,Cloudflare 会把请求分摊到
|
|
# 所有副本注册上来的连接上。所以:
|
|
#
|
|
# 副本 A: --protocol quic --ha-connections 5
|
|
# 副本 B: --protocol http2 --ha-connections 5
|
|
#
|
|
# 一共 10 条连接、横跨两种协议。QUIC 犯病时 HTTP/2 那半边还在服务,
|
|
# 反过来也一样 —— 这是真正的**故障隔离**,而不只是加连接数。
|
|
#
|
|
# == 资源 ==
|
|
# 每个 cloudflared 约占 25 MB。设备 460 MB 总内存、平时可用 300+ MB,
|
|
# 两个实例没问题(原来 MemoryMax=160M 是给单实例的,两个各自限 128M)。
|
|
set -u
|
|
|
|
TUNNEL_ID=7fd33d9c-f910-42e3-88aa-752d6b2a9f2f
|
|
CONFIG=/root/.cloudflared/config.yml
|
|
MAIN=/etc/systemd/system/lanremote-tunnel.service
|
|
SECOND=/etc/systemd/system/lanremote-tunnel-h2.service
|
|
|
|
echo "════════ 1. 备份现有主服务 ════════"
|
|
cp -a "$MAIN" "/root/lanremote-tunnel.service.bak.dual.$(date +%s)"
|
|
echo " 当前参数: $(grep -oE '\-\-protocol [a-z0-9]+|--ha-connections [0-9]+' "$MAIN" | tr '\n' ' ')"
|
|
|
|
echo
|
|
echo "════════ 2. 主服务改成 QUIC + 5 ════════"
|
|
sed -i 's/--protocol [a-z0-9]*/--protocol quic/' "$MAIN"
|
|
sed -i 's/--ha-connections [0-9]*/--ha-connections 5/' "$MAIN"
|
|
sed -i 's/^MemoryMax=.*/MemoryMax=128M/' "$MAIN"
|
|
grep -E 'ha-connections|protocol|MemoryMax' "$MAIN" | sed 's/^/ /'
|
|
|
|
echo
|
|
echo "════════ 3. 创建 HTTP/2 副本服务 ════════"
|
|
cat > "$SECOND" <<'UNITEOF'
|
|
[Unit]
|
|
Description=ykxmz Cloudflare 具名隧道(HTTP/2 副本)
|
|
# 和主实例跑同一个隧道 ID —— Cloudflare 会把请求分摊到
|
|
# 两个副本注册的全部连接上,形成协议级的故障隔离。
|
|
After=network-online.target lanremote-relay.service lanremote-nettune.service
|
|
Wants=network-online.target lanremote-relay.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/local/bin/cloudflared tunnel \
|
|
--protocol http2 \
|
|
--edge-ip-version 4 \
|
|
--ha-connections 5 \
|
|
--no-autoupdate \
|
|
--retries 5 \
|
|
--config /root/.cloudflared/config.yml run lanremote
|
|
Restart=always
|
|
RestartSec=5
|
|
MemoryMax=128M
|
|
StandardOutput=append:/root/lanremote-relay/tunnel-h2.log
|
|
StandardError=append:/root/lanremote-relay/tunnel-h2.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNITEOF
|
|
echo " 已写入 $SECOND"
|
|
cat "$SECOND" | grep -E 'protocol|ha-connections|MemoryMax' | sed 's/^/ /'
|
|
|
|
echo
|
|
echo "════════ 4. 启动,等连接建齐 ════════"
|
|
systemctl daemon-reload
|
|
systemctl enable lanremote-tunnel-h2 > /dev/null 2>&1
|
|
systemctl restart lanremote-tunnel
|
|
systemctl restart lanremote-tunnel-h2
|
|
|
|
for i in $(seq 1 40); do
|
|
sleep 4
|
|
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \
|
|
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
|
|
# 两个实例各自暴露 metrics 端口会冲突,靠下面分别看进程
|
|
[ "${n:-0}" -ge 10 ] && break
|
|
done
|
|
echo " metrics 报的连接数: ${n:-?}"
|
|
|
|
echo
|
|
echo "════════ 5. 两个实例的进程和内存 ════════"
|
|
for svc in lanremote-tunnel lanremote-tunnel-h2; do
|
|
pid=$(systemctl show $svc -p MainPID --value)
|
|
mem=$(systemctl show $svc -p MemoryCurrent --value)
|
|
proto=$(grep -oE '\-\-protocol [a-z0-9]+' /etc/systemd/system/$svc.service | head -1)
|
|
conns=$(grep -oE '\-\-ha-connections [0-9]+' /etc/systemd/system/$svc.service | head -1)
|
|
printf " %-26s PID=%-7s 内存=%s MB %s %s 状态=%s\n" \
|
|
"$svc" "$pid" "$(( ${mem:-0} / 1048576 ))" "$proto" "$conns" \
|
|
"$(systemctl is-active $svc)"
|
|
done
|
|
|
|
echo
|
|
echo " ⚠ 两个实例都想占 20241 metrics 端口,后起的会失败 —— 这是正常的。"
|
|
echo " 实际连接数看 cloudflared 日志里的 Registered 行。"
|
|
echo " ---"
|
|
echo " 主实例(QUIC)最近注册:"
|
|
tail -60 /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection' | sed 's/^/ /'
|
|
echo " 副本实例(HTTP/2)最近注册:"
|
|
tail -60 /root/lanremote-relay/tunnel-h2.log 2>/dev/null | grep -c 'Registered tunnel connection' | sed 's/^/ /'
|
|
|
|
echo
|
|
echo "════════ 6. 等公网可用 ════════"
|
|
for i in $(seq 1 25); do
|
|
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 12 -H 'User-Agent: LanRemote/1.8.2' https://relay.xxmxxs.top/v1/health)
|
|
printf " 第 %d 次: %s\n" "$i" "$c"
|
|
[ "$c" = "200" ] && break
|
|
sleep 3
|
|
done
|
|
|
|
echo
|
|
echo "════════ 7. 快速验收(40 次)════════"
|
|
ok=0; times=()
|
|
for i in $(seq 1 40); do
|
|
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 -H 'User-Agent: LanRemote/1.8.2' https://relay.xxmxxs.top/v1/health)
|
|
c=$(echo "$out" | awk '{print $1}'); ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
|
[ "$c" = "200" ] && { ok=$((ok+1)); times+=("$ms"); }
|
|
done
|
|
stats=$(printf '%s\n' "${times[@]:-0}" | 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]}')
|
|
echo " 可用率 $ok/40 = $((ok*100/40))% $stats ms"
|