Skip to main content
Glama
konraddzbik

termdesk

by konraddzbik

TermDesk

一款跨平台 SSH + SFTP + VNC + RDP 桌面客户端——多标签终端、流式文件传输和远程桌面(基于 SSH 的 VNC,外加原生 RDP),尽在一个窗口中。

Electron React TypeScript CI License

状态:已发布——SSH 终端、SFTP、基于 SSH 的 VNC、原生 RDP、MCP 代理访问、SSH 隧道管理器、集群自动化、本地终端(可选择终端程序:登录 shell / tmux / Zellij / screen / 备用 shell)、Prompt Book + 定时 Routines、设置、命令面板和打包。


✨ 功能特性

  • 🖥️ 灵活的主机 — 创建时可选择仅 SSH、仅 VNC 或组合主机。SSH 和 VNC 各自独立可选。纯 VNC 主机支持直接 TCP 或 SSH 隧道连接(若在没有 SSH 凭据时尝试隧道连接,会给出明确错误)。UI 操作、命令面板和测试按钮会根据主机类型自适应。

  • 🖥️ 多标签 SSH 终端 — 基于 xterm.js (WebGL),支持搜索、复制/粘贴、调整大小;可对相同或不同主机建立多个并行会话。早期输出会缓冲到终端挂载为止,因此 MOTD/横幅不会丢失。

  • 📁 带流式传输队列的 SFTP — 远程文件浏览器、拖放(递归文件夹上传)、逐块流式传输且内存占用恒定、取消/重试、就地编辑并在保存时自动上传。

  • 🔒 基于 SSH 的 VNC — noVNC 在应用内渲染,通过受一次性、30 秒令牌保护的环回 WebSocket 桥接;默认传输方式是 SSH forwardOut 隧道,因此端口 5900 永不暴露。VNC-only 主机也支持直连模式。

  • 🖥️ RDP — 通过 IronRDP WASM 客户端在应用内连接 Windows/RDP 主机,该客户端经由进程内 RDCleanPath 代理(与 VNC 桥接相同的单次使用、30 秒令牌、来源校验、TLS 终止形态),并采用首次信任服务器证书固定。

  • 🔐 加密保险库 — 主机、分组、代码片段和已知主机存储在 SQLite 中;密码/口令在到达主进程的瞬间即使用基于操作系统钥匙串的 safeStorage 加密。

  • 🛡️ 主机密钥验证 — 首次连接时显示 SHA256 指纹确认对话框,同类型密钥变更时硬性阻止,当已知主机出示从未被信任的密钥/类型时,会发出醒目的"可能存在中间人攻击"警告。

  • 🪜 ProxyJump — 通过链式 forwardOut 实现多跳链(user@jump:port,next)。

  • 🔌 SSH 隧道管理器 — 在侧边栏面板中定义、持久化并启动/停止**本地(-L端口转发和动态 SOCKS5(-D**代理,带实时状态点和吞吐量。尽可能复用已打开终端的连接;无破坏性、按所有者隔离、全程记录。参见 docs/TUNNELS.md

  • 🪟 分屏窗格 — 并排或上下堆叠查看两个会话(Alt-点击标签或分屏工具栏),带可拖拽分隔条。与多主机自动化搭配可形成实时监控网格。

  • 💻 本地终端 — 本机上的 node-pty shell 标签页,可保存工作目录——无需 SSH。连接时会遵循每台主机的默认远程路径。

  • 🧩 选择你的终端程序 — 选择终端打开时运行的程序(设置 → 常规):默认登录 shell、多路复用器(tmux、Zellij、GNU Screen——在重启/断开后保持)或备用 shell(bash、zsh、fish、PowerShell、Nushell)。只有检测到你机器上存在的程序才可选;该选择同时适用于本地终端和 SSH 会话(远程存在时 exec 该程序,否则使用普通 shell)。

  • 🚀 在外部终端中打开 — 将当前目录交给您喜欢的 GUI 终端——Ghostty、Warp、iTerm2、kitty、Alacritty、WezTerm、GNOME Terminal、Konsole、Windows Terminal——自动检测您机器上已安装的程序,可保存默认项(设置 → 常规),通过 ⌘K 或任意本地终端标签页上的一键按钮触发。

  • 集群自动化 — 一次在整组 SSH 主机上运行代码片段或命令,实时流式显示每台主机的输出。

  • 🕘 活动日志 — 本地时间线,记录连接/断开、SFTP/VNC 打开和自动化运行。仅元数据:含机密的命令令牌会被打码,条目在 90 天后清除。

  • 🎨 终端配色方案 — 内置 Dracula、Solarized Dark、Gruvbox、One Dark、Nord(外加默认方案),按您的偏好选择。

  • 🧱 可自定义侧边栏 — 从设置 → 常规或侧边栏的 自定义 按钮显示或隐藏左侧边栏的每个分区(主机、本地终端、工作区、隧道、代码片段、Prompt Book、Routines)。您的选择会持久保存;默认全部可见。

  • 🧩 Prompt Book — 面向 AI 代理的可复用模板化提示词。提示词为纯文本,带 {{variable}} 占位符(可选 {{name:default}} / {{name|description}});运行时会询问取值、显示实时预览,并将渲染后的文本发送到活动终端(SSH 或本地),或在您选择的目录中通过 AI 代理——Claude Code、Aider、OpenCode、Codex 或 Gemini——启动。模板化是纯替换(无 eval),提示词作为单个带引号的参数传递,因此其中的 shell 元字符不会生效。

  • 🔁 Routines — 保存的"在 此目录 中通过 此代理 运行 此提示词"任务,可按需或按计划(间隔 / 每日 / cron)运行,每个任务都有自己的运行历史。Routines 在 TermDesk 打开时触发,应用关闭期间错过的运行会在下次启动时补跑一次,而不会蜂拥补跑所有错过的时段。无人值守的自主模式为可选加入,默认关闭;存储的运行摘要会打码机密信息。

  • 📋 代码片段 — 保存的命令,发送到活动会话。

  • ⌨️ 命令面板⌘/Ctrl+K 模糊主机搜索 + 所有命令(终端、SFTP、VNC、自动化、日志);按 ? 查看快捷键速查表。

  • ⚙️ 设置 — 主题(深色 / 浅色 / 跟随系统)、终端字体 + 配色方案、右键粘贴行为、SSH 保活;纯 JSON,绝不包含机密。

  • 🤖 AI 代理访问(MCP) — 让 Claude、Cursor、Grok 或任何 MCP 客户端使用 TermDesk:列出主机、运行命令、在集群中分发任务。代理获得的是双手,而非钥匙——凭据留在主进程中,每个操作都需按主机选择加入 + 审批门控,并实时显示在 AI 活动日志中。默认关闭。参见 docs/MCP-INTEGRATION.md

  • 🏠 本地优先 — 您的主机、密钥和历史记录永不离开您的机器;无需强制云账户,无遥测。

Related MCP server: SentryFrogg MCP Server

🏗 架构

所有 SSH/SFTP/VNC 逻辑都位于主进程中。渲染进程完全沙箱化(contextIsolation: truenodeIntegration: falsesandbox: true),仅通过类型化、经 Zod 校验的 IPC 契约(src/shared/ipc.ts)与之通信。

flowchart LR
    subgraph R["Renderer (sandboxed, no Node)"]
        UI["React 19 UI<br/>zustand stores"]
        XT["xterm.js<br/>terminal"]
        NV["noVNC<br/>RFB client"]
    end

    subgraph M["Main process"]
        IPC["Typed IPC<br/>Zod-validated contracts"]
        SM["SessionManager<br/>(ssh2: shell, agent,<br/>keys, ProxyJump)"]
        SF["SftpManager +<br/>TransferManager<br/>(streaming queue)"]
        WB["ws-bridge<br/>127.0.0.1, random port,<br/>single-use 30s tokens"]
        VA[("Vault<br/>better-sqlite3 + Drizzle<br/>secrets via safeStorage")]
    end

    RH[("Remote host<br/>sshd · sftp · vncserver")]

    UI <-->|"invoke/handle"| IPC
    XT <-->|"ssh:data:#lt;sessionId#gt; stream"| IPC
    IPC <--> SM
    IPC <--> SF
    SM --- VA
    SF --- SM
    NV -->|"ws://127.0.0.1:#lt;port#gt;/#lt;token#gt;"| WB
    WB -->|"variant B (default):<br/>ssh2 forwardOut → :5900"| SM
    WB -.->|"variant A (opt-in):<br/>direct TCP → :5900"| RH
    SM <-->|"SSH"| RH

对"机密留在主进程"有两个有意为之的例外:存储的 VNC 密码(用于 RFB 凭据握手)和存储的 RDP 密码(用于 IronRDP 客户端)会在主进程中解密并返回给渲染进程。目前两者都未绑定到打开的标签页,也未做速率限制——参见已知限制

🚀 快速开始

TermDesk 目前从源码运行konraddzbik/termdesk 没有标签,也没有 GitHub Releases,因此 Releases 页面暂时没有可下载的内容——参见 INSTALL.md

您需要 Node.js >=22.12.0npm 10+,外加 C/C++ 工具链(Xcode Command Line Tools / build-essential / Visual Studio C++ build tools)以便原生模块能够编译。在 Linux 上您还需要一个已解锁的操作系统钥匙环(gnome-libsecret 或 kwallet)——TermDesk 采用故障关闭策略,而不是使用 Electron 不安全的 basic_text 回退方案,因此没有钥匙环就无法保存主机密码(SECURITY.md)。

npm install        # also applies the better-sqlite3 patch + rebuilds native deps
npm run dev        # electron-vite dev server + Electron window, HMR

首次运行: npm install && npm run dev 即可打开一个可用的应用——无需许可证检查、无需席位、无需账户。完整的开发环境搭建参见 CONTRIBUTING.md,包括运行 npm test 之前所需的 better-sqlite3 ABI 步骤。

脚本

说明

npm run dev

electron-vite 开发服务器 + Electron 窗口

npm run doctor

预检:Node、工具链、better-sqlite3 ABI、Linux 钥匙环

npm run build

类型检查(TS 严格模式)+ 生产构建到 out/

npm run lint / lint:fix

biome 检查(格式 + 代码规范)

npm test

vitest(单元 + 集成)

npm run test:coverage

带 v8 覆盖率的 vitest

npm run dist

构建 + electron-builder(dmg / NSIS / AppImage)

npm run dist 为宿主操作系统生成未签名的安装包(Apple Silicon macOS 上为 dist/TermDesk-<version>-arm64.dmg,外加交叉构建的 x64 .dmg)。它不会发布任何内容。从源码搭建、本地产物名称以及未来如何发布 GitHub Release,参见 INSTALL.md

🧪 开发测试环境

单个 Docker 容器即可提供 SSH + TigerVNC 用于本地测试——无需真实服务器:

npm run test:keys                                  # throwaway SSH keys (untracked)
docker compose -f docker-compose.test.yml up -d

服务

端点

凭据

SSH

127.0.0.1:2222

testuser / testpass123,或 .test/ 中生成的密钥(test_keytest_key_enc

VNC

127.0.0.1:5901(直连或通过 SSH 隧道——同一容器)

testvncpass

E2E 冒烟测试套件

五个自包含的端到端套件在真实的 Electron 应用内运行(前四个针对 docker 容器;MCP 套件无需外部服务)。每个套件在成功时都会打印一个 *_OK 标记:

命令

验证内容

验证结果

TERMDESK_SMOKE=vault npx electron .

密钥在持久化前使用 safeStorage 加密;SQLite 中无明文;绝不返回给渲染进程

VAULT_SMOKE_OK

TERMDESK_SMOKE=ssh npx electron .

使用密码、密钥、密钥+加密口令进行真实登录

SSH_SMOKE_OK

TERMDESK_SMOKE=sftp npx electron .

1 GB 上传+下载,带 RSS 监控(峰值 259 MB——低于 300 MB 预算)以及 500 个文件的文件夹上传

SFTP_SMOKE_OK

TERMDESK_SMOKE=vnc npx electron .

RFB 握手,直连和经 SSH 隧道均可;伪造令牌连接被拒绝;令牌一次性使用

VNC_SMOKE_OK

TERMDESK_SMOKE=mcp npx electron .

令牌门控的 MCP 服务器在 loopback 上启动;伪造的 bearer 令牌被拒绝

MCP_SMOKE_OK

✅ 测试

npm test                # unit + integration suite (vitest) — all green on main
npm run test:coverage   # v8 line coverage report into coverage/

Vitest 在 jsdom(Testing Library)下运行渲染进程测试,其余在 node 下运行。覆盖率集中在关键处——纯逻辑覆盖率很高,而进程胶水代码和 UI 外壳由五个 e2e 冒烟测试覆盖,而非单元测试(以下百分比仅供参考,并非固定值——运行 npm run test:coverage 获取当前数字):

  • 高(行覆盖率): shared 97.1%——每个进程都依赖的 Zod IPC 契约——renderer/lib 97.5%,main/store 81.4%(db、hosts-repo、settings 和 snippets-repo 达到或接近 100%),ssh-util 100%,~/.ssh/config 解析器及其 Include 解析器在 90% 以上。

  • 低,由冒烟测试覆盖: main/ipc 处理器胶水 9.8%,session-managersftp-manager/transfer-manager 0%,vnc-manager 和 UI 外壳(SftpTabTerminalViewVncTab、布局和侧边栏面板)低或为零。这些是进程胶水和 Electron/DOM 外壳;TERMDESK_SMOKE 测试框架针对真实服务器端到端地运行它们。入口点和 *-smoke.ts 文件通过配置从覆盖率中排除。

构建 + 类型检查 + lint 均干净,有头开发启动记录零错误行。

⌨️ 键盘快捷键

快捷键

操作

⌘/Ctrl+K

命令面板(模糊主机搜索 + 命令)

⌘/Ctrl+T

新建会话(打开命令面板)

⌘/Ctrl+W

关闭当前标签页

⌘/Ctrl+F

在终端中搜索

⌘/Ctrl+Shift+C/V

在终端中复制/粘贴

⚙️ 设置

主题(深色、浅色或系统——跟随操作系统的 prefers-color-scheme)、终端字体大小/字体族、SSH keepalive 间隔——以纯 JSON 存储在 userData/settings.json 中(绝不存储密钥)。

📡 功能详情

SSH 终端

  • 所有 ssh2 逻辑都在主进程中(src/main/ssh/session-manager.ts);渲染进程只看到类型化的流 API。输出通过 ssh:data:<sessionId> 流式传输;早期输出被缓冲,直到终端附加。

  • 认证:保险库密码(带 keyboard-interactive 回退)、私钥(+ 加密口令)或 SSH agent(SSH_AUTH_SOCK,Windows OpenSSH 管道)。密钥仅在连接时解密。

  • 主机密钥:首次连接显示 SHA256 指纹批准对话框并持久化到 known_hosts 表;不匹配则硬阻断连接。

  • ProxyJump 链(user@jump:port,next)通过链式 forwardOut;keepalive 15 秒。

SFTP

  • SFTP 会话复用同一主机的已打开终端的实时 SSH 连接(无需二次登录),并回退到专用的无 shell 连接。

  • 传输逐块流式进行(内存恒定——1 GB 在 300 MB RSS 以下验证通过),可取消/可重试;文件夹拖放递归上传并保持结构。

  • 就地编辑下载到临时文件,打开操作系统编辑器并在保存时自动上传。

VNC

  • noVNC 在渲染进程中渲染;它通过 WebSocket 与绑定到 127.0.0.1 随机端口的主进程桥通信。每个连接都需要一个一次性、30 秒有效期的令牌——其他本地进程无法使用该桥(因此 CSP 中有 ws://127.0.0.1:*)。

  • 默认传输是 SSH forwardOut 通道(变体 B——端口 5900 永不暴露),在存在实时终端连接时复用该主机的连接;纯 TCP(变体 A)按主机选择启用。

  • 存储的 VNC 和 RDP 密码在主进程中解密并返回给渲染进程供各自的客户端使用——这是"密钥留在主进程"的两个文档化例外。

  • 工具栏:本地缩放/远程调整大小切换、剪贴板粘贴、Ctrl+Alt+Del、全屏、重连(每次重连都会提供新令牌 + 隧道)。

保险库

  • 主机、组、代码片段和 known_hosts 存储在应用 userData 目录下的 SQLite 中(better-sqlite3 + Drizzle)。

  • 密码/口令在到达主进程的那一刻使用 safeStorage(操作系统钥匙串密钥)加密;只有密文 blob 触及数据库,密钥绝不发送回渲染进程。

  • ~/.ssh/config 导入解析 Host/HostName/Port/User/IdentityFile/ProxyJump,解析 Include 指令(波浪号/相对/绝对路径和通配符,带循环/深度/大小保护),并使用 OpenSSH 先到先得排序将 Host */通配符默认值合并到具体主机中。

🔐 安全检查清单

  • contextIsolation: truenodeIntegration: falsesandbox: true——渲染进程和 preload 从不接触 Node。

  • 通过 meta 标签严格 CSP:script-src 'self'unsafe-eval,无远程模块;connect-src 仅允许 'self' 和 loopback WebSocket(令牌门控的 VNC 桥)。仅开发环境的放宽通过 Vite 插件实现,漂移时会大声失败。

  • webSecurity: true;导航经过源检查;window.open 被拒绝(外部 https: → 系统浏览器);<webview> 被阻止;权限请求默认拒绝。

  • 每个 IPC 负载都用 Zod 验证(src/shared/ipc.ts 是唯一契约);调用结果通过 IpcInvokeMap 端到端类型化。

  • 密钥(SSH/VNC 密码、密钥口令)在到达主进程的那一刻使用 safeStorage 加密;只持久化密文;解密在连接时进行,引用立即丢弃。由 TERMDESK_SMOKE=vault 验证。两个文档化例外:VNC 和 RDP 密码为各自的协议客户端传到渲染进程(见 已知限制)。

  • 跨 IPC 的错误已清理(仅第一行,无堆栈/路径);密钥绝不记录日志。

  • 会话、SFTP 句柄、传输和主机密钥提示都限定在创建它们的 WebContents 所有者范围内,并在其销毁时清理。

  • 主机密钥:首次连接时 SHA256 指纹批准,不匹配时硬阻断——对最终目标和每个 ProxyJump 跳点

  • 活动日志仅含元数据:操作系统用户名、含密钥的命令令牌已脱敏、90 天清理。

  • 可选主密码(Argon2id 派生的第二加密层)——已推迟,作为未来增强跟踪。

完整安全模型、已知限制和漏洞披露政策见 SECURITY.md

📂 项目结构

src/
  main/                  # Electron main process — all SSH/SFTP/VNC logic lives here
    ipc/                 # IPC handlers per domain (ssh, sftp, vnc, hosts, …)
    ssh/                 # session-manager, ssh-config-parser, ssh-util (+ smoke)
    sftp/                # sftp-manager, transfer-manager, edit-watch (+ smoke)
    vnc/                 # ws-bridge, vnc-manager (+ smoke)
    store/               # Drizzle + better-sqlite3, secrets (safeStorage), settings
  preload/               # contextBridge — typed, minimal API surface (window.api)
  renderer/              # React UI (no Node access)
    components/{layout,hosts,terminal,sftp,vnc,snippets,ui}/
    hooks/  stores/  lib/
  shared/                # IPC channel contracts + Zod schemas shared by all processes

🗺 已知限制 / 路线图

  • 主密码——保险库上可选的 Argon2id 派生第二加密层;已推迟。

  • Playwright 冒烟测试(启动应用 → 添加主机 → 模拟连接)——已推迟;目前五个 TERMDESK_SMOKE 测试框架覆盖 e2e。

  • ~/.ssh/config 导入——Match 块和令牌展开(%h/%p)仍被跳过;云提供商清单导入(AWS/GCP)未实现。

加固路线图见 SECURITY.md

🔧 原生模块说明

  • better-sqlite3 需要源码补丁才能针对 Electron 的 V8 编译(尚无上游预构建)——通过 npm install 时的 patch-package 自动应用(patches/better-sqlite3+12.10.0.patch)。

  • cpu-features(可选的 ssh2 原生依赖)在 package.json 中被覆盖为 noop2,以避免不必要的原生构建;ssh2 回退到其纯 JS 路径。

🤝 贡献

欢迎提交 issue 和 pull request。外部安装、软件包和贡献者设置的交付计划见 docs/OSS-DELIVERY-PLAN.mdCONTRIBUTING.md 包含开发环境设置——包括两个否则会浪费你一下午的原生模块陷阱——新代码必须保持的不变式,以及 PR 前检查清单。贡献采用 inbound=outbound MIT:无需 CLA,无需版权转让。

参与受行为准则约束。安全问题通过 SECURITY.md 中的私密渠道报告,绝不通过公开 issue。

📄 许可证

本仓库中的源代码根据 MIT 许可证发布——见 LICENSE。你可以使用、修改和重新分发它,包括商业用途,只要版权声明和许可证文本随附。

当 Releases 页面存在预构建安装程序时,它们还受 EULA.txt 约束,该文件管辖这些二进制文件。目前没有 GitHub Releases,因此你根据此源代码自行构建的产物仅受 LICENSE 管辖。

此客户端中没有许可证检查、席位激活或账户:商业许可子系统在源代码发布时已被移除,因此你从此仓库构建的就是完整应用。

该边界在代码中强制执行,而不仅仅在此描述:首次运行的 EULA 提示仅编译进项目自己的发布构建中,因此 fork 的 npm run dist、发行版软件包或你自己的安装程序永远不会显示它。

第三方依赖保留各自的许可证,列于 THIRD-PARTY-NOTICES.md。其中一个带有值得指出的义务:@novnc/novnc 是 MPL-2.0,本仓库对其打了补丁(patches/@novnc+novnc+1.7.0.patch),因此该修改在此以 MPL-2.0 与其余源代码一起发布。

Maintenance

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables SSH interactive session management through MCP, supporting commands, menus, and session lifecycle operations.
    1
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server that provides SSH tools (read-only probes and arbitrary exec) to a fleet of hosts outside Kubernetes, with an inventory-based allowlist and key-based authentication.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/konraddzbik/termdesk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server