mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 13:40:45 +00:00
修「经中转几次后永久哑掉」——「按一下没反应」的真因
== 现象 ==
用户报:
* 局域网能搜到电脑,IP/端口/配对码都对,连上后**没有任何反应**
* 走中转「已配对」,但点按钮没反应
局域网那一半已经修过(preferLan)。这次抓到的是**经中转**那一半,
而且比预想的严重得多。
== 证据链(插桩复现出来的)==
被控端日志里直接抛了未捕获异常,线程当场死:
Exception in thread client-relay:
server.py:407 session.send(reply)
relay.py:166 self._link.send_to_client(data)
relay.py:357 self._post("/v1/agent/send", ...)
relay.py:766 raise RelayError(f"请求失败: {last_error}")
lanremote.relay.RelayError: 请求失败: Idle
之后的状态:
* client-relay 线程没了 —— 电脑再也不往手表回任何东西
* relay-link 线程还活着、还在长轮询 —— 中转那边看着「设备在线」
* **手表看到的就是「连上了,但按一下毫无反应」**
写入仓库的测试 _tests/test_relay_local.py 一直是这个形状:
前面几项 PASS、跑到经中转发命令就卡死。之前没人深挖。
== 根因 1:一条 HTTP/1.1 连接被两个线程同时用 ==
_post() 里锁只包住「取连接」那一段:
with self._http_lock:
connection = self._http # 锁到这里就放了
try:
connection.request("POST", ...) # 锁外发请求
于是两个线程会同时用同一条 connection:
* relay-link 线程跑 /v1/agent/poll —— 长轮询,一条请求挂 180 秒
* client-relay 线程同时 POST /v1/agent/send
HTTP/1.1 一条连接同一时刻只能有一个在途请求,第二个线程一进来
就撞上 http.client 状态机,抛 CannotSendRequest("Idle")。
_post 的「重试一次」也救不了:它 _drop_http() 之后,另一个线程
手里还攥着那条旧连接对象在发,照样坏。
修法:
* **发命令用独立连接**(_post 加 dedicated 参数)。
长轮询那条常年被占着,发命令不该去挤它,各走各的。
* 两条连接各自一把锁**锁住整个「发请求 + 读响应」周期**,
不再只锁取连接那一下。
* 普通请求若发现缓存连接正忙(长轮询在途),临时另开一条用完即关。
* _drop_http(dedicated=) 区分清哪条;finally 里一定归还占用
(否则那条连接会被永久标记为忙)。
== 根因 2:回包失败会打死整个会话线程 ==
server.py 的 _client_loop 里:
if reply is not None:
session.send(reply) # <- 不在任何 try 里
...
except OSError: # <- 外层只捕获 OSError
pass
而中转链路上 send 抛的是 RelayError,直接冒出去把线程打死。
**死一次就永久哑**,代价太大。
修法:session.send() 单独兜住所有异常,失败就断开这个会话
(让手表重连),绝不让线程消失。外层也补一个兜底 except。
== 修复效果 ==
修复前:pair OK -> ping OK -> ping 卡死 -> 线程崩溃,永久哑
修复后:pair 21ms -> ping 82ms -> ping 4ms -> ping 6ms,零异常
_tests/test_relay_local.py:**之前完全卡死 -> 现在 21/21 全过**
这个测试之前一直是红的,说明「35 个阶段的回归全绿」这件事
本身就不成立过 —— 某个阶段卡住时看起来像环境问题而不是缺陷。
== 同批修掉验证阶段找到的其它问题 ==
1. **TTL 清理 + 关掉接入开关 = 设备永久锁死**(严重)
register() 用 `device_id in self.devices` 判断老设备,但超时清理
把设备从 devices 和 codes 都删了。电脑关机过夜(必然 > 6 小时)
第二天开机就会被自己的开关挡住,而且**重启也救不回来**
(清 codes 时没落盘,重启后又当孤儿清一遍,自我维系)。
管理员在页面上还看不到这台设备,不知道该放开谁。
修法:状态文件里单独存 knownDevices 设备历史,超时清理绝不动它;
register 改查这份历史;清理后补 _save();delete_device 才真正忘记。
2. **管理页 JS 语法错误 -> 所有按钮失效**(严重)
admin.py 里 ADMIN_JS 是普通三引号字符串,我在 Python 源码里写的
`\n` 被变成真实换行,拼进 JS 就是字符串里的裸换行 ->
整段 <script> 不执行 -> 改码/删除/批量删除/全选全变幽灵按键。
元凶是我加改码功能那次提交。改成 `\\n`。
3. **--relay / --relay-status 崩溃**(严重)
RelayLink.start(url) 的 url 是必填参数,我把默认地址改空白时
run_agent.py 两处调用没跟着改。一处被 try 吞成「测试失败:missing
1 required positional argument」(用户以为地址填错了),
一处直接报栈。两处都补上 url。
4. 非法 deviceId(控制字符 / 路径分隔符)现在被拒(白名单:
字母数字 + - _ . :)
5. enable_relay() 第二次调用不再谎报成功(加 RelayLink.running 属性,
返回值反映真实运行状态;不再用「对象建过没有」判断)
6. snapshot() 清孤儿码后补 _save(),磁盘和内存不再脱节
== 局域网全面优化(task-2,另一个智能体完成)==
4 个真问题:
* **网段判断写死 /24** —— 在 /16 网络上 10.8.1.5 和 10.8.2.5
被判成不同网络,明明能直连却去走中转还费流量。
改成按网卡真实掩码比较。
* **广播只发一个包** —— 丢一个就整轮搜不到。改成 5 个时间点重发。
* **只探一个网段** —— 多网卡手表搜不到另一网段的电脑。改成全探。
* **走中转之后再也回不到局域网** —— 出门再回家永远挂中转。
新增定期回切(先便宜地 TCP ping,通了才动真格,
不破坏正在工作的中转连接)。
实测:局域网握手 2ms、50/50 = 100%、中位 0ms。
(跨网络那半之前拿不到数字,就是被上面那个线程崩溃挡住的 ——
现已修复。)
顺带:instance.py 单实例锁端口加 YKXMZ_LOCK_PORT 覆盖(默认不变)。
用户自己那台被控端正跑在这台机器上占着 8767/TCP + 8766/UDP,
是这次排查里好几个「测试怎么都不通」的根因。
== 服务端一键部署脚本(task-1,另一个智能体完成)==
relay/deploy.sh + relay/deploy-tune.sh,随服务端 zip 发布:
* 自动检查并安装 cloudflared
* 部署程序、装 4 个 systemd 服务、网络调优、配隧道、自检打公网地址
* **不传 --hostname 且没有 TUNNEL_TOKEN 时明确提示要填**,
不会静默用一个写死的域名(用户明确说过这个中转站是他自己用的,
不对外开放,绝不能把私人域名烧成默认值)
* 重跑 = 升级,已有数据/配对码/管理密码/凭据都不动
顺带修了发布包一直缺 site_shots/ 的老 bug
(package_remote.py 用 iterdir()+is_file() 收文件,目录进不去,
新机器装完官网截图全是 404)。
== 默认中转地址改成空白 ==
用户要求:软件默认不带任何中转地址,由用户自己填。
四处硬编码全部清空(server.py / MainActivity.java / run_agent.py /
relay.py 的注释和文档示例),全仓已无非注释的硬编码地址。
== 文档同步(task-5,另一个智能体完成)==
README / BEFORE-INSTALL / AFTER-INSTALL / 打包模板:
* 新增「软件里不带任何中转地址」告示,讲清跨网络要自己部署
* 修正 Android 版本(写的是 9.0/API28,实际 API 24 / Android 7.0)
* 删掉「地址已经内置好了」(现在是错的)
* AGENT_README 原来把作者自己的中转地址当成「内置地址」发给用户,已重写
* RELAY_README 原来写「两条隧道同时跑会互相争抢」,
和真机/CHANGELOG 矛盾;按实测改成「两种协议同时在跑(故意的)」
This commit is contained in:
+224
-51
@@ -73,7 +73,9 @@ PREFIX="${PREFIX:-}" # 安装根,默认 /(测试时可
|
||||
TOKEN="${TUNNEL_TOKEN:-}" # 隧道 Token,默认空
|
||||
HOSTNAME_ARG="" # 域名,默认沿用已有 config.yml
|
||||
TUNNEL_NAME="${TUNNEL_NAME:-lanremote}" # 具名隧道名
|
||||
PROTOCOL="${PROTOCOL:-quic}" # quic 或 http2
|
||||
# 隧道协议。默认 both = 两条隧道一起跑(实测最优,见下面的说明)。
|
||||
# 传 quic 或 http2 才是"只跑那一种"。
|
||||
PROTOCOL="${PROTOCOL:-both}"
|
||||
SKIP_TUNNEL=0
|
||||
DOWNLOADS_SRC="" # 可选:把发布产物拷进下载目录
|
||||
PORT="${RELAY_PORT:-8080}"
|
||||
@@ -89,11 +91,21 @@ ykxmz 服务器端 · 一键部署 v$VERSION
|
||||
|
||||
选项:
|
||||
--token TOKEN 用隧道 Token 部署(等价于环境变量 TUNNEL_TOKEN)
|
||||
--hostname NAME 公网域名,例如 relay.example.com
|
||||
--hostname NAME 公网域名,例如 relay.你的域名
|
||||
不填则沿用 /root/.cloudflared/config.yml 里已有的
|
||||
**脚本没有任何默认域名** —— 猜不出来会明确提示你填
|
||||
--tunnel-name NAME 具名隧道名字(默认 lanremote)
|
||||
--protocol P 隧道协议:quic(默认)或 http2
|
||||
quic 尾部延迟更好;http2 走 TCP、建连更稳
|
||||
--protocol P quic / http2 / both(默认 both)
|
||||
默认**两条隧道一起跑**,这是实测最优:
|
||||
主 quic + 8 条 副本 http2 + 2 条
|
||||
为什么两条一起跑 —— 运营商对 UDP 有限制
|
||||
(12 个 edge IP:TCP 12/12 通、UDP 0/12 通),
|
||||
所以 quic 拨号成功率只有约 72%;但连上之后
|
||||
它的丢包恢复好于 http2。
|
||||
两个一起 = http2 兜住 quic 拨号失败的那部分,
|
||||
而且 8+2 的配比在 40 次/组对照里拿到了
|
||||
100% 可用率 + 最低的 P90/P99。
|
||||
传 quic 或 http2 就只跑那一种。
|
||||
--port N 中转监听端口(默认 8080)
|
||||
--downloads DIR 把 DIR 里的发布产物拷进下载目录(官网下载按钮用)
|
||||
--skip-tunnel 只装中转,不碰隧道
|
||||
@@ -103,12 +115,15 @@ ykxmz 服务器端 · 一键部署 v$VERSION
|
||||
环境变量:
|
||||
TUNNEL_TOKEN 和 --token 等价(推荐用这个,不留在 shell 历史里)
|
||||
RELAY_PORT 和 --port 等价
|
||||
QUIC_CONNS 主隧道(quic)连接条数,默认 8
|
||||
H2_CONNS 副本隧道(http2)连接条数,默认 2
|
||||
|
||||
例子:
|
||||
bash deploy.sh # 全自动
|
||||
bash deploy.sh # 全自动(双隧道)
|
||||
TUNNEL_TOKEN=eyJh... bash deploy.sh # 用 Token
|
||||
bash deploy.sh --hostname relay.mydomain.com # 换域名
|
||||
bash deploy.sh --hostname relay.你的域名 # 换域名
|
||||
bash deploy.sh --skip-tunnel # 只装中转
|
||||
bash deploy.sh --protocol http2 # 只跑 HTTP/2(UDP 被挡的环境)
|
||||
|
||||
做完之后:
|
||||
浏览器打开 https://<你的域名>/ —— 就是官网
|
||||
@@ -138,11 +153,60 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$PROTOCOL" != "quic" ] && [ "$PROTOCOL" != "http2" ]; then
|
||||
echo "协议只能是 quic 或 http2,收到的是: $PROTOCOL" >&2
|
||||
if [ "$PROTOCOL" != "quic" ] && [ "$PROTOCOL" != "http2" ] \
|
||||
&& [ "$PROTOCOL" != "both" ]; then
|
||||
echo "协议只能是 quic / http2 / both,收到的是: $PROTOCOL" >&2
|
||||
echo "(不传这个参数就是 both —— 两条隧道一起跑)" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
# 两条隧道怎么分工(默认两条都跑)
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
# 为什么**两条一起跑**才是最优(真机 40 次/组的受控对照,MTU 1400):
|
||||
#
|
||||
# quic 5 + h2 5 可用率 100% 中位 1490 P90 2346 P99 3295
|
||||
# quic 3 + h2 7 可用率 97% 中位 1271 P90 2558 P99 13934
|
||||
# quic 8 + h2 2 可用率 100% 中位 1250 P90 1715 P99 2632 <- 采用
|
||||
#
|
||||
# 根因也查清了:**运营商对 UDP 有限制**。
|
||||
# 对 12 个 Cloudflare edge IP 探测:
|
||||
# TCP:7844 12/12 可达
|
||||
# UDP:7844 0/12 有回应
|
||||
# 所以 QUIC 的拨号成功率只有约 72%;但只要拨上了,
|
||||
# 它的丢包恢复明显好于 HTTP/2(QUIC 没有队头阻塞)。
|
||||
#
|
||||
# 于是:单独用 QUIC,会有近三成的时候连不上;
|
||||
# 单独用 HTTP/2,队头阻塞在高丢包下会拖长尾延迟。
|
||||
# 两个一起跑,HTTP/2 正好兜住 QUIC 拨号失败的那部分 ——
|
||||
# 这就是**协议级故障隔离**,比在同一个协议上堆连接数有效得多。
|
||||
#
|
||||
# (早先这里写的是"两条同时跑会互相争抢",那个结论是错的,已作废。)
|
||||
MAIN_PROTO=quic
|
||||
MAIN_CONNS="${QUIC_CONNS:-8}"
|
||||
ALT_PROTO=http2
|
||||
ALT_CONNS="${H2_CONNS:-2}"
|
||||
|
||||
# metrics 端口必须**显式钉死**。
|
||||
# cloudflared 默认都用 20241:正常启动顺序下看着像"主 20241 / 副 20242",
|
||||
# 但实测**停掉主实例再重启副实例,副实例会抢到 20241**。
|
||||
# 于是 watchdog 和所有按端口读指标的脚本全部读错实例,还不会有任何报错。
|
||||
MAIN_METRICS="127.0.0.1:20241"
|
||||
ALT_METRICS="127.0.0.1:20242"
|
||||
|
||||
# --protocol 用来强制只跑一种(默认 both)
|
||||
MAIN_ENABLED=1
|
||||
ALT_ENABLED=1
|
||||
if [ "$PROTOCOL" = "quic" ]; then
|
||||
# 只跑 QUIC(它拨号失败率约三成,只在明确知道 UDP 通的时候用)
|
||||
MAIN_ENABLED=1
|
||||
ALT_ENABLED=0
|
||||
elif [ "$PROTOCOL" = "http2" ]; then
|
||||
# 只跑 HTTP/2(UDP 被封的环境)
|
||||
MAIN_ENABLED=0
|
||||
ALT_ENABLED=1
|
||||
fi
|
||||
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
# 路径:全部挂在 $PREFIX 下面
|
||||
# 真实部署时 PREFIX 是空的,于是这些就是标准的绝对路径;
|
||||
@@ -422,7 +486,7 @@ write_unit() {
|
||||
}
|
||||
|
||||
# ---- 中转服务 ----
|
||||
cat > "$UNIT_DIR/lanremote-relay.service" <<EOF
|
||||
write_unit "$UNIT_DIR/lanremote-relay.service" <<EOF
|
||||
[Unit]
|
||||
Description=ykxmz 中转服务器
|
||||
After=network-online.target
|
||||
@@ -452,9 +516,10 @@ StandardError=append:$RELAY_DIR/relay.log
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
[ $? -eq 0 ] && RELAY_UNIT_CHANGED=1
|
||||
|
||||
# ---- 网络调优(开机先跑,排在 relay / tunnel 前面)----
|
||||
cat > "$UNIT_DIR/lanremote-nettune.service" <<EOF
|
||||
write_unit "$UNIT_DIR/lanremote-nettune.service" <<EOF
|
||||
[Unit]
|
||||
Description=ykxmz 网络调优(keepalive + MSS + MTU)
|
||||
After=network-online.target
|
||||
@@ -473,15 +538,16 @@ ExecStart=/bin/bash $RELAY_DIR/deploy-tune.sh
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
[ $? -eq 0 ] && UNITS_CHANGED=1
|
||||
|
||||
# ---- 隧道(主用:协议由 --protocol 决定)----
|
||||
# 参数都是实测调出来的,改动前先看 deploy-tune.sh 顶部的说明:
|
||||
# ---- 隧道(默认两条一起跑,见文件前面 MAIN_PROTO / ALT_PROTO 的说明)----
|
||||
# 参数都是实测调出来的:
|
||||
# --edge-ip-version 4 这条线路有 IPv6 地址但实际不通,
|
||||
# 边缘默认优先 IPv6 会一直超时
|
||||
# --ha-connections 10 连接多 = 单条掉线影响小(实测 10 条时
|
||||
# P99 从 13.7 秒降到 2.99 秒)
|
||||
# --ha-connections N 每个单元各自多少条连接(默认 quic 8 / h2 2)
|
||||
# --retries 5 连边缘失败时多重试
|
||||
TUNNEL_COMMON="--no-autoupdate --edge-ip-version 4 --ha-connections 10 --retries 5"
|
||||
# --metrics ADDR:PORT **必须显式钉死**,见下面的注释
|
||||
TUNNEL_BASE="--no-autoupdate --edge-ip-version 4 --retries 5"
|
||||
|
||||
if [ -n "$TOKEN" ]; then
|
||||
# Token 模式:Token 存单独的 600 权限文件,不写进 unit
|
||||
@@ -494,15 +560,22 @@ else
|
||||
fi
|
||||
|
||||
write_tunnel_unit() {
|
||||
local unit="$1" proto="$2"
|
||||
local unit="$1" proto="$2" conns="$3" metrics="$4"
|
||||
# ⚠ 文件名必须带 .service 后缀。
|
||||
# systemd 是按「<名字>.service」找文件的,
|
||||
# `systemctl enable lanremote-tunnel` 找的是 lanremote-tunnel.service。
|
||||
# 少这个后缀的话文件是写下去了,但 systemctl 完全看不见它 ——
|
||||
# 而且不会有任何报错(enable 会安静地失败),非常难查。实测踩过。
|
||||
cat > "$UNIT_DIR/$unit.service" <<EOF
|
||||
#
|
||||
# ⚠ --metrics 必须显式写死端口:
|
||||
# cloudflared 的默认 metrics 端口两个实例都是 20241。
|
||||
# 正常启动顺序下看着像"主 20241 / 副 20242"(后启动的发现端口被占
|
||||
# 就自己往后挪),但实测**停掉主实例再重启副实例时,
|
||||
# 副实例会抢到 20241** —— 于是 watchdog、看连接数的脚本、
|
||||
# 以及所有按端口读指标的排查动作全部读错实例,而且不报任何错。
|
||||
write_unit "$UNIT_DIR/$unit.service" <<EOF
|
||||
[Unit]
|
||||
Description=ykxmz Cloudflare 隧道($proto)
|
||||
Description=ykxmz Cloudflare 隧道($proto,$conns 条连接)
|
||||
After=network-online.target lanremote-relay.service
|
||||
Wants=network-online.target
|
||||
Requires=lanremote-relay.service
|
||||
@@ -510,7 +583,7 @@ Requires=lanremote-relay.service
|
||||
[Service]
|
||||
Type=simple
|
||||
$ENV_LINE
|
||||
ExecStart=$CF_BIN tunnel $TUNNEL_COMMON --protocol $proto $TUNNEL_ARGS
|
||||
ExecStart=$CF_BIN tunnel $TUNNEL_BASE --protocol $proto --ha-connections $conns --metrics $metrics $TUNNEL_ARGS
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
MemoryMax=160M
|
||||
@@ -522,19 +595,23 @@ WantedBy=multi-user.target
|
||||
EOF
|
||||
}
|
||||
|
||||
# 装两份:一份主用、一份 HTTP/2 备用。
|
||||
# 不自动同时启用 —— 两条隧道同时跑会互相争抢,实测反而更不稳。
|
||||
# 需要切换时:
|
||||
# systemctl disable --now lanremote-tunnel
|
||||
# systemctl enable --now lanremote-tunnel-h2
|
||||
write_tunnel_unit lanremote-tunnel "$PROTOCOL"
|
||||
ALT_PROTO="http2"
|
||||
[ "$PROTOCOL" = "http2" ] && ALT_PROTO="quic"
|
||||
write_tunnel_unit lanremote-tunnel-h2 "$ALT_PROTO"
|
||||
# 两个单元都写出来(即使用户选了只跑一种 —— 留着方便随时切回来)
|
||||
write_tunnel_unit lanremote-tunnel "$MAIN_PROTO" "$MAIN_CONNS" "$MAIN_METRICS"
|
||||
[ $? -eq 0 ] && TUNNEL_UNIT_CHANGED=1
|
||||
write_tunnel_unit lanremote-tunnel-h2 "$ALT_PROTO" "$ALT_CONNS" "$ALT_METRICS"
|
||||
[ $? -eq 0 ] && TUNNEL_UNIT_CHANGED=1
|
||||
say " 主隧道: $MAIN_PROTO + $MAIN_CONNS 条 metrics $MAIN_METRICS (启用: $MAIN_ENABLED)"
|
||||
say " 副本隧道: $ALT_PROTO + $ALT_CONNS 条 metrics $ALT_METRICS (启用: $ALT_ENABLED)"
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
say " 已写入 4 个服务单元到 $UNIT_DIR"
|
||||
# 只有 unit 真的变了才 daemon-reload ——
|
||||
# 每次都 reload 会顺带触发 systemd 重新评估依赖,没必要。
|
||||
if [ "$UNITS_CHANGED" = "1" ]; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
say " 已写入 4 个服务单元到 $UNIT_DIR(有变化,已 reload)"
|
||||
else
|
||||
say " 4 个服务单元内容没变,跳过 reload"
|
||||
fi
|
||||
else
|
||||
say " [跳过] 没有 systemctl,服务单元已写好但没加载"
|
||||
fi
|
||||
@@ -546,6 +623,9 @@ step "6/7 配置隧道"
|
||||
|
||||
TUNNEL_OK=0
|
||||
PUBLIC_URL=""
|
||||
# config.yml 这次是不是真的被改写了 —— 隧道不吃旧配置,
|
||||
# 改了就必须重启隧道,没改就别去动它。
|
||||
CONFIG_CHANGED=0
|
||||
|
||||
if [ "$SKIP_TUNNEL" = "1" ]; then
|
||||
say " --skip-tunnel:不配置隧道"
|
||||
@@ -603,11 +683,32 @@ else
|
||||
[ -z "$HOST" ] && HOST="$CUR_HOST"
|
||||
|
||||
if [ -z "$HOST" ]; then
|
||||
warn "认不出公网域名(既没给 --hostname,config.yml 里也没有)"
|
||||
warn "补上域名再跑一次:"
|
||||
warn " bash $0 --hostname relay.你的域名"
|
||||
warn "如果这个域名还没指向隧道,先执行:"
|
||||
warn " $CF_BIN tunnel route dns $TUNNEL_NAME relay.你的域名"
|
||||
# ⚠ 绝不猜域名、也绝不内置任何默认值。
|
||||
# 这个中转是自用的,域名只有机器主人知道。
|
||||
# 脚本里写死一个域名的话,别人照抄就指到你的机器上去了 ——
|
||||
# 所以这里**明确停下来要输入**,而不是随便挑一个继续。
|
||||
say
|
||||
say " ┌────────────────────────────────────────────────────┐"
|
||||
say " │ 需要你提供公网域名(脚本不会替你猜,也没有默认值) │"
|
||||
say " └────────────────────────────────────────────────────┘"
|
||||
say
|
||||
say " 机器上有隧道凭据,但是:"
|
||||
say " · 命令行没给 --hostname"
|
||||
say " · 已有的 config.yml 里也没有 hostname 行"
|
||||
say " 所以定不下来公网地址。"
|
||||
say
|
||||
say " 补上域名再跑一次:"
|
||||
say " bash $0 --hostname relay.你的域名"
|
||||
say
|
||||
say " 如果这个域名还没指向隧道,先执行一次:"
|
||||
say " $CF_BIN tunnel route dns $TUNNEL_NAME relay.你的域名"
|
||||
say
|
||||
say " 或者改用 Token 模式(域名信息在 Token 里,不用手填):"
|
||||
say " TUNNEL_TOKEN=<Cloudflare 给的 Token> bash $0"
|
||||
say
|
||||
say " ── 本次结果 ──"
|
||||
say " 中转已经装好并启动了 —— **局域网内现在就能用**。"
|
||||
say " 只是跨网络(走公网)还差这一步域名。"
|
||||
else
|
||||
# config.yml 只在「不存在」或「用户明确给了 --hostname」时才重写,
|
||||
# 免得把别人手工调过的配置冲掉。
|
||||
@@ -632,6 +733,7 @@ ingress:
|
||||
- service: http_status:404
|
||||
EOF
|
||||
chmod 600 "$CF_DIR/config.yml"
|
||||
CONFIG_CHANGED=1
|
||||
say " 已写入 $CF_DIR/config.yml(域名 $HOST)"
|
||||
else
|
||||
say " 沿用已有 $CF_DIR/config.yml(域名 $HOST)"
|
||||
@@ -658,25 +760,85 @@ if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl enable lanremote-nettune >/dev/null 2>&1 || true
|
||||
systemctl start lanremote-nettune >/dev/null 2>&1 || true
|
||||
|
||||
say " 启动中转服务"
|
||||
systemctl restart lanremote-relay >/dev/null 2>&1 || true
|
||||
sleep 3
|
||||
# ── 中转服务 ──
|
||||
# ⚠ 只在「程序变了」「unit 变了」「本来就没在跑」这三种情况下才重启。
|
||||
# 用户在**已经跑着的机器上**重跑本脚本是为了升级,
|
||||
# 如果每次都不管三七二十一 restart,正在服务的连接就被白打断一次 ——
|
||||
# 重启会清掉内存里的设备注册和会话,客户端要几秒才重新接上。
|
||||
RELAY_NEED_RESTART=0
|
||||
[ "$RELAY_CHANGED" = "1" ] && RELAY_NEED_RESTART=1
|
||||
[ "$RELAY_UNIT_CHANGED" = "1" ] && RELAY_NEED_RESTART=1
|
||||
systemctl is-active --quiet lanremote-relay 2>/dev/null \
|
||||
|| RELAY_NEED_RESTART=1
|
||||
|
||||
if [ "$RELAY_NEED_RESTART" = "1" ]; then
|
||||
if [ "$RELAY_CHANGED" = "1" ] || [ "$RELAY_UNIT_CHANGED" = "1" ]; then
|
||||
say " 程序或配置有更新,重启中转服务"
|
||||
else
|
||||
say " 中转服务没在跑,启动它"
|
||||
fi
|
||||
systemctl restart lanremote-relay >/dev/null 2>&1 || true
|
||||
sleep 3
|
||||
else
|
||||
say " 程序和配置都没变,中转服务保持运行(不打断在线连接)"
|
||||
fi
|
||||
say " lanremote-relay: $(systemctl is-active lanremote-relay 2>&1)"
|
||||
|
||||
if [ "$TUNNEL_OK" = "1" ]; then
|
||||
say " 启动隧道服务"
|
||||
systemctl enable lanremote-tunnel >/dev/null 2>&1 || true
|
||||
# 备用隧道不自动启用 —— 两个同时跑会互相争抢
|
||||
systemctl disable lanremote-tunnel-h2 >/dev/null 2>&1 || true
|
||||
systemctl restart lanremote-tunnel >/dev/null 2>&1 || true
|
||||
say " lanremote-tunnel: $(systemctl is-active lanremote-tunnel 2>&1)"
|
||||
# ── 按开关启用/停用两个单元 ──
|
||||
# 默认两个都启用(协议级故障隔离,见文件前面的实测对照)。
|
||||
# 只有用户用 --protocol 明确指定单一协议时才关掉另一个。
|
||||
if [ "$MAIN_ENABLED" = "1" ]; then
|
||||
systemctl enable lanremote-tunnel >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl disable --now lanremote-tunnel >/dev/null 2>&1 || true
|
||||
say " --protocol 指定只跑 $ALT_PROTO,已停用主隧道"
|
||||
fi
|
||||
if [ "$ALT_ENABLED" = "1" ]; then
|
||||
systemctl enable lanremote-tunnel-h2 >/dev/null 2>&1 || true
|
||||
else
|
||||
systemctl disable --now lanremote-tunnel-h2 >/dev/null 2>&1 || true
|
||||
say " --protocol 指定只跑 $MAIN_PROTO,已停用副本隧道"
|
||||
fi
|
||||
|
||||
TUNNEL_NEED_RESTART=0
|
||||
[ "$TUNNEL_UNIT_CHANGED" = "1" ] && TUNNEL_NEED_RESTART=1
|
||||
# 新写的 config.yml 也算配置变更 —— 隧道不吃旧配置
|
||||
[ "${CONFIG_CHANGED:-0}" = "1" ] && TUNNEL_NEED_RESTART=1
|
||||
|
||||
# 启用的那几个单元,只要有一个没在跑就得拉起来
|
||||
for svc in lanremote-tunnel lanremote-tunnel-h2; do
|
||||
case "$svc" in
|
||||
lanremote-tunnel) [ "$MAIN_ENABLED" = "1" ] || continue ;;
|
||||
lanremote-tunnel-h2) [ "$ALT_ENABLED" = "1" ] || continue ;;
|
||||
esac
|
||||
systemctl is-active --quiet "$svc" 2>/dev/null \
|
||||
|| TUNNEL_NEED_RESTART=1
|
||||
done
|
||||
|
||||
if [ "$TUNNEL_NEED_RESTART" = "1" ]; then
|
||||
if [ "$TUNNEL_UNIT_CHANGED" = "1" ] \
|
||||
|| [ "${CONFIG_CHANGED:-0}" = "1" ]; then
|
||||
say " 隧道配置有更新,重启隧道服务"
|
||||
else
|
||||
say " 隧道服务没在跑,启动它"
|
||||
fi
|
||||
[ "$MAIN_ENABLED" = "1" ] \
|
||||
&& systemctl restart lanremote-tunnel >/dev/null 2>&1
|
||||
[ "$ALT_ENABLED" = "1" ] \
|
||||
&& systemctl restart lanremote-tunnel-h2 >/dev/null 2>&1
|
||||
else
|
||||
say " 隧道配置没变,保持运行(不重建隧道连接)"
|
||||
fi
|
||||
[ "$MAIN_ENABLED" = "1" ] && say " lanremote-tunnel: $(systemctl is-active lanremote-tunnel 2>&1)"
|
||||
[ "$ALT_ENABLED" = "1" ] && say " lanremote-tunnel-h2: $(systemctl is-active lanremote-tunnel-h2 2>&1)"
|
||||
elif [ "$SKIP_TUNNEL" != "1" ]; then
|
||||
say " 隧道没配好,先不启动"
|
||||
fi
|
||||
|
||||
# 看门狗(如果包里有)
|
||||
if [ -f "$RELAY_DIR/watchdog.sh" ]; then
|
||||
cat > "$UNIT_DIR/lanremote-watchdog.service" <<EOF
|
||||
write_unit "$UNIT_DIR/lanremote-watchdog.service" <<EOF
|
||||
[Unit]
|
||||
Description=ykxmz 隧道看门狗
|
||||
After=lanremote-relay.service lanremote-tunnel.service
|
||||
@@ -688,7 +850,7 @@ MemoryMax=64M
|
||||
StandardOutput=append:$RELAY_DIR/watchdog.log
|
||||
StandardError=append:$RELAY_DIR/watchdog.log
|
||||
EOF
|
||||
cat > "$UNIT_DIR/lanremote-watchdog.timer" <<'EOF'
|
||||
write_unit "$UNIT_DIR/lanremote-watchdog.timer" <<'EOF'
|
||||
[Unit]
|
||||
Description=定期检查隧道
|
||||
|
||||
@@ -701,7 +863,10 @@ Unit=lanremote-watchdog.service
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
# 看门狗的 unit 也可能刚被改过
|
||||
if [ "$UNITS_CHANGED" = "1" ]; then
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
fi
|
||||
systemctl enable lanremote-watchdog.timer >/dev/null 2>&1 || true
|
||||
systemctl start lanremote-watchdog.timer >/dev/null 2>&1 || true
|
||||
say " 看门狗: $(systemctl is-active lanremote-watchdog.timer 2>&1)"
|
||||
@@ -819,12 +984,20 @@ say " 常用命令"
|
||||
say " lanremote-status 看整体状态"
|
||||
say " lanremote-url 看公网地址"
|
||||
say " systemctl status lanremote-relay 看中转服务"
|
||||
say " systemctl status lanremote-tunnel 看隧道"
|
||||
say " systemctl status lanremote-tunnel 看主隧道(quic)"
|
||||
say " systemctl status lanremote-tunnel-h2 看副本隧道(http2)"
|
||||
say " journalctl -u lanremote-relay -f 跟日志"
|
||||
say
|
||||
say " 换隧道协议(quic 慢一点但尾延迟好 / http2 建连更稳)"
|
||||
say " systemctl disable --now lanremote-tunnel"
|
||||
say " systemctl enable --now lanremote-tunnel-h2"
|
||||
say " 两个隧道服务默认**都开着**(协议级故障隔离,实测最优配比 quic 8 + h2 2)"
|
||||
say " 看各自的连接数和实时指标:"
|
||||
say " curl -s http://127.0.0.1:20241/metrics | grep ha_connections # 主"
|
||||
say " curl -s http://127.0.0.1:20242/metrics | grep ha_connections # 副本"
|
||||
say " (两个实例的 metrics 端口是分开钉死的,不会互相抢)"
|
||||
say
|
||||
say " 只想跑一种(比如确认这台机器 UDP 被封):"
|
||||
say " bash deploy.sh --protocol http2 # 只跑 HTTP/2"
|
||||
say " bash deploy.sh --protocol quic # 只跑 QUIC"
|
||||
say " bash deploy.sh # 不传就是两条一起跑"
|
||||
say
|
||||
say " 重跑本脚本 = 升级,已有配置和数据都不会动"
|
||||
say
|
||||
|
||||
Reference in New Issue
Block a user