feat: release OpsXShell 2.1.0 native Tauri edition
This commit is contained in:
1 parent
e9a0d21758
commit
a3dba0c6ed
311 files changed
+31748
-14897
No files matched your search
@@ -0,0 +1,148 @@
|
||||
# 架构说明
|
||||
|
||||
## 产品边界
|
||||
|
||||
OpsXShell 是轻量原生的 SSH/SFTP 运维终端,核心范围是连接、终端、文件传输、端口转发、代理、串口和多会话操作。审计、录像、审批和堡垒机能力不在客户端范围内。
|
||||
|
||||
## 目录与职责
|
||||
|
||||
```text
|
||||
src/ React 界面、工作区和 Zustand 状态
|
||||
src/platform/ Tauri command/event 适配层
|
||||
shared/types/ 渲染层 TypeScript 契约
|
||||
src-tauri/src/commands/ 参数边界和统一响应
|
||||
src-tauri/src/domain/ Rust 领域模型
|
||||
src-tauri/src/services/ SSH、SFTP、隧道、PTY、串口、AI、存储和安全策略
|
||||
```
|
||||
|
||||
React 不直接访问 SSH、文件系统或加密实现。Rust 服务持有连接、Shell、SFTP 和串口资源,渲染层只保存资源 ID 与界面状态。新增原生接口时必须同步更新:
|
||||
|
||||
1. Rust command 和 `src-tauri/src/lib.rs` 注册
|
||||
2. `src/desktop.d.ts` 类型契约
|
||||
3. `src/platform/desktop.ts` 适配实现
|
||||
4. `test/setup.ts` 测试替身
|
||||
|
||||
Rust SSH 服务保留 `ssh.rs` 作为连接与 Shell 门面,已将独立边界拆出:
|
||||
|
||||
```text
|
||||
services/
|
||||
├── ssh.rs 连接池、Shell 与稳定公共 API
|
||||
├── ssh/
|
||||
│ ├── auth.rs 密码、私钥、Agent 与默认密钥认证
|
||||
│ ├── known_hosts.rs OpenSSH 主机密钥策略
|
||||
│ ├── proxy.rs HTTP/SOCKS5 连接代理与 ProxyJump
|
||||
│ ├── path.rs 本机路径展开
|
||||
│ └── sftp.rs 远程文件系统与断点传输
|
||||
├── tunnel.rs TunnelManager 与启动/停止编排
|
||||
└── tunnel/
|
||||
├── model.rs 运行实例、计数器和取消令牌
|
||||
├── socks5.rs SOCKS5 服务端协议
|
||||
└── relay.rs TCP/libssh2 双向流转发
|
||||
```
|
||||
|
||||
模块化只约束内部职责,不引入插件框架,也不把 libssh2 资源暴露到 command 层。后续 connection/shell 拆分继续保持现有 Tauri command 契约不变。
|
||||
|
||||
## 原生服务
|
||||
|
||||
| 能力 | 实现 |
|
||||
| -------------------- | ------------------------------------------------ |
|
||||
| SSH/SFTP | `ssh2 0.9` / libssh2 |
|
||||
| HTTP/SOCKS5 连接代理 | Rust TCP 握手后交给 libssh2 |
|
||||
| ProxyJump | libssh2 direct-tcpip + loopback relay,最多 8 跳 |
|
||||
| 本地/远程/动态转发 | libssh2 channel + Rust TCP relay |
|
||||
| 本地终端 | `portable-pty` |
|
||||
| 串口 | `serialport` |
|
||||
| 会话与设置 | 应用数据目录内原子写入 JSON |
|
||||
| 凭据 | AES-256-GCM + `~/.opsxshsell/master.key` |
|
||||
| 主机信任 | OpenSSH `~/.ssh/known_hosts` |
|
||||
| 终端 AI | Rust `reqwest` + OpenAI/Anthropic 兼容协议 |
|
||||
|
||||
## 终端 AI 边界
|
||||
|
||||
AI 是可选的终端助手。XTerm 只把当前焦点分屏的选区或用户明确勾选的当前屏幕交给 Rust;Rust 先脱敏常见密码、Token、Authorization Header、AWS Key 和私钥,再调用 Provider。需要实际处理时采用受控的单工具循环,不允许模型文本直接触发执行。
|
||||
|
||||
```text
|
||||
XTerm 当前焦点分屏
|
||||
-> 选区优先 / 显式当前屏幕
|
||||
-> Rust 本地脱敏
|
||||
-> AiService (OpenAI Chat / Responses / Anthropic)
|
||||
-> 建议文本
|
||||
-> 复制,或单行命令填入 XTerm(不发送回车)
|
||||
-> 执行任务:唯一单行命令 + 用户确认
|
||||
-> 当前 connectionId 的 ssh_exec
|
||||
-> 真实输出脱敏后交给 AI 总结
|
||||
```
|
||||
|
||||
- Provider 配置和模型路由保存在 `ai-providers-v1`,不包含 API Key。
|
||||
- API Key 使用 `ai-provider:<provider-id>:api-key` 作用域写入现有 AES-256-GCM secrets。
|
||||
- 模型可覆盖 Provider 协议和 Endpoint,以支持同一个上游模型的按量和 Plan 路由。
|
||||
- models.dev 目录仅由 Rust 访问三个固定 HTTPS 地址,最多读取 24 MiB;它用于维护和扩展预设,不是应用启动依赖。
|
||||
- new-api、One API 和其他聚合网关通过 OpenAI Compatible Provider 接入。
|
||||
- AI 对话、提示词、终端上下文和回答只存在于当前面板内存中;关闭面板即清空,不写入应用设置。
|
||||
- 新 Provider 的测试命令接收临时 API Key 并发出最小真实完成请求;测试过程不调用凭据存储。只有当前连接字段快照测试成功,界面才允许保存。
|
||||
- 模型返回内容按不可信文本处理。普通模式只有唯一单行代码块可“填入终端”,多行脚本只能复制。
|
||||
- 执行模式只接受唯一单行命令,目标固定为当前活跃 SSH connection;每次调用 `ssh_exec` 前显示目标、命令和风险确认。执行结果只保留在当前面板。
|
||||
- 不创建独立 Kubernetes 客户端或 podexec。集群运维命令沿当前 SSH 会话执行,避免引入本机 kubeconfig 与第二套目标/凭据边界。
|
||||
|
||||
## 端口转发生命周期
|
||||
|
||||
转发规则和运行实例是两种不同状态:
|
||||
|
||||
- `ForwardRule` 由 React 管理并按保存会话 ID 写入设置,只保存类型、端口和目标。
|
||||
- `ForwardStatus` 由 Rust `TunnelManager` 管理,只在当前进程有效,包含监听端口、开始时间、连接数和流量。
|
||||
- 每个运行实例有唯一 `forwardId` 和取消标志。停止、删除正在运行的规则或断开 SSH 连接时,Rust 必须关闭真实 Listener。
|
||||
- 规则支持编辑和复制。复制只产生新的规则 ID,不复制运行态;运行中的规则必须先停止真实 Listener 才能编辑。
|
||||
- 应用重启后不得根据上次 UI 状态显示“运行中”,必须以 `ssh_forward_list` 返回的原生状态为准。
|
||||
|
||||
当前动态 SOCKS5 默认仅绑定 `127.0.0.1`,不对局域网暴露。客户端仅支持 SOCKS5 CONNECT 和无认证方式。
|
||||
|
||||
## SFTP 写入策略
|
||||
|
||||
渲染层通过 `src/platform/sftp.ts` 固定路径方向:
|
||||
|
||||
- `localPath` 永远表示本机文件系统路径,`remotePath` 永远表示 SFTP 服务器路径。
|
||||
- `uploadRemoteFile` 固定为本地源到远程目标;`downloadRemoteFile` 固定为远程源到本地目标。
|
||||
- `readRemoteTextFile`/`writeRemoteTextFile` 只操作远程编辑器内存内容,不等同于下载/上传。
|
||||
- SFTP ID 只能由已连接的 SSH connection 创建,连接失效后不得复用旧 SFTP ID。
|
||||
|
||||
上传和下载使用 `.opsx-part` 临时文件:
|
||||
|
||||
1. 检查临时文件已有长度。
|
||||
2. 从相同偏移继续读取和写入。
|
||||
3. 完成后校验源与目标字节数。
|
||||
4. 校验通过后替换正式目标。
|
||||
|
||||
SSH 传输本身受协议 MAC 保护;当前应用额外执行大小校验,但尚未执行文件内容哈希。
|
||||
|
||||
## 终端输入输出管线
|
||||
|
||||
`TerminalPipeline` 是 XTerm 和 Shell API 之间的轻量边界,单终端与分屏共用:
|
||||
|
||||
- 输入:活动状态更新、Shell 路由、同步输入以及异步写入错误收敛。
|
||||
- 输出:IPC 数据归一化、流式 UTF-8 解码、关键字变换、每帧限量写入和积压上限。
|
||||
- 终端视图只处理布局、焦点与生命周期;Zmodem、OSC 和登录脚本后续作为显式管线阶段增加,不继续堆进工作区组件。
|
||||
|
||||
## ssh:// 深链接
|
||||
|
||||
系统协议由 Tauri deep-link 插件注册,single-instance 插件负责把后续启动参数交回现有进程。原生层只接受 `ssh://`,拒绝 URL 中的密码、路径、查询参数、片段、控制字符和异常用户名。
|
||||
|
||||
深链接不会静默连接任意服务器:Rust 将解析后的 `{ username, host, port }` 放入有界内存队列并通知前端;前端原子取走队列,打开预填充的快速连接窗口供用户确认。冷启动时通知可能早于 WebView,队列仍会保留;运行中唤起则聚焦现有主窗口。
|
||||
|
||||
## 安全规则
|
||||
|
||||
- 敏感凭据必须在 Rust 层加密后落盘。
|
||||
- 不持久化终端命令历史或批量命令输出。
|
||||
- 不持久化 AI 提示词、回答或终端上下文;API Key 不得返回 WebView 或写入普通 settings。
|
||||
- 未实现能力不能在界面中显示为可用。
|
||||
- 自动更新必须具备签名公钥、HTTPS 更新源和回滚策略,否则保持关闭。
|
||||
- 应用不能静默绕过 changed host key 或 known_hosts 不匹配。
|
||||
|
||||
## 体积预算
|
||||
|
||||
| 产物 | 上限 |
|
||||
| -------------- | ----------------------------- |
|
||||
| macOS DMG | 25 MB |
|
||||
| Windows 安装包 | 15 MB,不含 WebView2 引导程序 |
|
||||
| Linux AppImage | 35 MB |
|
||||
|
||||
Release 使用 LTO、单 codegen unit、`panic = abort` 和符号剥离。平台功能必须在对应系统实机验收。
|
||||
Reference in new issue
Block a user