# 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//mosh-client[.exe]` | `resources/et//et[.exe]` | | 打包后 `/mosh/mosh-client` | 打包后 `/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-.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//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//`。**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`:打包路径 `/et/et[.exe]`;dev 回退 `/resources/et//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--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 提交)