# EternalTerminal (ET) 集成清单 — 按 Mosh 方式重做

> 目标：在上游最新架构（分支 `feat/et-history-reapply`，基于 `031bf0ee`）上，
> **完全照搬 Mosh 的方式**重新集成 EternalTerminal：
> 1. **打包客户端** —— 像 `mosh-client` 那样，把 `et` 客户端二进制构建 + 下载 +
>    捆绑进安装包，运行时只用捆绑的二进制（不依赖系统安装的 et）。
> 2. **接入协议** —— 把旧分支 `feat/eternal-terminal`（tip `67e81616`）里的 ET
>    后端 + UI 重新落到上游重构后的目录结构上，并让它启动**捆绑的** `et`。
>
> 旧实现参考：`git show 67e81616`（共 7 个 ET 提交，见 `feat/eternal-terminal`）。
> Mosh 模板参考：`resources/mosh/README.md`、`scripts/*mosh*`、
> `electron/bridges/terminalBridge/moshSession.cjs`、`.github/workflows/build-mosh-binaries.yml`。

## 关键设计差异（ET vs Mosh）

- **协议**：Mosh 需要 Node 重写 Perl 包装器（SSH bootstrap + 抓 `MOSH CONNECT` +
  换 PTY）。**ET 不需要** —— `et` 客户端自己完成 SSH 引导 + 协议握手，我们只要
  把 `et` 当作普通 PTY 进程 `pty.spawn` 即可。所以**没有** `etHandshake.cjs`。
- **凭证注入**：Mosh 自己驱动 ssh、直接往 PTY 里敲密码；ET 内部驱动 ssh，需用
  **SSH_ASKPASS + 临时 ~/.ssh 环境**把保存的密码/密钥/跳板/算法喂给 et 内部的 ssh
  （旧实现 `prepareEtSshEnvironment` 已完整实现，直接搬运）。
- **terminfo**：`et` 是纯传输客户端、本地不渲染终端，**无需** 捆绑 terminfo
  （Mosh 因静态 ncurses 才需要）。打包目录里只放 `et[.exe]`（+ Windows DLL）。
- **构建系统**：Mosh 用 autotools；**ET 用 CMake + Ninja + vcpkg**
  （`cmake -DDISABLE_TELEMETRY=ON -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo`），
  产物是单个 `et`（Windows `et.exe`）。

## 命名约定（镜像 Mosh）

| Mosh | ET |
|------|----|
| `resources/mosh/<plat-arch>/mosh-client[.exe]` | `resources/et/<plat-arch>/et[.exe]` |
| 打包后 `<Resources>/mosh/mosh-client` | 打包后 `<Resources>/et/et` |
| `scripts/build-mosh/` | `scripts/build-et/` |
| `scripts/fetch-mosh-binaries.cjs` | `scripts/fetch-et-binaries.cjs` |
| `scripts/resolve-mosh-bin-release.cjs` | `scripts/resolve-et-bin-release.cjs` |
| `scripts/mosh-extra-resources.cjs` | `scripts/et-extra-resources.cjs` |
| env `MOSH_BIN_RELEASE` / 仓库 `Netcatty-mosh-bin` / tag `mosh-bin-*` | env `ET_BIN_RELEASE` / 仓库 `Netcatty-et-bin` / tag `et-bin-*` |
| `npm run fetch:mosh[:dev]` | `npm run fetch:et[:dev]` |
| `bundledMoshClient()` / `resolveBareMoshClient()` | `bundledEtClient()` / `resolveBareEtClient()` |
| `.github/workflows/build-mosh-binaries.yml` | `.github/workflows/build-et-binaries.yml` |

---

## Phase 1 — 打包基础设施（构建/下载/捆绑）

- [x] **1.1** `resources/et/README.md` —— 镜像 `resources/mosh/README.md`：说明
      二进制来源、`Netcatty-et-bin` 发布仓库、`et-bin-*` tag、许可证（ET 为
      Apache-2.0，与 GPL-3.0 兼容）、可复现构建命令。
- [x] **1.2** `.gitignore` —— 追加 ET 段（镜像 mosh 段）：
      `/resources/et/*/et`、`/resources/et/*/et.exe`、`/resources/et/*/*.dll`、
      `/resources/et/*/et-win32-*-dlls/`。保留 `resources/et/README.md`。
- [x] **1.3** `scripts/build-et/build-linux.sh` —— manylinux2014 + vcpkg 静态三元组
      构建 `et`（x64/arm64），产物 `et-linux-<arch>.tar.gz`(+.sha256)，内含单个 `et`。
      校验非系统动态库（同 mosh 的 ldd 白名单）。
- [x] **1.4** `scripts/build-et/build-macos.sh` —— arm64 + x86_64 分别构建后 `lipo`
      成 universal，`MACOSX_DEPLOYMENT_TARGET=11.0`，产物 `et-darwin-universal.tar.gz`。
- [x] **1.5** `scripts/build-et/build-windows.ps1`（或 `.sh`）—— MSVC + vcpkg
      `x64-windows-static`，产物 `et-win32-x64.tar.gz`（含 `et.exe`；若动态链接 CRT
      则随附 DLL 目录 `et-win32-x64-dlls/`，否则纯静态无 DLL）。
- [x] **1.6** `scripts/et-extra-resources.cjs` —— 镜像 `mosh-extra-resources.cjs`：
      按平台/arch 仅当 `resources/et/<plat-arch>/et[.exe]` 存在时才产出 extraResources
      指令（`to: "et/"`）；Windows 额外处理可选 DLL 目录。**去掉 terminfo 分支**。
- [x] **1.7** `scripts/resolve-et-bin-release.cjs` —— 镜像 `resolve-mosh-bin-release.cjs`：
      `TAG_RE=/^et-bin-.../`，默认仓库 `Netcatty-et-bin`，env `ET_BIN_RELEASE` 优先。
- [x] **1.8** `scripts/fetch-et-binaries.cjs` —— 镜像 `fetch-mosh-binaries.cjs`：
      `TARGETS` 四项（linux-x64/arm64、darwin-universal、win32-x64），全部 tar.gz；
      SHA256SUMS 校验；解包到 `resources/et/<plat-arch>/`。**Windows 用自建产物**
      （ET 官方有 Windows 构建，无需 FluentTerminal 那种 fallback）。去掉 terminfo 校验。
- [x] **1.9** 单元测试：`scripts/fetch-et-binaries.test.cjs`、
      `scripts/resolve-et-bin-release.test.cjs`、`scripts/et-extra-resources.test.cjs`
      （镜像对应 mosh 测试，改名/改路径）。
- [x] **1.10** `package.json` scripts：新增
      `"fetch:et": "node scripts/fetch-et-binaries.cjs"`、
      `"fetch:et:dev": "node scripts/fetch-et-binaries.cjs --host --resolve-release"`；
      把 `dev` 脚本改成先 `fetch:mosh:dev && fetch:et:dev`；`test` glob 已覆盖
      `scripts/*.test.cjs`（确认即可）。
- [x] **1.11** `electron-builder.config.cjs`：引入 `etExtraResources`，在 darwin/win32/
      linux 三处把 `etExtraResources(plat)` 合并进 `extraResources`（与 mosh 数组拼接）。
- [x] **1.12** `.github/workflows/build-et-binaries.yml` —— 镜像
      `build-mosh-binaries.yml`：四个构建 job + 一个 `release` job（dispatch 且
      `release_tag` 非空时发布到 `Netcatty-et-bin`，附 `SHA256SUMS`）。`paths` 过滤
      指向 `scripts/build-et/**`、`scripts/fetch-et-binaries.cjs`、`scripts/et-extra-resources.cjs`。
      env 用 `ET_REF`（默认 ET release tag，如 `et-v6.2.x`）。
      > 注：实际二进制由用户手动 `workflow_dispatch` 触发产出；本地/CI 未设
      > `ET_BIN_RELEASE` 时 fetch 步骤安静跳过（同 mosh）。

## Phase 2 — 运行时定位捆绑客户端

- [x] **2.1** `electron/bridges/terminalBridge.cjs` 新增 `bundledEtClient(opts)`
      —— 镜像 `bundledMoshClient`：打包路径 `<Resources>/et/et[.exe]`；dev 回退
      `<projectRoot>/resources/et/<plat-arch>/et[.exe]`；导出到 module.exports。

## Phase 3 — ET 协议后端（搬运旧实现到新架构）

- [x] **3.1** 新建 `electron/bridges/terminalBridge/etSession.cjs` —— 用上游
      `moshSession.cjs` 的 `createXxxSessionApi(ctx)` + `with(ctx)` 工厂模式，封装：
      `ET_ASKPASS_SCRIPT`、`writeSecureFile`、`prepareEtSshEnvironment`、
      `createEtAskpassArtifacts`、`cleanupStaleEtTempDirs`、
      `cleanupSessionExternalAuthArtifacts`、`execOnEtSession`、`startEtSession`。
      **改动点**：`etCmd` 由 `findExecutable('et')` 改为 `resolveBareEtClient()`
      （取捆绑二进制）；找不到时抛错（同 mosh：提示跑 `npm run fetch:et:dev`）。
      Windows 若有 DLL 目录，复用 `prependEnvPath` 思路把 DLL 目录加进 PATH。
- [x] **3.2** `terminalBridge.cjs` 接线 `createEtSessionApi(ctx)`（镜像 moshSessionApi
      的 ctx），传入 `bundledEtClient`、`tempDirBridge`、`execFile/execFileSync` 等；
      解构出 `startEtSession`、`execOnEtSession`、`cleanupStaleEtTempDirs`、
      `cleanupSessionExternalAuthArtifacts`、`resolveBareEtClient`。
- [x] **3.3** `init()` 调 `cleanupStaleEtTempDirs()`；`registerHandlers` 加
      `ipcMain.handle("netcatty:et:start", startEtSession)`；`closeSession` 与
      `cleanupAllSessions` 调 `cleanupSessionExternalAuthArtifacts(session)`；
      `module.exports` 导出 `startEtSession`、`execOnEtSession`、`bundledEtClient`。
- [x] **3.4** 测试：`terminalBridge.bundledEt.test.cjs`（路径解析）+
      `terminalBridge/etSession.test.cjs`（prepareEtSshEnvironment 的端口/密钥/
      askpass/跳板/legacy 算法分支）。可参考旧分支是否已有 ET 测试并搬运。

## Phase 4 — domain / 类型 / preload 接口面

- [x] **4.1** `domain/models.ts`：`HostProtocol` 加 `'et'`；`ProtocolConfig.etPort?`；
      `Host`/`GroupConfig` 加 `etEnabled?`/`etPort?`/`etTerminalPath?`；
      `TerminalSession.etEnabled?`；`ConnectionLog.protocol` 加 `'et'`。
      （照搬 `git show 794eecdf -- domain/models.ts`）
- [x] **4.2** `domain/groupConfig.ts`：加 `etEnabled` 默认项（照搬旧 diff）。
- [x] **4.3** `global.d.ts`：`NetcattyBridge` 加 `startEtSession?(options): Promise<...>`
      及相关 options 类型（照搬 `git show 794eecdf -- global.d.ts`，并补齐后续 ET 提交
      新增的 etPort/terminalPath/jumpHosts/legacyAlgorithms 字段）。
- [x] **4.4** `electron/preload/api.cjs`：加 `startEtSession`（镜像第 26 行的
      `startMoshSession`）→ `ipcRenderer.invoke("netcatty:et:start", options)`。
      **注意**：上游已把 preload 重构成 `createPreloadApi`，落点在 `preload/api.cjs`，
      不是旧的 `preload.cjs` 内联对象。

## Phase 5 — 渲染层 + UI + i18n

- [x] **5.1** `application/state/useTerminalBackend.ts`：加 `etAvailable`（查
      `bridge?.startEtSession`）+ `startEtSession`，并在返回对象/依赖数组里登记
      （镜像 mosh 的第 10/42/198/205 行处）。
- [x] **5.2** `application/state/useSessionState.ts`：路由 ET 会话（照搬旧 diff，+6 行）。
- [x] **5.3** `components/terminal/runtime/createTerminalSessionStarters.ts`：加
      `startEt(term)`（镜像 `startMosh`，组装 options：etPort/terminalPath/
      jumpHosts/legacyAlgorithms/凭证/identityFilePaths）。
      **注意**：上游把它从旧的 `infrastructure/runtime/` 移到了
      `components/terminal/runtime/` —— 落点以上游为准。
- [x] **5.4** UI 组件（照搬 `git show b1a306f8 6c0d5bf3 55caa268` 的相应文件，
      映射到上游同名组件）：
      - [ ] `components/ProtocolSelectDialog.tsx` —— 新增 ET 选项
      - [ ] `components/QuickConnectWizard.tsx`
      - [ ] `components/HostDetailsPanel.tsx` —— ET 设置（启用、ET 端口、etterminal 路径）
      - [ ] `components/GroupDetailsPanel.tsx`
      - [ ] `components/VaultView.tsx`
      - [ ] `components/Terminal.tsx` / `components/TerminalLayer.tsx`
      - [ ] `components/terminal/TerminalConnectionDialog.tsx` / `TerminalToolbar.tsx`
      - [ ] `App.tsx`
- [x] **5.5** i18n：`application/i18n/locales/en.ts` 与 `zh-CN.ts` 加 ET 文案
      （照搬旧 diff，键名对齐上游现有 mosh 文案结构）。

## Phase 6 — 校验

- [x] **6.1** `npm run lint`（确保新 .cjs 在 scripts/ 下不受 ESLint 限制，
      或按需加 eslint-disable，与 mosh 脚本一致）。
- [x] **6.2** `npm test`（新增的 fetch/resolve/extra-resources/etSession 测试全绿）。
- [x] **6.3** `npm run build`（渲染层 TS 编译通过，无类型错误）。
- [ ] **6.4** 手动冒烟（需先有发布的二进制）：
      `ET_BIN_RELEASE=et-bin-... npm run fetch:et` → `npm run start` →
      新建 ET 会话连一台装了 etserver 的主机，验证连接/输入/退出/凭证注入。

---

## 进度记录

- 状态：**Phase 1–5 已完成并通过校验**（仅余 1 个可选项 + CI 产二进制）
- 验证结果：
  - `npx eslint <所有改动文件>` → 干净（0 错 0 警）
  - `npx tsc --noEmit` → 我的改动 **0 个新增类型错误**
    （`TerminalConnectionDialog` 里 `case 'mosh'` 的 TS2678 是既有问题，行号因我插入 ET 早返回从 60→64，非新增）
  - `node --test`（ET 相关）→ etSession/bundledEt/3 个脚本测试 **全绿**
  - `npm test` → 1383 通过 / 16 失败，**16 个全是既有的 Windows 环境失败**
    （mosh 打包测试的 GNU-tar `C:` 问题、`isExecutableFile` 无 x 位、ACP execPath、SKILL.md 权限、Comware DH 等；均在我未改动的文件里）
  - `npm run build`（Vite）→ **构建成功**（8.55s），渲染层打包通过

### 已完成
- **Phase 1**：`scripts/et-extra-resources.cjs` / `resolve-et-bin-release.cjs` /
  `fetch-et-binaries.cjs`（+3 测试，27 通过）、`scripts/build-et/{build-linux.sh,
  build-macos.sh,build-windows.ps1}`、`.github/workflows/build-et-binaries.yml`、
  `resources/et/README.md`、`.gitignore`、`package.json`、`electron-builder.config.cjs`。
- **Phase 2**：`terminalBridge.cjs` 新增并导出 `bundledEtClient`。
- **Phase 3**：`terminalBridge/etSession.cjs`（startEtSession + prepareEtSshEnvironment +
  SSH_ASKPASS 机制 + execOnEtSession + 清理），接线进 terminalBridge.cjs（ctx/IPC
  `netcatty:et:start`/init 清理/close/quit 清理/导出），+2 测试（13 通过）。
  **et 指向捆绑二进制**（resolveBareEtClient→bundledEtClient），找不到则报错。
- **Phase 4**：domain `connection.ts`/`history.ts`/`terminal.ts`、`groupConfig.ts`、
  `types/global/netcatty-bridge-session.d.ts`（startEtSession + NetcattyJumpHost[]）、
  `electron/preload/api.cjs`、`domain/vaultImport.ts`（排除 'et' 导入协议）。
- **Phase 5**：
  - 启动派发：`useTerminalEffects.ts`、`Terminal.tsx`(×3) → `startEt`
  - 运行时 starter：`createTerminalSessionStarters.ts` 新增 `startEt`（含单跳板/凭证/
    legacy 算法/askpass 路径），`.types.ts` 加 `etAvailable`/`startEtSession`
  - 后端 hook：`useTerminalBackend.ts`（etAvailable + startEtSession）
  - 会话透传 etEnabled：`sessionFactories.ts`、`useSessionState.ts`(×6)、
    `TerminalLayer.tsx`(×3)、`TerminalLayerSupport.tsx`、`AppHandlers.ts`(协议解析/日志/选择)
  - UI：`HostDetailsAdvancedSections.tsx`（ET 开关+端口+etterminal 路径，与 Mosh 互斥）、
    `HostDetailsPanel.tsx`、`ProtocolSelectDialog.tsx`（ET 选项）、
    `TerminalConnectionDialog.tsx`（ET 标签）、`TerminalToolbar.tsx`（编码菜单门控）、
    `GroupSshSettingsSection.tsx` + `GroupDetailsPanel.tsx`（组级 ET）、`VaultView.tsx`
  - i18n：en/zh-CN 的 `hostDetails.section.et`、`hostDetails.et.*`、
    `terminal.connection.protocol.et`、`terminal.et.*`

### 剩余（可选 / 非阻塞）
- [ ] **QuickConnectWizard.tsx**：把 ET 加为“快速连接”协议按钮（type/端口/建主机映射 +
      UI 按钮）。当前快速连接未列 ET；保存主机后开启 ET 再连即可，故仅为便利项。
- [ ] **产出二进制**：手动 `workflow_dispatch` 跑 `build-et-binaries.yml`（带
      `release_tag=et-bin-<ver>-1`）发布到 `Netcatty-et-bin`，并配 `ET_BIN_RELEASE_TOKEN`
      secret。之后 `ET_BIN_RELEASE=... npm run fetch:et` 即可本地/打包捆绑 `et`。
      build-et 脚本本机无法编译 C++，需在 CI 验证。
- [ ] **端到端冒烟**：有二进制后 `npm run dev`，对装有 etserver 的主机建 ET 会话验证。

- 当前分支：`feat/et-history-reapply`（基于上游 `031bf0ee`）
- 旧 ET 实现参考分支：`feat/eternal-terminal`（tip `67e81616`，7 个 ET 提交）
