2026年7月23日星期四

手机 tmux 客户端的网络接入之争

tmux-next 写完之后,顺手把市面上同类产品翻了一遍。发现这批工具真正在解决的,其实不是”画一个终端”,而是一个网络问题。

核心难题

你的机器在路由器后面,运营商多半还套了一层 CGNAT——它能主动往外连,但外面连不进来。手机在 4G/公司 WiFi/别人家,随时换网。要打通,无非四条路,这批 app 各选其一(或让你自选):

A · 公网反向代理——tmux-next(默认)用了这条。一个域名指向你的机器,前面放 Caddy/Nginx 终结 HTTPS 再转发到本地端口。要么你有公网 IP + 端口转发,要么反代本身再叠一层隧道(下面 C)。

B · 叠加网络 / 网状 VPN——Tmux Bridge 就是典型。手机和机器装进同一张虚拟网(WireGuard),之后像在同个局域网一样直接访问,不开任何入站端口,CGNAT 也穿得过去。

C · 出站隧道——Reattach 提供 Cloudflare Tunnel 选项。机器上跑个 cloudflared 守护进程,主动往云端拨一条常连的加密隧道,外部请求先到 Cloudflare 边缘,再顺着隧道推回机器。全程只有出站连接。

D · 直接 SSH——dotMux、Attach、NeoServer 都是这路。手机 SSH 进机器,拿到一个加密的 PTY 通道。但 SSH 端口得可达——所以现实里它常和 B/C/端口转发/跳板机叠着用。

关键洞察是这四条路不是互斥的,而是分层的:B、C 解决”够到”,A、D 解决”够到之后说什么话”。tmux-next 的反代完全可以架在 Cloudflare Tunnel 之上;dotMux 的 SSH 也常跑在 Tailscale 里。真正区分产品的,是它们把哪几层替你封好了。

三种”够到”的网络机制

Tailscale = WireGuard + 协调服务 + DERP 兜底。协调服务器只交换公钥,私钥永远不出设备。两端拿到对方信息后,用 STUN + 打洞尝试建直连 P2P(官方称九成以上能直连)。打不通时流量走 DERP 中继:它只在 443 上转发已经被 WireGuard 加密过的包,中继看不到明文。

Cloudflare Tunnel = cloudflared 出站拨号。机器上的守护进程向 Cloudflare 边缘拨出若干条 QUIC 长连接(往不同机房各一条,端口 7844),并一直保持。用户请求先打到 Cloudflare,再被顺着隧道推回机器。机器不监听任何公网端口,CGNAT 下照样能用。

反向代理 = 你自己终结 TLS。没有第三方叠加网时,就是经典打法:域名解析到你的机器,Caddy 监听 443、拿证书、验登录、转发到本地服务。tmux-next 正是如此。

够到之后:应用层协议

通道打通了,手机和 tmux 之间说什么话?目前两种。

裸 PTY:SSH 里直接 tmux attach,拿到一串终端字节流,本地用终端模拟器渲染。简单、通用,但手机拿到的只是一屏字符,没有窗口/pane 的结构信息。Attach、NeoServer 走这个路线。

tmux control mode:用 tmux -CC 打开控制模式。tmux 不再吐终端画面,而是一套结构化的行协议。发命令返回被 %begin / %end 包起来的输出块,还有以 % 打头的异步通知——%output%window-add%layout-change 等等。

拿到这套结构,用法就分野了:dotMux 把 %window-add / %layout-change 翻译成原生 iOS 的窗口和分屏;tmux-next 则主要用 %output 把字节喂给浏览器里的 xterm.js。同一套协议,一个驱动原生 UI,一个驱动网页终端。

tmux-next 的字节,端到端

手机端说的是 WebSocket,不是 SSH——这是 tmux-next 和那些原生 app 最根本的分岔。

1
浏览器 → WebSocket / 443 → Caddy(TLS + JWT 登录)→ 本地 Bun 服务 → control mode → tmux 会话

返回的方向同理:tmux 的 %output 被 Bun 解析后,通过同一条 WebSocket 把字节推给 xterm.js 渲染。手机只需要一个浏览器;SSH、control mode 这些都被关在服务端和本机之间,从不暴露给公网。

为什么是 WebSocket 而不是 SSH?因为客户端是”网页”。浏览器不能开 SSH 连接,但天生会说 WebSocket。代价是你得自己搭反向代理(TLS + 登录);回报是客户端零安装、跨设备,攻击面收敛成一个带登录的 HTTPS 端点,而不是一个对公网开放的 SSH/shell。

两个边角细节

TLS 证书:tmux-next 的 Caddy 用 DNS-01 校验,通过 Cloudflare 的 DNS 接口写一条 TXT 记录来证明域名归你,不必对公网开放 80 端口就能拿到 Let’s Encrypt 证书。适合藏在 NAT 后、端口能不开就不开的机器。

推送通知:原生 app 目前领先、tmux-next 还空白的一环。原生用 APNs,网页形态可以用 Web Push(VAPID)。把 tmux-next 加到主屏当 PWA 就能做,会话列表其实已经在判定活跃状态了(绿点排序),接上推送只是时间问题。

整理自:tmux Control-Mode wiki、Tailscale how-it-works、Cloudflare Tunnel 文档、dotMux 官网。

tmux-next:在手机上查看 tmux 会话

用 Claude Code 跑个长时间任务(重构、批量迁移之类的),然后人走开,过一会儿想在手机上看看跑完没有。以前的做法是跑回电脑前 tmux attach,或者 ssh 进去。有点烦。

tmux-next 就是在那个场景下写的。

做了什么

一个很小的 tmux 网页客户端。装好之后 bunx tmux-next 起一个服务,浏览器打开就能看到所有 tmux 会话。每个会话显示最后几行输出,Claude 打完一轮在等你回复时,那个会话会亮绿点并排到最前面,省得一个个点开看。

打开一个会话就是全屏的终端,手机键盘上没有的键(Esc、Ctrl、方向键)放在底部的工具条里。

最常用的场景

躺沙发上用手机看 Claude Code 的进度。不用再 ssh、不用开笔记本。扫一眼绿点就知道它还在等我还是正在跑。

代码托管在 GitHub,也发到了 npm

关于安全

它自己没有登录,只绑在 localhost。想从手机访问得在前面放一个反向代理(比如 Caddy)做 HTTPS 和登录,不然等于把一个 shell 挂到公网上。项目首页有完整的 Caddy 部署指引,跟着配就行。