# ohmyssh **Repository Path**: csn1024/ohmyssh ## Basic Information - **Project Name**: ohmyssh - **Description**: ohmyssh - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2026-06-08 - **Last Updated**: 2026-08-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ohmyssh > 一款使用 [Wails](https://wails.io) (Go + Vue 3) 构建的跨平台桌面 SSH 客户端 / 服务器管理工具。 > 内置 xterm.js 终端、SFTP 文件管理、主机分组与凭据加密存储。 ![platform](https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-blue) ![go](https://img.shields.io/badge/go-1.25%2B-00ADD8) ![wails](https://img.shields.io/badge/wails-v2.12-FF6C37) ![release](https://img.shields.io/badge/release-v1.3.0-brightgreen) ![license](https://img.shields.io/badge/license-MIT-green) ## 📥 下载 最新正式版:**v1.3.0** (2026-08-03) | 平台 | 产物 | SHA256 | | --- | --- | --- | | macOS Universal | [`ohmyssh-1.3.0-mac.zip`](https://gitee.com/csn1024/ohmyssh/releases/tag/v1.3.0) | `ee50a2beb5498dcff714726e41fff656376d8d682ad219d3592ed75214d24d48` | | Windows amd64 | [`ohmyssh-1.3.0-windows.zip`](https://gitee.com/csn1024/ohmyssh/releases/tag/v1.3.0) | `f5fe30cb0b6748fd038c49902ab5fb95326b75b18422acc64ec62b784eb9158e` | | Linux amd64 | `ohmyssh-1.3.0` | — | 完整变更记录:[CHANGELOG.md](./CHANGELOG.md) ## ✨ 功能特性 ### 连接与会话 - 🖥️ **多标签 SSH 终端** — 基于 xterm.js + xterm-256color,**rAF 帧内 batch 回显** (WebView IPC 5-15ms/次降到 1 次/帧) - 📁 **SFTP 文件管理** — 浏览/上传/下载/新建/删除,**拖拽上传**(支持文件/文件夹), 目录递归,**批量传输聚合进度**,**剪切/粘贴** 🆕(同侧 + 跨面板,文件+文件夹, 半透明删除线视觉),**文本文件编辑** 🆕(Monaco 等宽 textarea + dirty 守卫 + `Ctrl/⌘+S` 保存),**文件重命名** 🆕(F2 / 右键,模态选中"不含扩展名"那段, macOS Finder 习惯) - 🔁 **断线重连** — 单个 session 可在断连后重建,状态机 `connecting → connected → closed/error` - 🔌 **多 session 并发** — 同主机可开多个 session,互相独立 - 🌐 **SSH 代理** — 每台主机可独立配置: - **SOCKS5**(`golang.org/x/net/proxy`) - **HTTP CONNECT**(自实现,无第三方依赖) - **SSH ProxyJump**(堡垒机/跳板机,复用 hosts 表已有行,自动 cycle 检测 + maxJumpDepth=5 兜底) - 🖱️ **终端自定义右键菜单** — Copy / Paste / Select All / Clear, 剪贴板走 Wails 原生通道避开 macOS paste-permission 弹窗 - ⌨️ **完整键位** — `Ctrl+Ins` / `Shift+Ins` 复制粘贴, `Ctrl+Shift+C/V` 在 WebView2 不再重复触发 ### 主机与凭据 - 🗂️ **主机管理** — 增删改查、备注、标签、最后使用时间,**按创建时间排序** - 📑 **分组管理** — 分组创建/重命名/删除,**拖拽排序**(独立 `host_groups` 表 + `sort_order`), 分组选择器为可搜索下拉 - 🔐 **认证方式** — 密码 / 私钥(带 passphrase)/ keyboard-interactive - 🛡️ **本地凭据加密** — AES-256-GCM,主密钥首次启动生成后落盘到用户配置目录(`master.key`,权限 `0o600`) - 🔑 **OpenSSH 新格式私钥** — 支持 `OPENSSH PRIVATE KEY`(修复之前只能解析 PEM 的限制) ### 同步与备份 - ☁️ **Gitee Gists 云同步** — 跨机器备份/恢复主机和命令, access_token 鉴权、404 fallback、429/5xx 指数退避、token 落 `config.toml` (AES-256-GCM 加密) - 📦 **导入/导出** — 密码模式 envelope 跨机器恢复 ### 🚀 一键部署 (Publish) - 📜 **多产物发布计划** — 一个方案可挂多个 artifact,每个 artifact 独立选择目标 远端(本地 / SSH / Gitee release...)+ 各自的"发布前 / 发布后"本地命令 + 关联主机。编辑器增删行 UI,运行时按顺序执行,失败立即中断后续 - ⏱ **双维度发布延迟** — 独立可配 `hostIntervalMs`(同产物内主机间,默认 200ms) + `artifactIntervalMs`(产物之间,默认 1000ms)。解决同时发到 Gitee + 私有 GitLab 时第二个 artifact 在第一个尚未完成上传就被前端误标 success 的竞态 - 🔔 **发布完成全局通知** — `PublishNotifier` 挂在 `App.vue`,任意页面(主机列表 / 终端 / 设置 / 命令)部署成功时右上角弹 `n-notification`(方案名 + 主机数 + 时间戳),4 语言 i18n 齐 - 🔁 **断线续传 / 失败重试** — 单台主机 publish 失败时不影响后续主机与产物, 在 `publish_run_steps` 表里留独立状态供 UI 详情视图聚合 ### 体验 - 🌗 **主题切换** — 跟随系统 / 浅色 / 深色 - 🔍 **主机搜索与分组筛选** — 前端 store 派生 `filteredHosts` - 💾 **会话缓存** — `keep-alive :include="['SessionContainerView']"` 切走终端不被 dispose - 📜 **连接历史** — `connection_history` 表记录每次会话的开始/结束/状态/错误 - 📜 **应用内日志查看** — 设置页 NModal 实时查看 `ohmyssh.log` 尾部、 一键复制 + 清空(Go 端 `LogService` 走 `close→truncate→reopen` 三步保证 slog 句柄不丢) - 🌏 **国际化** — zh-CN / en-US / ko / ja(后两者走 zh-CN 兜底) ## 🧱 技术栈 | 层 | 技术 | | --- | --- | | 桌面框架 | [Wails v2.12](https://wails.io) (Go ↔ WebView 双向绑定) | | 后端 | Go 1.25+, `golang.org/x/crypto` (SSH/AEAD), `modernc.org/sqlite` (CGO-free) | | 配置 | `spf13/viper` (TOML) | | 前端 | Vue 3 + TypeScript + Vite | | UI | [Naive UI](https://www.naiveui.com/) + Tailwind CSS | | 状态 | [Pinia](https://pinia.vuejs.org/) | | 终端 | [`@xterm/xterm`](https://xtermjs.org/) + `addon-fit` | | 图标 | `@vicons/ionicons5` | | 路由 | `vue-router` (hash 模式) | ## 📁 项目结构 ``` ohmyssh/ ├── main.go # Wails 启动入口 ├── app.go # App 结构 + 启动/关闭钩子 + 主密钥管理 ├── app_e2e_test.go # 端到端冒烟测试 │ ├── internal/ │ ├── config/ # Viper 配置加载 + 平台数据目录解析 │ ├── crypto/ # AES-256-GCM AEAD,HKDF 派生 │ ├── ssh/ # SSH 客户端/会话/代理 (SOCKS5/HTTP CONNECT/ProxyJump)/SFTP/连接池 │ ├── storage/ # SQLite 仓库(hosts / host_groups / history,v10 migration 加代理字段) │ ├── cloudsync/ # Gitee Gists API v5 客户端 + 同步逻辑 │ ├── backup/ # 导入/导出 envelope(密码模式) │ ├── electerm/ # electerm 主机导入底层 parser │ ├── finalshell/ # FinalShell 主机导入底层 parser(含 AES 密钥还原) │ ├── service/ # Wails 绑定层 │ ├── host_service.go # 主机 CRUD + 分组 CRUD/重排 + 代理字段 │ ├── connect_service.go # SSH 终端会话生命周期(含 ProxyConfig) │ ├── sftp_service.go # SFTP 文件操作 + 批量进度事件 │ ├── file_service.go # 本地文件对话框桥接 │ ├── sync_service.go # Gitee 云同步(token 加密落 config.toml) │ ├── publish_service.go # 一键部署:多产物计划 / 双维度延迟 / 失败重试 │ ├── backup_service.go # 导入/导出 envelope(密码模式) │ ├── log_service.go # 应用内日志查看 + 清空 │ ├── command_service.go # 快速命令 CRUD │ ├── ai_service.go # AI 对话(parseAIChatResult 鲁棒解析) │ ├── update_service.go # Gitee 兜底更新页 + 版本比较 │ ├── electerm_import.go # electerm 主机导入 │ └── finalshell_import.go # FinalShell 主机导入(含 AES 密钥还原) │ ├── pkg/logger/ # 结构化日志(落盘到数据目录) │ ├── frontend/ # Vue 3 前端 │ └── src/ │ ├── App.vue # 根布局(Sidebar + 路由出口 + Naive UI 主题) │ ├── components/ # TerminalPane / SftpPane / HostFormModal / ContextMenu / ... │ ├── views/ # HomeView / SessionContainerView / SettingsView (含 Cloud Sync 卡片) │ ├── stores/ # host / session / settings / sync (Pinia) │ ├── router/ # 路由(hash 模式) │ └── style.css # 全局样式 │ ├── Makefile # build / build-release / build-mac / build-win / build-linux ├── wails.json # Wails 应用配置 └── build/ # 平台资源 / 图标 ``` ## 🚀 快速开始 ### 环境要求 - **Go** ≥ 1.25 - **Node.js** ≥ 16(推荐 18+) - **Wails CLI**:`go install github.com/wailsapp/wails/v2/cmd/wails@latest` - 平台原生依赖:参见 [Wails 安装文档](https://wails.io/docs/gettingstarted/installation) ### 开发模式 ```bash # 安装前端依赖 cd frontend && npm install && cd .. # 启动 wails dev(Vite HMR + Go 监听 + 调试 devtools) wails dev ``` `wails dev` 会同时启动: - 前端 Vite 开发服务器 - 一个独立的浏览器调试入口:,可在 devtools 中直接调用 Go 绑定方法 ### 生产构建 ```bash # 通用:默认平台,无版本后缀 wails build # 推荐:使用 Makefile,自动注入版本号 (LDFLAGS) + 文件名后缀 make build VERSION=1.2.3 # 当前平台 make build-mac VERSION=1.2.3 # macOS universal (arm64 + amd64) make build-win VERSION=1.2.3 # Windows amd64 make build-linux VERSION=1.2.3 # Linux amd64 make build-all VERSION=1.2.3 # 三平台全部 # 版本号默认从 git tag 提取:make build-release (无需传 VERSION) ``` 构建产物在 `build/bin/` 目录,命名格式 `ohmyssh-v<版本>.`: | 平台 | 产物 | | --- | --- | | macOS | `ohmyssh-v1.2.3.app` (universal: arm64 + x86_64) | | Windows | `ohmyssh-v1.2.3.exe` | | Linux | `ohmyssh-v1.2.3` (amd64 可执行) | ## 🗃️ 数据存储 | 平台 | 数据目录 | | --- | --- | | macOS | `~/Library/Application Support/ohmyssh` | | Windows | `%AppData%\ohmyssh` | | Linux | `~/.config/ohmyssh` | 目录内容: ``` ohmyssh/ ├── config.toml # Viper 配置(主题、字号、终端尺寸、语言) ├── master.key # 32 字节主密钥(base64,权限 0o600) ├── ohmyssh.db # SQLite 数据库(modernc 驱动,CGO-free) └── logs/ # 结构化日志(pkg/logger) ``` ### 数据库表 - `hosts` — 主机记录;`password_enc` / `private_key` / `passphrase_enc` / `proxy_password_enc` 均为 AEAD 密文, `tags` JSON,v10 migration 新增 `proxy_type` / `proxy_host` / `proxy_port` / `proxy_user` / `proxy_password_enc` / `jump_host_id` / `proxy_command` 7 列 - `host_groups` — 分组目录(含 `sort_order`,拖拽排序用) - `connection_history` — 每次会话的开始/结束/状态/错误 ## 🔒 安全说明 - 凭据(密码 / 私钥 / passphrase)在写入 SQLite 前使用 **AES-256-GCM** 加密 - 主密钥首次启动随机生成,base64 后存于 `master.key`(`0o600`) - 主密钥**不离开本机**,请妥善备份 `master.key` — 丢失后已存凭据将无法解密 - AEAD 实现位于 [internal/crypto/aead.go](internal/crypto/aead.go),单元测试覆盖 [aead_test.go](internal/crypto/aead_test.go) ## 🧪 测试 ```bash # 全部 Go 测试 go test ./... # 只跑核心服务(host / sftp / connect / file) go test ./internal/service/... # 前端类型检查 cd frontend && npx vue-tsc --noEmit ``` ## 🛠️ 常用脚本 | 命令 | 作用 | | --- | --- | | `wails dev` | 开发模式(HMR + Go 监听 + :34115 devtools) | | `wails build` | 生产构建(当前平台) | | `wails generate module` | 重新生成 `wailsjs/` 绑定 | | `make build VERSION=1.2.3` | 当前平台 + 版本号注入 | | `make build-mac VERSION=1.2.3` | macOS universal (arm64+amd64) | | `make build-win VERSION=1.2.3` | Windows amd64 | | `make build-linux VERSION=1.2.3` | Linux amd64 | | `make build-all VERSION=1.2.3` | 三平台全打 | | `make build-release` | 走 git tag 自动取版本号 | | `cd frontend && npm run build` | 仅构建前端 | | `cd frontend && npx vue-tsc --noEmit` | 类型检查 | | `go test ./...` | 全部 Go 测试 | ## 🧭 路由 / 页面 | 路径 | 名称 | 说明 | | --- | --- | --- | | `/` | `home` | 主机列表 + 分组管理 + 搜索筛选 | | `/sessions/:id?` | `sessions` | 多标签 SSH 会话容器(终端 / SFTP 子 tab) | | `/settings` | `settings` | 主题 / 终端设置 / Cloud Sync / 日志查看 | > `SessionContainerView` 被 `keep-alive :include` 缓存:切到主机列表或设置再回来,xterm scrollback 不会丢。 ## 📝 开发者备忘 - 公共绑定方法**不带 `context.Context`** — Wails 运行时 ctx 在 `startup` 钩子里通过 `SetCtx` 注入 - `connect ↔ sftp` 通过 setter 注入解决循环依赖 - 所有走 Wails 事件总线的 `EventsEmit` 都做了 `ctx == nil` 兜底 + `recover()`,避免测试环境 panic - 拖拽排序走 `ReorderGroups`(**整组覆盖**语义),后端不做局部推断,避免误改 `sort_order` ## 📄 License MIT --- Author: [heyi](mailto:heyi@qq.com)