Files
ykxmz/relay/install.sh
T
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

427 lines
14 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 局域网遥控 · 中转服务器 —— 安装脚本
#
# 在一台能上外网的 Linux 机器(这里是随身 WiFi / Debian 12)上跑:
# bash install.sh
#
# 做完这些:
# 1. 把 relay.py 装到 /root/lanremote-relay
# 2. 注册两个 systemd 服务,开机自启:
# lanremote-relay 中转服务
# lanremote-tunnel Cloudflare 隧道(公网入口)
# 3. 打印公网地址和管理页地址
#
# 面向 512MB 内存的小机器:两个进程加起来约 60MB。
set -eu
RELAY_DIR=/root/lanremote-relay
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
echo "============================================================"
echo " 局域网遥控 · 中转服务器 安装"
echo "============================================================"
echo
# ---------- 0. 环境检查 ----------
echo "[0/5] 检查环境"
if [ "$(id -u)" != "0" ]; then
echo " 需要 root 权限运行"
exit 1
fi
command -v python3 >/dev/null || { echo " 没有 python3"; exit 1; }
echo " python3 $(python3 -V 2>&1 | awk '{print $2}')"
if ! command -v cloudflared >/dev/null; then
echo " 没有 cloudflared,尝试安装…"
ARCH=$(uname -m)
case "$ARCH" in
aarch64) CF=cloudflared-linux-arm64 ;;
armv7l) CF=cloudflared-linux-arm ;;
x86_64) CF=cloudflared-linux-amd64 ;;
*) echo " 不认识的架构 $ARCH"; exit 1 ;;
esac
curl -fsSL --http1.1 -o /usr/local/bin/cloudflared \
"https://github.com/cloudflare/cloudflared/releases/latest/download/$CF"
chmod +x /usr/local/bin/cloudflared
fi
echo " cloudflared $(cloudflared --version 2>&1 | head -1)"
echo " 内存: $(free -m | awk 'NR==2{print $2" MB 总量 / "$7" MB 可用"}')"
# ---------- 0b. 检查 Cloudflare API 有没有被 DNS 劫持 ----------
# 踩过的坑:这条 4G 线路把 api.cloudflare.com 解析到运营商设备
# (141.193.154.210,自签名证书)。cloudflared 下载登录证书必须走这个域名,
# 于是永远报 Failed to fetch resource,具名隧道根本建不起来。
echo
echo "[0b] 检查 Cloudflare API"
CF_HOSTS=/etc/hosts
CF_MARK="# LanRemote: 钉住 Cloudflare API(网络会 DNS 劫持)"
resolve_cf() {
getent ahostsv4 api.cloudflare.com 2>/dev/null | awk 'NR==1{print $1}'
}
is_real_cf() {
case "$1" in
104.16.*|104.17.*|104.18.*|104.19.*|104.20.*|104.21.*|172.64.*|172.66.*|172.67.*|162.159.*|188.114.*)
return 0 ;;
*) return 1 ;;
esac
}
CF_IP=$(resolve_cf)
echo " api.cloudflare.com 现在解析到: ${CF_IP:-(解析不出来)}"
CF_CODE=$(timeout 12 curl -4 -s -o /dev/null -w '%{http_code}' \
https://api.cloudflare.com/client/v4/user 2>/dev/null)
CF_CODE=${CF_CODE:-000}
echo " 实测 HTTP: ${CF_CODE}"
CF_BODY=$(timeout 12 curl -4 -s https://api.cloudflare.com/client/v4/user 2>/dev/null | head -c 200)
if is_real_cf "$CF_IP" && echo "$CF_BODY" | grep -q '"success"'; then
echo " ✓ 正常(返回的是真正的 API JSON)"
else
echo " ⚠ 检测到 Cloudflare API 被 DNS 劫持或不可达"
echo " 具名隧道需要访问它来下载证书,先修掉。"
cp -n "$CF_HOSTS" "${CF_HOSTS}.lanremote.bak" 2>/dev/null || true
# 清掉所有和本程序相关的 hosts 记录(含早期版本留下的不同文案)
sed -i '/LanRemote.*Cloudflare API/d' "$CF_HOSTS" 2>/dev/null || true
sed -i '/api\.cloudflare\.com/d' "$CF_HOSTS" 2>/dev/null || true
# 顺手删掉清理后可能剩下的空行
sed -i '/^[[:space:]]*$/d' "$CF_HOSTS" 2>/dev/null || true
echo "" >> "$CF_HOSTS"
{
echo ""
echo "$CF_MARK"
# 这几个是用公共 DNS(1.1.1.1 / 8.8.8.8 / 223.5.5.5)查出来的
# 真实地址。之前随手挑的 104.16.132.229 是错的 ——
# 它返回 Cloudflare 错误页(error code: 1034)而不是 API,
# 会导致 cloudflared tunnel create 报 unauthorized。
echo "104.19.192.174 api.cloudflare.com"
echo "104.19.192.175 api.cloudflare.com"
echo "104.19.192.176 api.cloudflare.com"
echo "104.19.192.177 api.cloudflare.com"
echo "104.17.0.1 api.cloudflare.com"
echo "104.18.0.1 api.cloudflare.com"
} >> "$CF_HOSTS"
sleep 1
CF_IP2=$(resolve_cf)
CF_CODE2=$(timeout 12 curl -4 -s -o /dev/null -w '%{http_code}' \
https://api.cloudflare.com/client/v4/user 2>/dev/null)
CF_CODE2=${CF_CODE2:-000}
echo " 修完解析到: ${CF_IP2:-?} HTTP: ${CF_CODE2}"
CF_BODY2=$(timeout 12 curl -4 -s https://api.cloudflare.com/client/v4/user 2>/dev/null | head -c 200)
if echo "$CF_BODY2" | grep -q '"success"'; then
echo " ✓ 修好了(返回的是真正的 API JSON)"
else
echo " ✗ 还是不通 —— 具名隧道可能建不起来,"
echo " 但匿名隧道仍然可用(它走的是另一条链路)"
fi
fi
# ---------- 1. 放文件 ----------
echo
echo "[1/5] 安装文件到 $RELAY_DIR"
mkdir -p "$RELAY_DIR/data"
# 脚本可能就在 RELAY_DIR 里(自己拷自己 cp 会报错),所以先比一下真实路径
SRC="$SCRIPT_DIR/relay.py"
DST="$RELAY_DIR/relay.py"
SAME=no
if [ -f "$SRC" ] && [ -f "$DST" ]; then
if [ "$(readlink -f "$SRC")" = "$(readlink -f "$DST")" ]; then
SAME=yes
fi
fi
if [ "$SAME" = "yes" ]; then
echo " relay.py 已在位(源和目标同一个文件)"
elif [ -f "$SRC" ]; then
cp -f "$SRC" "$DST"
echo " 已复制 relay.py"
elif [ -f "$DST" ]; then
echo " relay.py 已在位"
else
echo " 找不到 relay.py"
exit 1
fi
python3 -m py_compile "$RELAY_DIR/relay.py"
echo " 语法检查通过"
# ---------- 2. 停掉旧的 ----------
echo
echo "[2/5] 停掉旧的实例"
systemctl stop lanremote-tunnel lanremote-relay 2>/dev/null || true
pkill -f 'relay.py' 2>/dev/null || true
pkill -f 'cloudflared tunnel' 2>/dev/null || true
sleep 2
# ---------- 3. systemd ----------
echo
echo "[3/5] 注册开机自启服务"
cat > /etc/systemd/system/lanremote-relay.service <<EOF
[Unit]
Description=LanRemote 中转服务器
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=$RELAY_DIR
Environment=RELAY_PORT=8080
Environment=RELAY_BIND=127.0.0.1
Environment=RELAY_DATA=$RELAY_DIR/data
Environment=RELAY_POLL=25
Environment=RELAY_QUEUE_BYTES=2097152
ExecStart=/usr/bin/python3 $RELAY_DIR/relay.py
Restart=always
RestartSec=3
# 小内存机器:限制住,别把系统拖死
MemoryMax=128M
CPUQuota=60%
StandardOutput=append:$RELAY_DIR/relay.log
StandardError=append:$RELAY_DIR/relay.log
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/lanremote-tunnel.service <<EOF
[Unit]
Description=LanRemote Cloudflare 隧道
After=network-online.target lanremote-relay.service
Wants=network-online.target
Requires=lanremote-relay.service
[Service]
Type=simple
# --- 下面这几个参数都是实测调出来的,不是拍脑袋 ---
#
# IPv4:这条线路**有 IPv6 地址但实际不通**,
# Cloudflare 边缘默认优先 IPv6,于是建隧道一直超时
# (日志里是 context deadline exceeded,对端 2606:4700:a8::10)。
# 所以必须 --edge-ip-version 4。
#
# 协议:用 HTTP/2(走 TCP 7844)。
# 实测对比(同一设备、各跑 90 秒):
# QUIC(UDP) 可用 48%,断开 16 次,拨号失败 16 次
# HTTP/2 可用 80%,断开 2 次,拨号失败 0 次
# 这条线路在丢 UDP 包 —— 日志里全是
# 'Failed to dial a quic connection: no recent network activity'
# 'Connection terminated: Failed to accept QUIC stream'
# TCP 有重传,慢一点但稳得多。
# 注意:早先在 192.168.2.x 的出口上 TCP 7844 是被挡的,
# 换了出口就通了 —— 换网络必须重测,别照搬旧结论。
ExecStart=/usr/local/bin/cloudflared tunnel --no-autoupdate \
--protocol http2 --edge-ip-version 4 --ha-connections 2 --retries 5 \
--url http://127.0.0.1:8080
Restart=always
RestartSec=5
MemoryMax=128M
StandardOutput=append:$RELAY_DIR/tunnel.log
StandardError=append:$RELAY_DIR/tunnel.log
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable lanremote-relay lanremote-tunnel >/dev/null 2>&1
systemctl start lanremote-relay
sleep 3
systemctl start lanremote-tunnel
echo " 两个服务已注册并启动"
# ---------- 3b. 装隧道看门狗 ----------
# 匿名隧道建立之后中途失效没人管,用户只能自己发现、自己上来重启。
# 看门狗每 5 分钟检查一次公网可达性,连续 3 次不通就重启隧道。
echo
echo "[3b] 装隧道看门狗"
if [ -f "$RELAY_DIR/watchdog.sh" ] || [ -f "$SCRIPT_DIR/watchdog.sh" ]; then
if [ -f "$SCRIPT_DIR/watchdog.sh" ] && \
[ "$(readlink -f "$SCRIPT_DIR/watchdog.sh")" != \
"$(readlink -f "$RELAY_DIR/watchdog.sh" 2>/dev/null || echo x)" ]; then
cp -f "$SCRIPT_DIR/watchdog.sh" "$RELAY_DIR/watchdog.sh"
fi
chmod +x "$RELAY_DIR/watchdog.sh"
cat > /etc/systemd/system/lanremote-watchdog.service <<'WDUNIT'
[Unit]
Description=LanRemote 隧道看门狗(检查公网可达性)
After=lanremote-relay.service lanremote-tunnel.service
[Service]
Type=oneshot
ExecStart=/bin/bash /root/lanremote-relay/watchdog.sh
MemoryMax=64M
StandardOutput=append:/root/lanremote-relay/watchdog.log
StandardError=append:/root/lanremote-relay/watchdog.log
WDUNIT
cat > /etc/systemd/system/lanremote-watchdog.timer <<'WDTIMER'
[Unit]
Description=每 5 分钟检查一次隧道
[Timer]
OnBootSec=3min
OnUnitActiveSec=5min
AccuracySec=30s
Unit=lanremote-watchdog.service
[Install]
WantedBy=timers.target
WDTIMER
systemctl daemon-reload
systemctl enable lanremote-watchdog.timer >/dev/null 2>&1
systemctl start lanremote-watchdog.timer
echo " 看门狗已启用(每 5 分钟检查一次)"
else
echo " [跳过] 没找到 watchdog.sh"
fi
# ---------- 4. 取地址 ----------
echo
echo
echo "[3c] 网络调优(修 MTU —— 这是可用率从 48% 提到 90% 的关键)"
# 实测:接口 MTU 1500 时,ping 包 1488 就丢、1500 全丢。
# TLS 握手要发大证书包 -> 被丢 -> 新建连接约 30% 失败。
# 夹 MSS 到 1360 + 打开路径 MTU 探测之后,可用率 48%~75% -> 90%~92%。
# 注意:**不要改接口 MTU**,实测降到 1400 反而掉到 50%。
if [ -f "$SCRIPT_DIR/net-tune.sh" ]; then
cp -f "$SCRIPT_DIR/net-tune.sh" "$RELAY_DIR/net-tune.sh"
elif [ -f "$RELAY_DIR/net-tune.sh" ]; then
: # 已经在位
fi
if [ -f "$RELAY_DIR/net-tune.sh" ]; then
chmod +x "$RELAY_DIR/net-tune.sh"
# 内核参数持久化
cat > /etc/sysctl.d/99-lanremote-mtu.conf <<'MTUEOF'
# LanRemote: 修路径 MTU 问题。
# 接口 MTU 1500 时大包会被丢(1488/1500 全丢),导致 TLS 握手约 30% 失败。
net.ipv4.tcp_mtu_probing = 1
MTUEOF
sysctl -p /etc/sysctl.d/99-lanremote-mtu.conf >/dev/null 2>&1
# 开机自动夹 MSS
cat > /etc/systemd/system/lanremote-nettune.service <<NUNIT
[Unit]
Description=LanRemote 网络调优(修 MTU:夹 MSS + 路径探测)
After=network-online.target
Wants=network-online.target
Before=lanremote-relay.service lanremote-tunnel.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=$RELAY_DIR/net-tune.sh
[Install]
WantedBy=multi-user.target
NUNIT
systemctl daemon-reload
systemctl enable lanremote-nettune.service >/dev/null 2>&1
systemctl start lanremote-nettune.service
sleep 1
echo " tcp_mtu_probing = $(cat /proc/sys/net/ipv4/tcp_mtu_probing)"
echo " MSS 规则 $(iptables -t mangle -L -n 2>/dev/null | grep -ci mss) 条"
echo " 已注册 lanremote-nettune.service(开机自动应用)"
else
echo " 跳过:找不到 net-tune.sh"
fi
echo "[4/5] 等隧道拿到公网地址"
URL=""
for i in $(seq 1 45); do
sleep 2
URL=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' \
"$RELAY_DIR/tunnel.log" 2>/dev/null | tail -1)
[ -n "$URL" ] && break
done
if [ -z "$URL" ]; then
echo " 还没拿到地址,隧道日志:"
tail -15 "$RELAY_DIR/tunnel.log" 2>/dev/null || true
exit 1
fi
echo "$URL" > "$RELAY_DIR/public_url.txt"
# 便捷命令
cat > /usr/local/bin/lanremote-url <<'EOF'
#!/bin/bash
# 打印中转服务器的公网地址(隧道地址每次重启会变)
cat /root/lanremote-relay/public_url.txt 2>/dev/null || echo "(还没有地址)"
EOF
chmod +x /usr/local/bin/lanremote-url
cat > /usr/local/bin/lanremote-key <<'EOF'
#!/bin/bash
# 打印管理页钥匙
cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null || echo "(还没有钥匙)"
EOF
chmod +x /usr/local/bin/lanremote-key
cat > /usr/local/bin/lanremote-status <<'EOF'
#!/bin/bash
# 一眼看清中转 + 隧道的状态
echo "=== 服务 ==="
for s in lanremote-relay lanremote-tunnel lanremote-watchdog.timer; do
printf " %-28s %s\n" "$s" "$(systemctl is-active "$s" 2>/dev/null)"
done
echo
echo "=== 公网地址 ==="
cat /root/lanremote-relay/public_url.txt 2>/dev/null || echo " (还没有)"
echo
echo "=== 可达性 ==="
URL=$(cat /root/lanremote-relay/public_url.txt 2>/dev/null)
if [ -n "$URL" ]; then
C=$(timeout 20 curl -4 -s -o /dev/null -w '%{http_code}' "$URL/v1/health" 2>/dev/null)
echo " HTTP ${C:-超时}"
fi
echo
echo "=== 看门狗最近记录 ==="
tail -3 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /' || echo " (还没有)"
echo
echo "=== 内存 ==="
free -m | awk 'NR==2{printf " 已用 %s MB / 可用 %s MB\n", $3, $7}'
EOF
chmod +x /usr/local/bin/lanremote-status
# ---------- 5. 自检 ----------
echo
echo "[5/5] 自检"
sleep 3
if curl -s --max-time 8 http://127.0.0.1:8080/v1/health | grep -q '"ok"'; then
echo " 本机健康检查: 通过"
else
echo " 本机健康检查: 失败"
systemctl status lanremote-relay --no-pager -l | tail -10
fi
if curl -s --max-time 20 "$URL/v1/health" | grep -q '"ok"'; then
echo " 公网访问: 通过"
else
echo " 公网访问: 还没通(隧道可能要再等一会)"
fi
KEY=$(cat "$RELAY_DIR/data/admin_key.txt" 2>/dev/null || echo "")
echo
echo "============================================================"
echo " 装好了"
echo "============================================================"
echo
echo " 公网地址 $URL"
echo " 管理页 $URL/?key=$KEY"
echo
echo " 服务状态 systemctl status lanremote-relay"
echo " systemctl status lanremote-tunnel"
echo " 看地址 lanremote-url"
echo " 看钥匙 lanremote-key"
echo
echo " 注意:快速隧道的地址**每次重启会变**。"
echo " 想固定地址要用带域名的具名隧道,见 使用说明。"
echo
free -m | head -2
echo
ps -eo rss,comm --sort=-rss 2>/dev/null | head -4 | awk '{printf " %-8s %s\n", $1, $2}'