s-blog

STerminal —— 跨平台桌面终端管理工具

SSH / SFTP / 本地终端 / 端口转发 / 命令片段 一体化桌面客户端,自带端到端加密的开源云同步。

Electron Vue TypeScript xterm.js ssh libsodium Express SQLite

Demo ↗ GitHub ↗

STerminal —— 跨平台桌面终端管理工具

STerminal

一个我自己每天上班都在用的终端工具。

为什么自己做

终端 / SSH 客户端这个赛道现成产品很多 —— iTerm2 / Warp / Tabby / Termius / Royal TSX。 用了一圈下来,每个都有让我别扭的地方:

  • iTerm2: 只是个 emulator,不管 SSH 主机管理。配置散在 ~/.ssh/config 里手敲,再加一堆 alias
  • Warp: AI 体验做得不错,但订阅制 + 远程协作要付费 + 历史数据进云端,对生产环境用着心里发毛
  • Termius: 跨平台齐全但订阅制,云同步是闭源 SaaS,密码 / 密钥指纹 / 内部主机名全交给服务器
  • Tabby: 开源功能堆满,但插件依赖大、UI 老
  • Royal TSX: 老牌稳,UI 陈旧

我想要的就一句话:跨平台 + 一体化 + UI 现代 + 云同步 E2EE 且服务器开源可自建。 没有现成的,那就自己做。

一些可能值得讲的设计

环境感知边框

主机配置里加了一个 environment 字段(dev / staging / production)。 xterm 容器外层根据这个值上色 —— 生产服务器是红边、staging 黄边、本地默认绿

动机直白:每个运维都被「我以为我在测试机,其实在生产」咬过。 那个瞬间不是缺信息(标签页上写着主机名)—— 是注意力被命令吃掉了,没扫到。 颜色比文字截留得住眼神。

实现是个 <div class="terminal-frame" :class="env">,CSS variables 切一下就完事, 工作量两小时。但救你一次就能值回票价。

会话录制 + GIF 导出

xterm 的 onData 钩子写 asciicast v2 NDJSON。回放时按 timestamp 重放(带倍速 0.5x-10x、 进度条、跳转)。最有用的是 一键导出 GIF(带 STerminal 水印)。

用途三个:

  1. 写文档 —— 命令演示比截图直观,比录屏体积小一个数量级
  2. 报 bug —— 把"我做了什么导致它崩了"丢出来比文字描述快十倍
  3. 教徒弟 —— 一段 SSH 操作录下来,新人自己回放就行

asciicast 文件本质是文本,可搜可 diff,理论上可以接 git。

E2EE 云同步

终端配置里有什么?密码、密钥指纹、内部主机名 / 内网 IP、各种 SSH proxyjump 链路 —— 比邮件还敏感。服务器永远不该看到这些明文。

栈:libsodium-wrappers-sumo,Argon2id 从用户密码派生密钥,XSalsa20-Poly1305 加密 payload。 服务器(packages/server)只看得到密文 + 元数据(时间戳、版本号),即便我自己拿数据库 也解不了密。

同步范围:主机配置 / 命令片段 / 设置 / 端口转发 / SSH 密钥 / Vault 密钥库 / 主题 / 快捷键。增量推送、乐观锁、冲突走 Last-Write-Wins。

服务器是独立 npm 包(Express + SQLite + WebSocket + JWT),完全开源, 带 Dockerfile 和 docker-compose。可以选官方云,也可以自己 docker compose up 一台。 这是跟所有商业产品的根本区别。

命令片段变量占位符

${name} / ${name:default} / ${name:A|B|C} / ${!password}(最后一个会遮蔽显示)。 内置 ${date} / ${time} / ${timestamp}

例子:

kubectl logs ${pod:my-app-pod} -n ${namespace:default} --tail=${lines:100} -f

双击片段,弹一个变量填写对话框(实时预览、密码遮蔽、值记忆)。一键执行到当前终端。

比 alias 灵活(alias 不能交互式填参),比 chezmoi 轻(不用同步整个 dotfiles), 比 Notion 收藏命令快(不用切换上下文复制粘贴)。

CLI 工具

设置页一键把 sterminal 命令安装到 PATH。然后:

sterminal ssh prod-server

直接拉起 GUI 客户端连到 prod-server(这是你在 GUI 里配过的主机名)。 桌面应用 + CLI 共享同一份配置 —— 你既不用在 ~/.ssh/config 里手敲, 也不用每次切到 GUI 找。

这个细节大部分桌面工具不做,因为做了 GUI 就懒得管 CLI 了。但实际上很多操作场景是 先在终端git push 完想跑个 deploy),切换到 GUI 反而打断节奏。

工程一瞥

sterminal/
├── packages/
│   ├── client/                # Electron 桌面客户端
│   │   └── src/
│   │       ├── main/          # 主进程:IPC / PTY / SSH / DB / 审计 / 更新 / 托盘
│   │       ├── preload/       # contextBridge
│   │       ├── renderer/      # Vue 渲染进程
│   │       └── shared/
│   └── server/                # 同步服务后端(独立可自托管)
│       └── src/
│           ├── controllers/  services/  middleware/  routes/  validators/
│           ├── websocket/
│           └── database/
├── .github/workflows/
│   ├── build.yml      # CI: typecheck (vue + main) + 客户端/服务端测试 + 三平台构建
│   └── release.yml    # 发布: tag 触发 → Electron 安装包 + Docker 镜像 (GHCR)
└── docs/  PRD.md  ARCHITECTURE.md  PROGRESS.md
  • 测试:274 个 Vitest(happy-dom + better-sqlite3 内存数据库)。覆盖 SSH 连接逻辑、 片段变量解析、加密管线、API endpoint
  • CI/CD:GitHub Actions 三平台并行构建(macOS .dmg / Windows .exe / Linux .AppImage)。 tag push 触发 release workflow,自动发包 + 推 GHCR 镜像
  • 代码量:TS 584 KB + Vue 519 KB,monorepo 两个包
  • i18n:简体 / English / 繁體,即时切换
  • 平台适配:macOS 交通灯、Windows 标题栏覆盖、Linux 默认

还在做的

  • Beta 收尾 —— 集中清剩下的边角 issue,然后准备 v1.0
  • 多窗口 —— 当前一个进程一个窗口,想加一个"一窗多 workspace"模式
  • 更精细的审计日志查询 —— 现在能 filter / 分页,但跨字段组合查询 UI 还粗糙
  • 官方云同步服务器开服 —— 后端代码已经齐全,部署到 VPS 就行,但还在等 sterminal.app 域名
  • AI 辅助层 —— 命令补全 / 错误诊断 / 自然语言转 shell 等。已在路线图里,但有几条原则: 本地模型优先(避免敏感命令进云端)、可一键关、不绑订阅、不抢操作流的主导权。 本质是"减少敲第二遍命令"的体力活,不是替你决策

没做 / 不打算做的

没做 原因
插件市场 插件生态会反过来限制核心功能演进 —— Tabby 走过这路,再不想踩
内置 K8s / Docker UI 那是另一个产品的范畴。要管容器装 Lens / OpenLens / k9s 体验更好
Web 版 xterm.js 在 web 跑没问题,但本地 PTY / 文件系统是核心,web 版会是阉割品

链接

—— 用着不顺手的地方欢迎提 issue,最快的反馈通道。

原文链接:https://www.ssssmy.net/works/sterminal