从 1ms 直连到全局证书信任:OMP 与 Tailnet 点对点终端协同实录
我一直梦想着一种真正的、没有妥协的跨终端协作编程体验。
不是那种把所有协作者强行拉伸到同一个物理分辨率下的 tmux 共享——在那种模式下,用手机加入电脑的会话简直是一场灾难,你必须互相迁就对方的终端布局。我想要的是:主控端(笔记本)发起会话,协作者(手机 Termux、电脑浏览器、其他终端)可以独立渲染,各自以最适合自己屏幕尺寸的排版渲染 TUI 界面,但底层的数据流、光标和 AI 提示却保持完美同步。
Oh My Pi (OMP) 提供了原生的 /collab 协同信道,但官方默认使用的 wss://my.omp.sh 公网中转服务器因为地理位置阻隔以及全球负载,延迟通常在几百毫秒以上,打起字来有明显的粘滞感。
既然我手中有一套完整的、由 Headscale 协调的私有 Tailnet 网格,我们能不能打破这堵墙,利用 P2P 直连与自建 CA 证书,在内网搭建一套 1ms 延迟、全局 TLS 强加密的独立终端协同网络?
不仅能,而且其优雅与丝滑程度,远远超出了最初的想象。
以下是这套“终极移动协同方案”从零到一的完整搭建与深夜排障实录。
架构设计:点对点直连的“漂移服务”
要实现这套协同生态,我们需要在三个端之间打通链路:
- 主控端 (Laptop):启动 OMP 本地中继(Local Relay),充当 WebSocket 交换机,同时托管并分发 React 协同前端。
- 协调端 (Headscale / hk-edge):用于节点发现、打洞协商以及自建 Root CA 证书签发。
- 协作端 (Phone Termux / Web Browser):通过域名访问中转,通过 E2E 加密的加密二进制帧与主控端实时互动。
在传统的局域网协同中,如果直接使用明文 ws:// 协议,现代浏览器在安全上下文(Secure Context)中会直接拦截 WSS 以外的连接。因此,我们必须为私有内网域名(例如 collab.tailnet.cagedbird.cn)以及内网 IP 签发可信的 SSL 证书。
第一步:TLS 根签名与内网 SAN 别名签署
因为我的 Laptop 已经在系统信任锚点 /etc/ca-certificates/trust-source/anchors/ 中导入了自建的 headscale-direct-root.crt。这意味着:只要能让这把自建 Root CA 签发一张覆盖本域名的证书,本机和整个 Tailnet 内的所有信任节点就能无警告、无绿锁报错地握手。
然而,由于私有 IP(100.64.0.1)无法通过 Let’s Encrypt 等公网 CA 的 HTTP-01 验证,我们只能通过 DNS-01 或者在私有 CA 侧直接手动签发。
在本机生成私钥与 CSR(证书签名请求)配置文件
/tmp/laptop-cert.conf,这里非常关键的一点是,必须在 Subject Alternative Name (SAN) 中同时写入内网域名、自定义服务域名以及内网 IP:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[dn]
CN = laptop.tailnet.cagedbird.cn
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = laptop.tailnet.cagedbird.cn
DNS.2 = collab.tailnet.cagedbird.cn
IP.1 = 100.64.0.1然后生成 CSR。
将 CSR 传输至香港服务器
hk-edge,使用受保护的 Root CA 密钥进行签署,生成/tmp/laptop-tailnet.crt并传回 Laptop:1
2
3
4
5
6
7sudo openssl x509 -req -in /tmp/laptop-tailnet.csr \
-CA /etc/headscale/tls-direct/rootCA.crt \
-CAkey /etc/headscale/tls-direct/rootCA.key \
-CAcreateserial \
-out /tmp/laptop-tailnet.crt \
-days 365 -sha256 \
-extfile <(printf "subjectAltName=DNS:laptop.tailnet.cagedbird.cn,DNS:collab.tailnet.cagedbird.cn,IP:100.64.0.1")
第二步:解耦硬编码,把中转服务器升级为“双用 Web 容器”
OMP 本地中继脚本 local-relay.ts 在官方的最初设计中,仅在接收到 WebSocket 升级协议时工作。如果协作成员通过浏览器点开分享链接,向根目录 / 发起 HTTP Get 请求,服务会直接返回 not found (404)。而公网生产环境 my.omp.sh 则是在反代侧挂载了编译好的 React 前端应用(collab-web)。
为了防止后续拉取上游代码覆盖我们本地的改动,并且让本地中转“开箱即用”:
- 重构启动解析器:通过在
local-relay.ts的parseArgs中加入通用的--tls-key和--tls-cert选项,将物理证书路径与 Git 追踪代码完全解耦。 - 挂载静态文件目录:重写了
Bun.serve的fetch拦截逻辑。如果是正常的浏览器请求,且文件存在于本地编译好的dist/文件夹中,则直接通过Response(file)分发静态 React 资源,没有文件则返回 404。
部分核心代码实现:
1 | export function startLocalRelay(port = 0, tls?: { key?: string; cert?: string }): LocalRelay { |
这不仅优雅,而且直接让本地中转变成了一个完全独立的 Web + WebSocket 服务器,不需要再在本机配置复杂的 Caddy/Nginx 反代。
第三步:利用 Systemd 绑定 443 端口
为了让域名更干净,我们希望省略链接里的端口号。但绑定小于 1024 的特权端口(如 443)需要 root 权限。
如果在 Systemd 用户级服务中运行,会直接报 EACCES。最完美的做法是:将服务移至系统级(/etc/systemd/system/),以普通用户 cagedbird 身份运行,但通过 AmbientCapabilities 借用特权端口绑定权限:
在 /etc/systemd/system/omp-collab-relay.service 中配置:
1 | [Unit] |
重新加载并拉起,服务在后台完美运行在 wss://localhost:443!
第四步:Headscale 自定义 DNS 漂移解析
因为 Laptop 并没有运行官方的 tailscaled,而是通过 sing-box 的 tailscale inbound 端点接入 Headscale 控制平面。
为了能让局域网和 Tailnet 内的所有节点直接通过 collab.tailnet.cagedbird.cn 找到本机的中继:
- 登录香港服务器,在 Headscale
/etc/headscale/config.yaml中配置extra_records:1
2
3
4
5dns:
extra_records:
- name: collab.tailnet.cagedbird.cn
type: A
value: 100.64.0.1 - 为了在本机解析生效,在 Laptop 的
sing-box-private-prod配置模板的 DNS 规则中,加入了对*.tailnet.cagedbird.cn后缀的分流规则,强制将其导向tailscale的 MagicDNS 适配器进行解析:推送模板并执行1
2
3
4
5{
"domain_suffix": ["tailnet.cagedbird.cn"],
"action": "route",
"server": "tailnet"
}sbc update。此时,ping collab.tailnet.cagedbird.cn瞬间返回100.64.0.1,DNS 通路完全闭环!
第五步:Termux 证书链信任与环境变量隔离
当我们在手机 Termux 上执行 omp join 时,遇到了最后一个大坑:unable to get local issuer certificate (60) 证书不受信任。
CA 证书导入:首先,Termux 容器内部的 CA 证书链(
/usr/etc/tls/cert.pem)是一个独立的沙盒文件,并不会读取 Android 系统凭据。我们直接把自建的 Root CA 的公钥证书追加到 Termux 的系统证书堆栈中:1
curl -k https://100.64.0.1/rootCA.crt >> /data/data/com.termux/files/usr/etc/tls/cert.pem
追加后,手机上的
curl可以无警告地通过 HTTPS 验证。Node/Bun 的环境变量拦截:由于 Termux 下的 Bun 并非标准的 Linux 发行版编译,即使更新了系统
cert.pem,它依然会因为 leaf 证书校验不匹配抛出UNABLE_TO_VERIFY_LEAF_SIGNATURE。
我们必须为 Node 和 Bun 进程注入NODE_EXTRA_CA_CERTS环境变量指明 CA 路径。但是我们又绝对不想因为局部的手机运行环境而弄脏全局托管的
.dotfiles配置文件。最优雅的做法是利用
/usr/etc/profile.d/系统目录(该目录下的脚本会被 Termux 的/etc/profile自动在所有 Shell 启动时执行,且完全独立于个人的.dotfiles仓库):在手机上创建
/data/data/com.termux/files/usr/etc/profile.d/omp-tls.sh:1
export NODE_EXTRA_CA_CERTS=/data/data/com.termux/files/usr/etc/tls/cert.pem
新开终端会话,完美!
Bun works! Status: 200!
终极体验:全屏协同,彻底告别 tmux!
现在,在电脑上运行 OMP 并输入 /collab,OMP 会生成一个极简的 WSS 链接:wss://collab.tailnet.cagedbird.cn/r/xxxx.xxxx
以及一个 Web 浏览器链接:collab.tailnet.cagedbird.cn/#collab.tailnet.cagedbird.cn/r/xxxx.xxxx
在手机上打开 Termux,直接 omp join 连上。
协作开启的那一瞬间,体验震撼至极:
- 真正的 P2P 直连延迟:由于走的是 WireGuard 直接打洞通道,字符流的传输完全是在内网直连中飞驰,延迟低于 20ms(同局域网下低于 1ms)。操作感如丝般顺滑。
- 独立多维排版:手机屏幕自动按照 Termux 的纵向屏幕宽度自适应排版,电脑端按照宽屏渲染。没有了传统终端共享中由于屏幕尺寸不同导致的折行、拉伸和黑边。你可以用大屏电脑从容写代码,同时在手机上随时随地查看最新的 AI 滚动输出并随时接手编写。

这种将点对点网络物理链路(Tailscale)、自建 Root CA 证书验证、高性能前端 Web 容器(Bun/React)以及系统级特权机制(Systemd AmbientCapabilities)融合到极致的私有协同生态,或许才是黑客眼中最酷的移动编程终极形态。