---
name: rt-diagnose
description: 诊断并修复 rt 客户端（跨平台设备网络）的问题。当用户说 rt 跑不起来、rt up 失败、SSH 连不上某台 rt 设备、rt 报 Segmentation fault /段错误、rt 提示没登录但明明装过、rt doctor 输出里有异常、看门狗不保活、隧道域名失效时使用。也用于回答"rt 能连上但很慢""局域网设备显示连不上"这类问题。触发词：rt 报错、rt 坏了、rt 连不上、rt doctor、rt upgrade 失败、rt 段错误、rt 隧道断了。
---

# rt 客户端排障

> 你正在帮用户修 **rt**（跨平台设备网络）。rt 让同一账号下的机器互相 SSH。
> 客户端在 Linux / macOS / Windows / Android-Termux 上跑。

## 第一步：拿到诊断信息（不要瞎猜）

**永远先要报告，不要凭用户描述推测。** rt 的失败模式高度依赖平台与权限，
"SSH 连不上"可能是六个完全不同的原因。

```bash
rt doctor --report
```

它生成 `~/.rt/rt-diag-<时间戳>.txt`，里面已包含：崩溃现场、平台探测、权限、
依赖清单、sshd 状态、账号信息、隧道日志。**已经自动脱敏**（登录 token 与隧道地址
已替换为 `<TOKEN>` / `<TUNNEL_HOST>`）。

拿到文件后读它。重点看这几段：

| 报告里的段落 | 说明什么 |
|---|---|
| `--- 崩溃现场 ---` | 有内容= 上次运行崩了，**这里最可能有直接答案** |
| `## 平台` | `termux=1` / `proot=1` / `darwin=1` 决定了后续所有判断 |
| `## 权限` | `真 root: no` + `sudo: 无` = 端口必然降到 8022 |
| `## 依赖` | `✗ 缺失` 的项要先装 |
| `## sshd` | `配置: ✗ 尚未生成` = 从没成功跑过 `rt up` |
| `##隧道` | `(空)` = cloudflared 没起来或日志写错了地方 |

## 硬性约束（必须遵守）

1. **不要要求用户提供密码、token、密钥。** 报告已脱敏，且用户不需要给你这些。
   如果某个判断真的需要登录信息，让用户自己跑 `rt doctor --report --with-login`
   并**自己决定**是否给你 —— 但绝大多数问题不需要。
2. **不要修改 `<TOKEN>` / `<TUNNEL_HOST>` 占位符**，也不要把它们"还原"。
3. **涉及不可逆操作先问用户**：删设备、删 `~/.rt`、改防火墙、动系统 sshd 配置、
   装系统级软件包。这些都要先说明后果并得到确认。
4. **改完让用户验证**，不要只报告"我改好了"。

## 已知故障速查表

下面每一条都是真实踩过的坑，**按报告里的证据对照命中**。

### 1. `Segmentation fault` / 段错误（退出码 139）

**先看报告的「崩溃现场」段**。rt 现在用「运行标记」抓崩溃，跨 SIGSEGV 有效。

若报告里**没有**崩溃记录，说明崩溃发生在标记机制之外（进程被信号杀死时
确实抓不到）。此时按顺序排查：

- **栈溢出/无限递归**：查最近改过的函数是否自己调自己。这是 rt 历史上最典型的
  段错误来源（v0.8.51 的 `_detach()` 误写导致 Termux 必崩、macOS 却正常）。
  判据：**同一份代码在某个平台崩、另一个平台完全正常** → 强烈怀疑平台分支。
- **Termux 特有**：确认 `$PREFIX` 下有 `setsid`（有才会走那条路径）。
- 交叉验证：在目标平台前台跑 `rt up`（不要后台），段错误会直接打栈。

### 2. `rt up` 说需要 root / 权限

**不要让用户盲目 `sudo`。** 先看报告 `## 权限` 段：

- `真 root: no` 且 `sudo: 无` → 端口已自动降级到 8022，**这是正常降级不是错误**。
- 用户以 `sudo bash install.sh` 装过 → rt 装到了 `/root/.local/bin`、
  配置在 `/root/.rt`，而用普通用户跑时读的是另一个 HOME 的配置。
  **修法：用普通用户重装**（不加 sudo），而不是给命令加 sudo。

### 3. `hostname contains invalid characters`

隧道域名里带了不可见字符（历史上是 CR）。表现是 ssh 直接拒绝。

```bash
grep -n 'reported' ~/.rt/config | cat -A   # 看有没有 ^M 之类的控制字符
```

修法：手工改 `~/.rt/config` 里的 `reported=` 那一行，删掉异常字符，
然后 `rt announce` 重新上报。

### 4. 端口被占 / `Address already in use`

sshd 已经在跑 → **不用管**。rt 会检测并复用。
若报告里 `## sshd → 进程` 为空但 `端口` 显示在监听，那可能是别的进程占着，
用 `ss -ltnp`（或 macOS 的 `lsof -i :22`）看是谁。

### 5. 隧道起不来 / 域名失效 / 网页显示「隧道断」

**先分清是哪一种失效** —— CF 回收 quick tunnel 有两种形态，判据不同：

| 形态 | 现象 | 判据 |
|---|---|---|
| ① 域名注销 | DNS 无解析 | `curl` 退出码 **6** |
| ② **CF 端隧道已死** | **DNS 正常解析，HTTP 返 530**（带 `Retry-After: 120`） | `curl -w '%{http_code}'` = **530** |

```bash
# 形态② 是元凶：它 DNS 正常，所以"只判 DNS"的旧逻辑抓不到，
# 会一直以为隧道活着，直到永远。
curl -s -o /dev/null -m 8 -w '%{http_code}\n' "https://<隧道域名>"
```

**为什么②会发生**：CF 会回收**长期无人连接**的 quick tunnel。
设备上的 cloudflared 日志会明写 `no more connections active and exiting`。
所以「进程在 + 域名在」完全不代表隧道还能用。

**判据要小心 `000`**：那是 curl 一个 HTTP 响应都没拿到（超时/网络不通），
**分不清"隧道死了"和"本机网络抖了"** → 不该据此重建，否则会造成重建风暴。
只有 `6`（域名注销）和 `5xx/530/1033`（CF 明确回了响应）才重建。

**注意「局域网能连 ≠ 公网正常」**：`rt ssh` 若走了局域网直连（本机与设备同网段），
它完全不经过 CF —— 所以你能 ssh 通，但网页（从 CF 侧探测）显示「隧道断」，
**这两个可以同时成立且都是对的**。别因为 ssh 通就断定隧道活着。

看 `## 隧道` 段：
- 日志为空 → 写日志失败了。检查 `/tmp` 是否可写、目录是否被 root 属主文件占用。
  修法：把日志挪到 `$RT_HOME/` 下。
- 有日志但没域名 → cloudflared 没起来。检查是否安装：`command -v cloudflared`。
- 域名有但连不上 → **先用上面的 curl 判 HTTP 码**，别只看 DNS。
  看门狗会对账并自动重建（v0.8.62 起 SSH 与 VNC 两侧都支持 530 判据）；
  没重建就手动 `rt selfheal on && rt up`。

### 6. 局域网直连没生效

报告 `## 平台` 里若 `RT_NO_LAN` 之类被设过，先清掉。默认是开的。
另外局域网判断是**设备本地做的**（比对访问者 IP 与自己的 `lan_ip`），
不是后端判断 —— 这点不要搞反。

### 7. macOS 远程控制（VNC）报 `window.crypto.subtle is undefined`

局域网入口是 `http://192.168.x.x:8091`，**不是 Secure Context**，浏览器不提供
WebCrypto。rt 的解法是已知的双管齐下：kickstart 开传统 VNC 口令（type 2）+
noVNC 补丁跳过 ARD。若仍失败，让用户跑 `rt doctor` 看 `vnc_diag` 段。

### 7. 远程控制（VNC）点不开 / 一直连不上

**先分清是「哪一层」挂了**，别直接改代码。VNC 链路有 5 层，从下往上：

```
手机/桌面 VNC 服务(5900) → ws_bridge(8090) → vnc_auth_proxy(8091) → CF 隧道 → 浏览器
```

在设备上按顺序查（`rt ssh <设备名> '...'`）：

```bash
# ① RFB 源在不在（Termux 需 droidVNC；Linux 需 x11vnc；Win 需 TightVNC）
grep -c ":170C" /proc/net/tcp        # 5900 = 0x170C，没有就是 VNC 服务没起
ps -eo args | grep -iE '[d]roidvnc|[x]11vnc'
# ② 桥与代理在不在
ps -eo args | grep -E '[w]s_bridge|[v]nc_auth_proxy.py 8091'
# ③ 代理日志（关键：它会明确写 allow / reject）
tail -6 ~/.rt/vnc_auth_proxy.log
# ④ 域名对不对（后端 vnc_host 是从这里来的）
head -1 ~/.rt/vnc_info
```

| 日志/现象 | 根因 | 修法 |
|---|---|---|
| `reject http: GET /app/ui.js` 一片 401 | 代理在 LAN 模式下要求**所有**请求带 token，而 noVNC 的 JS/SVG 资源浏览器不会自动带 → 页面 JS 加载失败 | v0.8.57 已修（只校验 WS + HTML 主文档）。老版本设备升级即可 |
| `reject ws` | token 不匹配 | 比对 `~/.rt/vnc_token` 与后端 `vnc_token` 是否同一个值 |
| `allow http` + `allow ws` 都有，但浏览器仍连不上 | 5900 没监听（VNC 服务没起） | Termux 装 droidVNC；Linux 装 x11vnc；mac 开「屏幕共享」 |
| `Connection closed (1006)` | 代理/桥通了，但 RFB 源握手失败 | 同上，先确认 5900 |
| `vnc_info` 首行为空 | 代理没读到隧道日志 → 后端 `vnc_host` 是旧域名 | 确认 `$HOME/cf8091.log` 存在且有 trycloudflare 域名 |

> ⚠ **`~/.rt/vnc_info` 三行分别是**：隧道域名 / token / 局域网 URL。
> 网页上的两个按钮（「远程控制」「远程控制（局域网）」）**全靠这个文件上报**，
> 它不对就一定点不开。

> ⚠ **Windows 没有局域网 VNC 入口**（`vnc_lan` 为空）—— 这是设计如此，
> Windows 的 RDP 走公网隧道。网页上不会出现局域网按钮是正常的。

#### VNC 专用的两个「假在线」陷阱（v0.8.58/59 修的，别的 AI 不知道）

**陷阱一：网页上「远程控制」按钮变灰 ≠ VNC 不可用**
v0.8.58 之前那个按钮绑在 **SSH 可达性**上，而 VNC 走的是 **8091 独立隧道**。
SSH 因 CF 换边缘节点而短暂不可达时（每次 `rt up` 都会发生几秒），VNC 按钮立刻变灰，
但此时 VNC 其实好好的。已修。现在按钮只按「设备报没报入口」显示。

**陷阱二：设备显示「在线」但 VNC 连不上**
`online` 原来只按 `last_seen` 的 15 分钟窗口算，而客户端 v0.7.0 起是**事件驱动上报**
（地址变了才报）—— 设备安稳无事就几小时不上报，于是被误判离线。
v0.8.58 起：**有现场探测结果时以探测为准**（`last_probe_state`/`last_probe_at`）。

**排查时先看 `vnc_diag`**（网页设备卡上有「⚠ 远程控制不可用」角标，点开有详情）。
它会直接说人话，例如：

```
远程控制不可用：本机没有 VNC 服务在 5900 监听。
Android/Termux：装并启动 droidVNC-NG（rt 管不了它，需要你在手机上授权屏幕共享）；
Linux：sudo apt install x11vnc 然后启动它；macOS：系统设置→通用→共享→屏幕共享。
装好后重跑 rt up。
```

**这一条 rt 帮不上忙** —— VNC 服务在**被控端机器上**，rt 只是个隧道，
装droidVNC / 开屏幕共享都得用户自己动手。看到这条就直说，不要试图用 rt 修。

#### VNC 的第三个陷阱：域名不同步（v0.8.61 修）

**现象**：设备上 `~/.rt/vnc_info` 第一行是新域名，但后端 `vnc_host` 还是旧的
→ 网页点远程控制必然失败（CF 返 530/ERR_NAME_NOT_RESOLVED）。

**判据**：
```bash
head -1 ~/.rt/vnc_info            # 设备实际用的
# 与后端的 vnc_host 比对（rt ls 那一列也是它）
```

**根因（已修）**：`vnc_start` 写的是 `$HOME/.rt/vnc_info`，而**心跳读的是
`$RT_HOME/vnc_info`**。两者不一致时（RT_HOME 被显式指定、或非交互 ssh 的 HOME=/root）
域名就永远同步不上。实测 kali 上就是 `citations-knives-…` vs `oklahoma-boring-…`。
现在全项目统一用 `$RT_HOME`。

> 如果你看到这两个域名不一致：**先确认设备上跑的是哪个 rt**
> （`rt doctor` 的「依赖」段会列出所有 rt 路径与版本 —— 多路径安装会让
> 「你以为升级了、其实跑的是另一份旧版」发生，实测 kali 上有 3 份）。

### 8. 看门狗被杀 / 隧道反复断

```bash
pkill -f "[w]atchdog 60"   # 中括号是技巧，避免 pkill 杀掉自己所在的 shell
rt up                     # 重启看门狗
```

⚠ 若用户是**换过 rt 文件**却没重启看门狗 —— 看门狗启动时把脚本读进了内存，
改文件不生效。这是很常见的"我明明改了却还是老样子"。

### 9. 升级失败

- `下载内容版本号不符` → 线上文件与后端版本号不一致，让用户稍后重试。
- `语法检查未通过` → 新版本有问题，rt 已放弃安装，现有版本未受影响。
  这种情况请反馈给维护者，不要绕过。
- Windows 用 `rt.ps1 upgrade`；注意 `.ps1` 必须是 UTF-8 with BOM + CRLF。

### 10. `rt android text` 报「未安装 uiautomator2」/ 中文打不进去

**先别查手机** —— 这个报错几乎总被理解成「手机缺东西」，实际不是：

- `set_text` 跑在 **rt 现在所在的这台机器** 上（控制端），经 adb 写被控机
  ⇒ 被控手机上装不装 uiautomator2 **完全无关**。
- 同一个「没装」报错，可能只是因为 **rt 跑在手机自己的 Termux 里**（那边默认不装）。
  同机型、同设备，rt 挪到某台装了 u2 的电脑上就正常 —— 这不是玄学。
- 处置顺序：① `rt u2-status`（查的就是 rt 所在机器）→ ② `rt setup-u2` →
  ③ 还不行就把 rt 挪到电脑上跑同一条命令。
- 只发英文/数字不需要 u2（走 `adb shell input text`）；
  中文必须 u2，因为 adb 既不能输中文、也写不进系统剪贴板。
- 通道与「一条命令发消息」的完整做法见仓库 `docs/AI-CONTROL-RECIPE.md`
  （含 `tools/rt-android-ready.sh`、`tools/rt-wechat-send.py` 与实测耗时）。

## 你能自己修 vs 必须转人工

**能自己修**（占绝大多数）：装依赖、改配置项、清理残留文件、重启看门狗、
重装客户端、清空 `~/.rt` 重新登录（⚠ 会换设备名/指纹，需先确认）。

**必须转人工**：客户端源码里的 bug、新增功能、被杀进程式的段错误（栈已废、
无法还原）、后端行为异常。让用户把报告发给维护者。

## 收尾

修完给用户一句话结论：**改了什么、为什么这么改、怎么验证**。
如果报告里有关键信息缺失，直接说缺什么、让用户补跑哪条命令 —— 别猜。
