mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 23:00:44 +00:00
琚帶绔?- 淇 MTU 闂锛氬す MSS + 璺緞 MTU 鎺㈡祴锛堝叕缃戝彲鐢ㄧ巼 48% -> 100%锛?- 澶嶇敤 HTTP 杩炴帴锛堝厤鎺夋瘡娆℃柊寤?TLS 鐨?30% 鎻℃墜澶辫触锛?- 姣忓垎閽熷洖澶存壘灞€鍩熺綉锛岃嚜鍔ㄥ垏鍥炲揩 400 鍊嶇殑璺緞 - 鐢婚潰蹇冭烦甯э細闈欐鏃剁渷娴侀噺锛屽鎴风浠嶇煡閬撴祦鏄椿鐨?- 閿佸睆鏃剁粰鍑哄彲鎿嶄綔鐨勬彁绀? 鎵嬭〃绔?- 鍐呯疆 GTS Root R4 / GlobalSign Root CA锛屼慨 SSLHandshakeException - 杩炴帴閿欒缈昏瘧鎴愪汉璇濓紙璇佷功/鏃堕棿/瑙f瀽/瓒呮椂鍒嗗埆鎻愮ず锛?- 缁忎腑杞繛涓婂悗鐢ㄦ湇鍔″櫒鎶ョ殑鍦板潃鍒锋柊宸查厤瀵硅褰?- 鏂板灞€鍩熺綉杩炴帴椤甸潰銆佹湇鍔″櫒寤惰繜妫€娴? 鏈嶅姟鍣ㄧ - 鏀寔 chunked 璇锋眰浣擄紙淇瘡 16 绉掍竴娆$殑杩炴帴閿欎贡涓庡ぇ闈㈢Н 502锛?- 缃戠粶璋冧紭鑴氭湰 net-tune.sh锛堝紑鏈鸿嚜鍔ㄥ簲鐢級 - 绠$悊椤碉細鎵归噺鍒犻櫎銆佺綉缁滀綋妫€銆佷笅杞藉尯 - 闅ч亾鏀?HTTP/2 + 鍙岃繛鎺? 鏂囨。 - 璁稿彲鍗忚 / 瀹夎鍓嶅繀璇?/ 瀹夎鍚庨』鐭ワ紙浣滆€咃細灏忛害瀛愶級
427 lines
14 KiB
Bash
427 lines
14 KiB
Bash
#!/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}'
|