13 · 远程连接与多客户端语义¶
Herdr 的远程模式不是把本地 TUI 包进 ssh -t。它在远端运行真正拥有 PTY/状态的 Herdr server,本地仍运行 client,通过 SSH bridge 传输与本地相同的 wire protocol。这样 Agent 和仓库都留在远端,输入、主题和屏幕设备留在本地。
13.1 远程拓扑¶
flowchart LR
subgraph Local["本地主机"]
UI["Herdr app client"]
SSH["SSH process / local forward"]
Cache["remote binary/install cache"]
end
subgraph Remote["远程主机"]
Bridge["herdr bridge over stdio/socket"]
Server["Herdr headless server"]
PTY["PTY + Agent + repo"]
API["remote session API socket"]
end
UI <-->|"wire protocol v20"| SSH
SSH <-->|"encrypted channel"| Bridge
Bridge <--> Server
Server <--> PTY
Server <--> API
Cache -."install exact compatible artifact".-> Bridge
主要实现集中在 src/remote/attach.rs 和 src/remote/host_unix.rs。
13.2 为什么不直接用远端 shell TTY¶
简单 ssh -t host herdr 有几个限制:
- UI 进程跟 SSH 连接同寿命,断线恢复弱;
- 本地/远端 Herdr 版本和协议不显式协商;
- 无法自然加入第二个 observer/client;
- 本地 terminal capabilities、theme、keyboard profile难以结构化传达;
- 远端 daemon 的更新/复用/命名 session 难管理;
- 文件传输和 bridge 失败难给出精确阶段。
显式 remote orchestration 把“连接一个 shell”升级为“发现并连接一个兼容服务”。
13.3 远程准备流程¶
sequenceDiagram
participant L as Local herdr
participant SSH as ssh
participant R as Remote shell
participant M as Release manifest
participant S as Remote server
L->>SSH: connect target + keepalive/control options
SSH->>R: probe uname / arch / known binary paths
R-->>L: platform + candidates + runtime status
L->>L: choose exact compatible binary/server plan
alt usable remote binary exists
L->>R: verify version/protocol/path
else needs install
L->>M: resolve exact asset + SHA-256
L->>R: transfer/download staged artifact
R->>R: verify/install executable
end
L->>S: reuse/start/stop-or-handoff plan
S-->>L: protocol/capabilities ready
L->>SSH: establish bridge/forward
L->>S: client handshake + attach
每一步都可能失败,所以实现保存阶段化诊断,而不是把所有错误压成 “ssh failed”。
13.4 平台探测与 asset key¶
远端先用 uname 等轻量命令确定 OS/arch,标准化示例:
未知 OS/架构应明确拒绝,不能下载“最接近”的 artifact。远端平台决定:
- 发布 manifest 的 asset key;
- 可执行文件格式/权限;
- daemon detach 与 Live Handoff capability;
- socket/SSH 转发方式。
13.5 发现真实二进制,而不是 shim¶
which herdr 可能返回 ~/.local/share/mise/shims/herdr。shim 依赖交互环境和版本选择,不能当作稳定的 server replacement/import executable。
远端发现会枚举:
- 显式
HERDR_REMOTE_BINARY; - 当前/标准 install paths;
- mise 的具体 version install 目录;
- Nix/其他已知布局;
- PATH 中非 shim 候选。
is_mise_shim_path 直接排除 shim。路径存在还不够,候选要能返回版本/protocol/status。
13.6 “相同版本”不如“相同协议”重要¶
固定快照使用 client protocol 20。远端二进制选择要满足当前 client 所需协议;发布 manifest 的 archived release metadata也保存各自 protocol,不能拿最新 release 的 protocol 填给旧 artifact。
candidate usable iff:
executable works
AND speaks required protocol
AND platform/arch matches
AND(安装来源时)checksum verified
若远端正在运行旧 server,但协议仍兼容,可以保留它;若不兼容,则根据 --handoff、remote capability 和用户策略选择 Live Handoff 或明确 stop。不能只因 semver 不同就杀掉活跃 Agent。
13.7 Stable 与 Preview 的精确 artifact¶
Stable manifest 提供版本、各平台 asset、protocol 和 SHA-256。Preview 还带 build id/commit,并保留 archived exact build:本地 preview client 应找到与自身 build/protocol 对应的远端 artifact,而不是无条件下载“今天最新 preview”。
这是可复现远程协议的关键。滚动 preview 如果只保留一个 URL,本地稍旧的客户端第二天连接新主机时就可能再也找不到兼容 server。
13.8 校验与安装¶
下载或传输后:
- 写 staging path;
- 对 stable artifact 要求 manifest 提供 SHA-256;
- 用
src/checksum.rs校验 64 位十六进制 digest; - 设置 Unix executable permission;
- 再作为 server/bridge candidate 运行 status probe;
- 失败不覆盖已有可用二进制。
传输加密不能代替 artifact 完整性。SSH 保护通道,checksum 证明实际文件与发布 manifest 一致。
13.9 SSH control 与 keepalive¶
频繁运行 probe、copy、bridge 时,每次重新认证成本高。Unix 路径使用 ControlMaster=auto 等连接复用选项;管理的 SSH config还设置:
实现尊重 OpenSSH “first value wins”,不覆盖用户显式 keepalive。Windows 管理配置保留 keepalive,但不强行使用 Unix control socket 语义。
配置文件权限必须限制为用户可读写,避免 SSH 拒绝或泄漏主机配置。
13.10 sun_path 长度约束¶
Unix domain socket 路径通常有很短的固定上限。把完整 user@very-long-host + session-name 拼进本地 forward path 会 bind 失败。
Herdr 计算短 hash,把 target/session identity 压缩到可验证长度,并有测试 local_forward_socket_path_fits_in_sun_path。
需要同时满足:
- 同 target/session 稳定;
- 不同组合碰撞概率可接受;
- 父目录权限安全;
- 字节长度而非字符数符合 OS 上限;
- stale socket 可验证后清理。
13.11 Bridge 的责任要薄¶
Bridge 主要在 SSH stdio 与远端/local socket 间双向 copy/flush。它不应复制 App state 或重新解释 frame:
- 协议握手仍由真正 client/server 完成;
- EOF 在任一方向传播,另一方向有界退出;
- partial write/Interrupted 正确重试;
- drop 时不无限等不存在的对端;
- 日志不能混入二进制 stdout 通道。
薄 bridge 让本地和远端共享同一 wire protocol 语义,减少第三套协议。
13.12 Remote server 决策矩阵¶
| 远端状态 | 协议 | binary | 计划 |
|---|---|---|---|
| 未运行 | — | 兼容 | 启动 server |
| 运行中 | 兼容 | 任意可用 | 直接 attach,通常不扰动 |
| 运行中 | 不兼容 | 新兼容 artifact | 若启用且支持则 Live Handoff |
| 运行中 | 不兼容 | 新兼容 artifact | 否则确认 stop 后替换 |
| status 不明 | 不明 | 任意 | 停止自动破坏动作,给诊断 |
| preview exact build 缺失 | 不明 | 无 | 要求显式 remote binary/手动安装 |
“状态不明”不能等同“未运行”;否则网络瞬断会触发错误 stop/install。
13.13 多客户端角色¶
远程只是 transport 变化,server 仍支持多 client:
| 角色 | 读取 frame | 写输入 | 改共享 PTY 尺寸 | 典型用途 |
|---|---|---|---|---|
| app foreground | 是 | 是 | 是 | 主交互 UI |
| app observer | 是 | 否 | 否 | 旁观/第二窗口 |
| direct terminal control | 目标流 | 有 lease 时 | 按协议 | 直接附着 pane |
| direct terminal observe | 目标流 | 否 | 否 | 日志/监看 |
一个 writable owner + 多 observer 避免网络两端同时向 shell 注入交错输入。takeover 是显式事件,并清理旧 input lease。
13.14 重连命令与 session identity¶
远端 attach 成功后,client 可得到可复现的 reattach 信息:target、命名 session、必要 remote flags。它不能只记临时 local forward socket,因为该路径在下次进程中可能重建。
重连关键是逻辑 identity:
临时 socket/control path 都是从它派生的实现细节。
13.15 失败边界¶
| 失败 | 期望行为 |
|---|---|
| SSH 认证失败 | 不创建/删除远端二进制,显示原 ssh 诊断 |
| 平台不支持 | 在下载前拒绝 |
| checksum mismatch | 删除/隔离 staging,不执行 |
| bridge 中断 | client 断线;远端 server/PTY 继续 |
| incompatible server handoff 失败 | 尽量确认旧 server 是否仍活,再询问 stop |
| forward socket stale | 探测 owner 后清理,不盲删活连接 |
| 本地窗口 resize storm | 合并尺寸消息,只有 foreground 改 PTY |
13.16 本章结论¶
- 远程模式延伸 server/client 架构,不是把整个 TUI 塞进 SSH TTY;
- 远端准备要验证平台、真实 binary、protocol 和 checksum;
- preview 必须保留 exact build artifact,才能复现兼容性;
- bridge 保持字节转发,业务语义仍由共享 wire protocol 承担;
- 多客户端的 foreground、lease、尺寸和 observer 规则在网络边界后仍不变。
下一章补齐产品外围但同样关键的三层:配置如何带诊断地局部加载,平台差异如何集中,self-update 又如何尊重 package manager 和活跃 server 所有权。