docs: 刷新交接文档至 M4 完成态(供无上下文新会话续接)
- HANDOFF: 修正 git 说明(6 commits/artifacts 有意提交)、重排 §7 下一步(真机化为首要缺口)、 更新单测计数(50)、补 M4/tmux resize 代码地图 - CLAUDE.md: 单测计数 50、git 状态 - README: 全面重写——M0~M4 进展、实际包结构(TXCore/TXTransport/app,澄清计划包未成形)、 artifacts 有意提交、指向 HANDOFF/CLAUDE Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ iOS/iPadOS 只做远程(沙盒禁 fork/exec);macOS 支持本地 PTY。
|
||||
|
||||
```sh
|
||||
# 纯逻辑单测(无需 Xcode/模拟器)
|
||||
swift test --package-path packages/TXCore # 35 tests
|
||||
swift test --package-path packages/TXCore # 50 tests (40 swift-testing + 10 XCTest)
|
||||
|
||||
# 重建 SSH 库 xcframework(libssh2+mbedTLS,改 vendor/libssh2|mbedtls 后)
|
||||
bash vendor/build/build-cssh.sh all # 产出 artifacts/CSSHCore.xcframework (library-only!)
|
||||
@@ -83,6 +83,6 @@ sleep N; xcrun simctl io $UDID screenshot /tmp/x.png # 然后用 Read 工具
|
||||
- **tmux 网关字节必须单消费者保序**:多个 `Task{@MainActor}` 到主线程顺序无保证,多 pane 高吞吐会打碎 % 协议 → 用 `TmuxByteChannel`(AsyncStream) 单消费者。
|
||||
- SourceKit "No such module" 是索引噪声,**以 `swift build`/`xcodebuild` 为准**。
|
||||
- 密钥/密码**绝不写进仓库**;测试主机凭据由用户即时提供。
|
||||
- 提交/推送 git 只在用户明确要求时(当前已 `git init`,尚无 commit)。
|
||||
- 提交/推送 git 只在用户明确要求时(main 分支已有 6 次提交,未 push、无 remote)。
|
||||
- 与用户始终用**简体中文**交流。
|
||||
</content>
|
||||
|
||||
57
README.md
57
README.md
@@ -4,33 +4,50 @@
|
||||
|
||||
核心选型:**libghostty**(终端引擎) · **mosh** · **Tailscale (tsnet 用户态)** · **tmux control mode hook**。
|
||||
|
||||
> 设计与决策见 `docs/`(若缺失,参见会话记忆 / 架构蓝图)。当前处于 **M0**(骨架 + 引擎 + libssh2 直连),走 SSH-first 路线。
|
||||
> **接手/继续开发先读 [`docs/HANDOFF.md`](docs/HANDOFF.md)**(项目全貌 + 里程碑 + 代码地图 + 下一步)与 [`CLAUDE.md`](CLAUDE.md)(构建/验证命令 + 环境约束 + 坑)。
|
||||
|
||||
## 仓库结构
|
||||
## 当前进展(2026-07-24,均 iOS 模拟器验证,未上真机/未签名)
|
||||
|
||||
- **M0** SSH 直连终端(libghostty 渲染/键盘/CJK、会话状态机+自动重连)✅
|
||||
- **M1** tsnet 用户态 + SSH-over-tsnet(fd 桥)✅
|
||||
- **M1.5 / tmux** control-mode 原生 tab + **多 pane 分屏 + iPadOS 动态 resize** ✅
|
||||
- **M2** mosh over tsnet(自编 `MoshCore.xcframework` + UDP relay)✅ 端到端验证
|
||||
- **M3** mosh 挂起→恢复 <3s(唤醒脉冲 + 会话状态机三相)✅
|
||||
- **M4** 安全:host key TOFU pin(MITM 防护)+ publickey 认证(SE 优先/软件回退)✅
|
||||
- **M5** 合规提审 ⏸️ 暂缓
|
||||
|
||||
四大核心需求(libghostty / tailscale / tmux / mosh)均已端到端验证。
|
||||
|
||||
## 仓库结构(实际)
|
||||
|
||||
```
|
||||
packages/ 纯 Swift SPM 包(源码构建,依赖只向下)
|
||||
TXCore 零依赖:模型 / 状态机 / 协议 / 纯算法(含 tmux control-mode 解析器)
|
||||
TXTransport SSH(libssh2) 引导 · mosh 驱动 · UDP relay · 连接编排 [M1+]
|
||||
TXTailnet tsnet 多节点 · DialerRouter · fd 桥 [M1+]
|
||||
TXTerminal GhosttyKit 包装:Metal 渲染 · 键盘 · IME [M0]
|
||||
TXSecurity Keychain / Secure Enclave / known_hosts [M4]
|
||||
TXPTY 本地 PTY(仅 macOS) [M4]
|
||||
TXUI SwiftUI 界面层 [M0+]
|
||||
apps/ Xcode app targets(XcodeGen 生成,iOS/iPadOS 合一 + macOS 独立)
|
||||
vendor/ 非 Swift 语言源(zig/go/c++),CI 产出 xcframework [M1+]
|
||||
artifacts/ 预编译 xcframework 落地(gitignore)
|
||||
packages/
|
||||
TXCore 零依赖纯逻辑:tmux 解析器/layout、SessionMachine(状态机+重连+mosh三相)、
|
||||
MoshConnect(解析)、SSH(线格式/ECDSA/host key)。50 单测。
|
||||
TXTransport SSHSession(libssh2)、MoshSession(mosh_main 桥)、Transport 协议;
|
||||
C shim: CSSH+CSSHCore(libssh2)、CMosh+MoshCore(mosh)。
|
||||
apps/TerminalX iOS app(XcodeGen 生成):SSHTerminalModel(编排)/ContentView/
|
||||
TmuxController(pane-per-surface 网关)/SigningKeyProvider/KeychainKnownHostsStore。
|
||||
vendor/ 非 Swift 依赖本地化(认证代理不支持 SwiftPM,全 vendor):
|
||||
libghostty-spm、libssh2、mbedtls、tsnet-bridge(Go)、mosh、protobuf。
|
||||
artifacts/ 预编译 xcframework(CSSHCore/TsnetBridge/MoshCore,**有意提交**,克隆即可构建)。
|
||||
```
|
||||
|
||||
## 开发
|
||||
> 计划中的 `TXTailnet/TXSecurity/TXPTY/TXUI` 未单独成包——tsnet 逻辑在 app 的 `TsnetManager`、
|
||||
> 安全在 app 的 `SigningKeyProvider`/`KeychainKnownHostsStore`、UI 直接在 app。macOS 本地 PTY 未建。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```sh
|
||||
# M0:纯逻辑包,命令行即可测,无需 Xcode 工程
|
||||
cd packages/TXCore && swift test
|
||||
# 纯逻辑单测(无需 Xcode/模拟器)
|
||||
swift test --package-path packages/TXCore # 50 tests
|
||||
|
||||
# 构建 app(模拟器)——详见 CLAUDE.md
|
||||
cd apps/TerminalX && xcodegen generate --spec project.yml
|
||||
xcodebuild -project TerminalX.xcodeproj -scheme TerminalX \
|
||||
-destination 'platform=iOS Simulator,id=<UDID>' -clonedSourcePackagesDirPath /tmp/tx-spm build
|
||||
```
|
||||
|
||||
## 工具链要求
|
||||
## 工具链
|
||||
|
||||
- Xcode 26+ / Swift 6.x(M0)
|
||||
- Go + gomobile(M1,tsnet)
|
||||
- CMake + Ninja + zig(M1/M2,vendor 构建)
|
||||
Xcode 26 / Swift 6.3 · Go 1.26(`~/.local/go`)+gomobile · CMake+Ninja+zig(vendor 构建)。构建命令与环境约束见 `CLAUDE.md`。
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## 0. 一句话现状
|
||||
一个**原生 iPad SSH 终端**已可用并真机验证:libghostty 渲染 + 键盘输入 + SSH 直连 + **SSH-over-tsnet(Tailscale 用户态)** + **tmux -CC 原生 tab + 多 pane 分屏** + **mosh(LAN + over-tsnet 均已端到端验证)** + 断线自动重连 + **mosh 挂起→恢复<3s(M3)**。四大核心需求(libghostty/tailscale/tmux/**mosh 均已验证**)。仅在 iOS 模拟器验证(未上真机/未签名)。
|
||||
> **mosh 状态(2026-07-24 全部验证通过)**:mosh(C++,blinksh/ios)+protobuf-lite 交叉编译成 `MoshCore.xcframework`(CommonCrypto 后端,含 arm64 模拟器 slice);tsnet 桥加 UDP relay(`StartMoshRelay`);`MoshSession`(pipe↔FILE*/pthread/SIGWINCH)+`MoshConnectScanner`(TXCore,39 单测)+`SSHTerminalModel` 编排(SSH 引导 mosh-server→解析 MOSH CONNECT→接管)。
|
||||
> **mosh 状态(2026-07-24 全部验证通过)**:mosh(C++,blinksh/ios)+protobuf-lite 交叉编译成 `MoshCore.xcframework`(CommonCrypto 后端,含 arm64 模拟器 slice);tsnet 桥加 UDP relay(`StartMoshRelay`);`MoshSession`(pipe↔FILE*/pthread/SIGWINCH)+`MoshConnectScanner`(TXCore)+`SSHTerminalModel` 编排(SSH 引导 mosh-server→解析 MOSH CONNECT→接管)。
|
||||
> - **LAN 直连端到端验证**(192.168.9.199):mosh-server 存活 104s≫60s 无客户端超时+子 shell+心跳=真连。
|
||||
> - **mosh over tsnet 端到端验证**(vohive-vm 100.69.201.101):app 日志 `parsed MOSH CONNECT port=60002`→`relay up localPort=63542`→`activateMosh started`;屏幕渲染远端 shell 且无 mosh 断连 overlay=relay UDP 双向流通。**R1(tsnet UDP 数据报语义) 证伪**。
|
||||
> - 复现 tsnet:模拟器已持久化 tsnet 节点状态(`~/Library/.../tsnet-main`),`-txTsnetKey` 传任意非空串即可凭存量身份重连(节点未被 tailnet 清理时);节点被清理则需新 ephemeral key。
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
| 里程碑 | 状态 | 真机实证 |
|
||||
|---|---|---|
|
||||
| **M0** SSH 直连终端 | ✅ 完成 | libghostty 渲染(SGR/CJK)、键盘输入、连接;**白屏 bug 修复**(OutputGate);会话状态机+自动重连(31→含后加共 35 单测) |
|
||||
| **M0** SSH 直连终端 | ✅ 完成 | libghostty 渲染(SGR/CJK)、键盘输入、连接;**白屏 bug 修复**(OutputGate);会话状态机+自动重连(TXCore 单测总数见 §5=50) |
|
||||
| **M1** tsnet + SSH-over-tsnet | ✅ 完成 | app 内 tsnet 加入 tailnet(IP 100.74.60.108);对端枚举;**SSH-over-tsnet 登录 Ubuntu shell**(服务端 `Last login from 100.x` 证明走 tailnet) |
|
||||
| **M1.5** tmux -CC 原生 tab | ✅ MVP | 直连跑 `tmux -CC attach` → 渲染 fish/tmux 两原生 tab + 活动窗口内容 |
|
||||
| dial 重试打磨 | ✅ | `TsnetManager.dialFD` 6 次退避重试,解决 tsnet 首连路径预热超时 |
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
## 5. 代码地图与数据流
|
||||
- `packages/TXCore/Sources/TXCore/`
|
||||
- `Tmux/TmuxControlParser.swift` `TmuxEvent.swift` `TmuxIDs.swift` `TmuxOutputDecoder.swift` `TmuxLayout.swift` `TmuxControlSequence.swift`(DCS 检测)—— 纯解析,35 单测在 `Tests/`。
|
||||
- `Tmux/TmuxControlParser.swift` `TmuxEvent.swift` `TmuxIDs.swift` `TmuxOutputDecoder.swift` `TmuxLayout.swift` `TmuxControlSequence.swift`(DCS 检测)—— 纯解析。TXCore 全部单测共 **50**(40 swift-testing + 10 XCTest:tmux 解析/`SessionMachine`含mosh三相/`MoshConnect`/`SSHWire`),`swift test --package-path packages/TXCore` 全绿。
|
||||
- `Session/SessionMachine.swift` —— `reduce(event)->[Effect]` 会话状态机(指数退避重连/后台冻结/前台恢复)。**M3 加 mosh 三相** `moshActive/moshParked/moshResuming` + 事件 `moshEstablished/moshHealthy/moshExited/resumeWatchdogFired` + 效果 `nudgeResume/scheduleResumeWatchdog/teardownMosh`:mosh 接管即拆 SSH、后台冻结不重建、前台打唤醒脉冲等 SSP 续、两次脉冲无效兜底全量重建。
|
||||
- `packages/TXTransport/Sources/`
|
||||
- `TXTransport/Transport.swift`(协议+SSHConfig+SSHError)、`SSHSession.swift`(libssh2;`preconnectedFD` 支持外部 fd;事件循环 poll+非阻塞)。
|
||||
@@ -72,15 +72,21 @@
|
||||
- **tmux 多 pane 分屏 + 动态 resize**:✅ 已做(pane-per-surface + layout rect 绝对定位渲染 + 按 pane %output 路由 + tap 切焦点/select-pane + attach `refresh-client -C` 设尺寸 + **iPadOS 拖拽窗口/旋转 → `onChange(geo.size)`→`containerResized`(点×displayScale÷cell像素)→debounce→refresh-client -C→%layout-change→更新 frame**,验证:容器 1032x1280@2x cell16x35→129x73,tmux 精确采纳)。**剩余**:完整 scrollback(仍靠 capture-pane 抓当前屏);pause 流控(`%pause/%continue`,防单 pane 刷屏);split/kill pane 手势;tmux<3.1 降级路径未做。
|
||||
- **tsnet 首连**:已加 dial 重试;更优是等 peer online 再 dial(读 PeersJSON online 状态)。
|
||||
- **签名/真机**:`CODE_SIGNING_ALLOWED=NO`,仅模拟器;上真机/TestFlight 需配置签名 + entitlements(tsnet 用户态**无需** NE entitlement,利好审核)。
|
||||
- **git**:已 `git init`,**尚无 commit**;`artifacts/*.xcframework` 已 gitignore(需 `make`/脚本重建或从 Release 拉)。
|
||||
- **git**:main 分支已有 6 次提交(初始 + M2/M3/tmux多pane/tmux resize/M4a/M4b),工作区干净,**未 push 远端**(无 remote)。commit/push 只在用户明确要求时。`artifacts/*.xcframework`(`CSSHCore`/`TsnetBridge`/`MoshCore`)+ `vendor/mosh`+`vendor/protobuf` 源码**均有意提交**(克隆即可构建,无需网络);构建中间产物(`vendor/build/mosh-out`、`vendor/protobuf/build-*`)已 gitignore。
|
||||
- **多平台**:iOS target 已跑;macOS target(本地 PTY, `TXPTY`)未建。
|
||||
|
||||
## 7. 下一步建议(按价值)
|
||||
1. **M2 mosh 打磨**(功能已通,打磨项):`activateMosh` 写死 80x24 初值(靠首次 resize 纠正);SSH 会话在 mosh 接管后保持 idle 未关(占一条连接);`MoshSession.close()` 后 mosh 线程可能滞留(阻塞主循环,见 TODO);moshiosbridge.cc 顶部 `fwrite("Hello from the Bridge!")` 调试行可清理;`SSHTerminalModel` 里 `NSLog("MOSHDBG …")` 诊断日志可按需保留/删除;mosh-server 引导后远端会累积 detached 会话(`mosh-server new` 每次新建),可考虑复用或清理。**挂起→恢复<3s 门(M3)待专门测**。
|
||||
3. **M3 真机深挂起补测**:模拟器不复现 socket defunct、且看门狗会杀掉被 `kill -STOP` 冻结过久的 app(>~30s 概率被杀),故 WG 密钥过期(>180s)/DERP 死链/socket defunct 路径需**真机锁屏数分钟**用例补测(`MoshRelay.Rebind`/`hop_port` 的正确性靠代码 + 真机)。
|
||||
4. **tmux 多 pane 打磨**:pause 流控(`%pause/%continue` 防单 pane 刷屏);split/kill pane 的 UI 手势;外接键盘 `Cmd+Opt+方向` 导航 pane;完整 scrollback。(多 pane + 动态 resize 已做并验证。)
|
||||
5. **M4 安全 + 冷启动 mosh 重连**:known_hosts 固定 + SE 密钥;mosh 进程死后重连原 detached 会话需持久化 MOSH_KEY+port 且改 mosh 序列化 SSP 序号/终端状态(Blink 式,大活)。
|
||||
5. **产品化**:主机列表持久化(Keychain)、多标签(非 tmux)、软键盘运维工具栏(GhosttyKit 内建,接线即可)。
|
||||
## 7. 下一步建议(M0~M4 + tmux 多pane/resize 均已完成并验证;以下按价值)
|
||||
1. **真机化(最大缺口,多项验证只在模拟器)**:配置签名 + entitlements(tsnet 用户态**无需** NE,利好审核)→ 上真机/TestFlight。真机上补验三件模拟器测不了的:①**SE 硬件密钥**生成+签名+keychain 持久化(M4b 现回退软件+文件;`SigningKeyProvider` 已备 SE 路径);②**mosh 深挂起恢复**(锁屏数分钟过 WG 180s 过期线 / socket defunct,验 `MoshRelay.Rebind`+`hop_port`);③签名后 **Keychain 可用**(当前未签名 SecItem 全 -34018,host key pin 才回退到文件)。
|
||||
2. **产品化**:主机列表持久化(签名后用 Keychain 存凭据/host 配置)、多账号多 tsnet 节点(架构决策 §2.6:每 host 绑 egress,禁多 tailnet 盲探)、软键盘运维工具栏(GhosttyKit 内建,接线即可)、多标签(非 tmux)。
|
||||
3. **打磨(可选,不阻塞)**:
|
||||
- 清理诊断埋点:`SSHTerminalModel`/`TmuxController`/`iosclient.cc` 的 `NSLog("MOSHDBG/TXM3/TMUXDBG/M4DBG …")` + moshiosbridge.cc 的 `fwrite("Hello from the Bridge!")`。
|
||||
- mosh:`activateMosh` 写死 80x24 初值(靠首次 resize 纠正);mosh 接管后 idle SSH 未关;`MoshSession.close()` 后 mosh 线程可能滞留;远端累积 detached mosh-server(每次 `mosh-server new`)需清理策略。
|
||||
- tmux:pause 流控(`%pause/%continue` 防单 pane 刷屏);split/kill pane 手势;外接键盘 `Cmd+Opt+方向` 导航 pane;完整 scrollback(现靠 capture-pane 抓当前屏);tmux<3.1 降级路径。
|
||||
- M4:`libssh2_session_method_pref` 锁定 pinned hostkey 类型防算法漂移误报;host key pin 的 UI 管理(查看/删除已信任)。
|
||||
- tsnet 首连:更优是等 peer online 再 dial(读 PeersJSON online 状态)。
|
||||
4. **冷启动 mosh 重连(大活)**:mosh 进程被杀后接原 detached 会话——受 SSP 序号防重放限制,需持久化 MOSH_KEY+port 且改 mosh 序列化客户端状态(Blink 式)。价值中等,成本高。
|
||||
5. **macOS target**:本地 PTY(`TXPTY`)未建。
|
||||
6. **M5 合规提审**:用户指示暂缓。
|
||||
|
||||
## 8. 测试资源(用户提供,**凭据勿写入仓库/勿硬编码**,每次向用户索取)
|
||||
- 一台局域网 macOS(有 tmux + **mosh-server**,如 MacBook-Air-M1;fish 为默认 shell,`mosh-server` 在 `/opt/homebrew/bin` 且交互 shell PATH 可见)、一台 tailnet 内 online 的 Ubuntu VM(vohive-vm)、一个 tailnet auth key(ephemeral,可能过期,用户会给新的)。用于直连 SSH / SSH-over-tsnet / tmux / M2 mosh 端到端验证。凭据每次向用户即时索取。
|
||||
|
||||
Reference in New Issue
Block a user