# dsh-web-ui

> Adds SSH host management, command execution, web terminal, SFTP file transfer, local port forwarding, cluster concurrent execution, and 6 Agent tools to the dsh Web GUI. Host configurations are stored centrally on the local machine.

## Metadata

- Author: [@zhu1090093659](https://github.com/zhu1090093659)
- Repo: <https://github.com/zhu1090093659/dsh-web-ui.git>
- GitHub: [zhu1090093659/dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui)
- Stars: 5,126
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Homepage: <https://gallery.dsh-market.com>
- Topics: `deepseek-harness`, `dsh`, `dsh-plugin`, `web-ui`
- Forks: 311
- Open Issues: 49
- Last push: 2026-08-20T14:37:38.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-ssh
```

## Wiki

## 一句话定位
为 dsh Web GUI 加入一套完整的 SSH 运维能力：在 Web 端集中管理多台远程主机（增删改查、搜索、连接测试、一键导入 `~/.ssh/config`），在 host 进程内复用持久 ssh2 连接池执行命令、跑 Web 终端、SFTP 传文件、开本地端口转发隧道，并在多台主机上并发跑同一条命令；同时为 Agent 提供 6 个工具调用同一份主机配置。

## 核心能力
- 主机增删改查、搜索、连接测试；支持按环境/标签分组折叠与组内批量测试，支持密钥/密码认证、passphrase 密钥、ProxyJump 多级跳板机
- 一键导入 `~/.ssh/config`：解析 Host/HostName/User/Port/IdentityFile/ProxyJump 等字段，已有别名自动跳过（通配模式 / 缺 HostName 跳过）
- 持久 ssh2 连接池：每台主机复用一条长连接（不每次重连），空闲 30 分钟自动断开，断线自动重连（最多 3 次）
- 命令执行：单主机 exec 带超时（默认 60s，可覆盖）、stdout/stderr 分离、单次输出上限 2MB 截断保护
- Web 终端（xterm.js + WebSocket PTY，背压自动暂停/恢复）、SFTP 文件传输（上传走 NDJSON 进度流，下载返回二进制流）、本地端口转发隧道（仅 127.0.0.1 监听，访问远程数据库/内网服务）、集群并发执行（默认并发 8，可按 alias/环境/标签过滤）
- Agent 工具 `ssh_list` / `ssh_exec` / `ssh_upload` / `ssh_download` / `ssh_tunnel` / `ssh_cluster`，GUI 与 Agent 共享同一份主机配置

## 技术实现
- **语言**: TypeScript（host 半区 `src/index.ts`）+ TypeScript + React 18 + CSS Modules（browser 半区 `src/client/`）
- **关键依赖**: `ssh2` ^1.17.0（持久 ssh2 连接池 + SFTP）、`@xterm/xterm` ^6.0.0 + `@xterm/addon-fit` ^0.11.0（浏览器终端）、`ws` ^8.18.0（WebSocket PTY）
- **架构模式**: 双半区 cordis bundle。`src/index.ts` 是 host 半区（`SshEngine` 连接池 + `/api/dsh-ssh/*` 路由 + 6 个 Agent 工具 + 系统提示词宣告），`src/client/index.ts` 是 browser 半区（侧边栏入口 + 主机管理/终端/传输/隧道面板 + locale 注册）。bundle 声明在 `packages/dsh-ssh/cordis.patch.yml:10-12`（插入 id `ssh`），浏览器端通过 `dsh.client.inject` 注入 3 个官方 `@deepseek-ai/dsh-client-*`（`client-runtime` / `client-connection` / `client-ui-settings`）+ `platform: web`。连接池默认参数：空闲 30 分钟、握手 15s 超时、keepalive 15s 间隔、输出 2MB 上限、集群并发 8、SFTP 并发 8（`packages/dsh-ssh/src/engine/connection-pool.ts:30-38`）
- **入口文件**: `packages/dsh-ssh/src/index.ts`（host apply，`applyImpl`）、`packages/dsh-ssh/src/client/index.ts`（browser apply，`apply(ctx)`）；bundle 声明 `packages/dsh-ssh/cordis.patch.yml:1-12`，浏览器依赖 `packages/dsh-ssh/package.json:32-40`

## 适用场景
- 日常需要同时管理多台 SSH 服务器（开发/测试/生产混合）的开发者：希望在 dsh Web GUI 中集中浏览主机、执行命令、传文件、开隧道访问远程数据库/内网服务，省去切换多个工具；
- 想让 Agent 直接执行远程运维任务的开发者：在 GUI 中预先配置好主机（手动填或导入 `~/.ssh/config`），Agent 就能通过 `ssh_list` / `ssh_exec` / `ssh_upload` / `ssh_download` / `ssh_tunnel` / `ssh_cluster` 调用同一套主机配置；
- 已有 `~/.ssh/config` 的用户：通过「导入 ssh_config」一键把所有现成的 SSH 别名同步到插件，无需重复输入 host/port/user/identityFile。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH 宿主 | `0.1.0-rc.8+` 推荐 | package.json 未在 `dsh.engines` 声明；devDependencies 统一锁到 `@deepseek-ai/dsh-* ^0.1.0-rc.8` |
| Node.js | `^22.19.0` 或 `>=24.0.0` | package.json `engines.node` |
| 平台 | 跨平台 | host 半区跑 Node.js、client 半区跑浏览器；ssh2 是纯 JS 库，无 OS 限制 |
| 原生模块 | 无（运行时） | ssh2 不需要 native 绑定；上传/下载走 ssh2 自带 SFTP |
| React | `^18.2.0` | peerDependency，由宿主运行时注入 |
| SSH 服务端 | 任意标准 OpenSSH | host 半区作为 ssh2 客户端，不依赖服务端版本 |

## 安装方式
```bash
dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-ssh
```

## 配置项
本插件在 dsh 设置面板的「SSH」区域提供 3 个开关/输入：

| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| enabled | 开关 | 插件总开关；关闭后不注册 `/api/dsh-ssh/*` 路由、不注册 Agent 工具、不向 Agent 宣告 | true |
| announceToAgent | 开关 | 是否在系统提示词中向 Agent 宣告本插件（关闭后 Agent 看不到 SSH 工具） | true |
| terminalFontFamily | 字符串 | Web 终端字体（写入 xterm `fontFamily`）；留空走 CSS 链（`--dsh-ssh-terminal-font` → 官方 `--ds-font-family-code` token → 内置 monospace 栈）。要渲染 powerline / Nerd Font 图标请填 Nerd Font 栈（如 `"SauceCodePro Nerd Font", monospace`）。对已打开的终端实时生效 | "" |

引擎内部还有几处不暴露给 GUI 的硬编码常量（`packages/dsh-ssh/src/engine/connection-pool.ts:30-38`）：空闲超时 30 分钟、握手超时 15 秒、keepalive 间隔 15 秒、输出上限 2MB、exec 默认超时 60 秒、集群默认并发 8、SFTP 并发 8；上传单次请求体上限 4 GiB（`packages/dsh-ssh/src/routes.ts:27-28`）。调整需改源码后重新构建。

## 常见问题

**Q: 安装之后侧边栏没出现 SSH 入口怎么办？**

A: 重启 `dsh web`。bundle 激活是 host 启动时一次性做的，刷新浏览器不足以触发。重启后侧边栏出现「SSH」入口；Agent 提示词也会自动带上插件说明（受 `announceToAgent` 开关控制）。重启流程见 `packages/dsh-ssh/README.zh.md:47`、`packages/dsh-ssh/README.md:47`。

**Q: 密码是怎么保存的？安全吗？**

A: 密码 / passphrase 以明文存于 `~/.dsh/dsh-ssh.json`，文件权限 0600、目录 0700，原子写入（tmp + rename）。这是与 ssh-skill 同一信任模型——既然 `~/.ssh/config` 也常以注释形式存密码，本插件不做额外加密（避免密钥管理带来新风险）。文件路径与权限在 `packages/dsh-ssh/src/store.ts:18-20、347-355`。

**Q: 局域网里别人能访问我的 SSH 控制台吗？**

A: 不能。所有 `/api/dsh-ssh/*` 路由仅信任本机回环连接（socket 地址必须命中 IPv4 127/8 / `::1` / IPv4-mapped `::ffff:127/8`，并要求同源 Host header + `sec-fetch-site` / `Origin` 校验），LAN 邻居直接 403。WebSocket 终端升级也走同一个 loopback 围栏；本地端口转发隧道只监听 `127.0.0.1`，不会暴露到 LAN。详见 `packages/dsh-ssh/src/routes.ts:101-111`、`packages/dsh-ssh/src/loopback.ts:44-62`。

**Q: Agent 能在我没配置的主机上执行命令吗？**

A: 不能。Agent 只能操作 GUI 中已配置、或从 `~/.ssh/config` 导入的主机别名。如果别名未配置，`ssh_exec` / `ssh_upload` 等工具调用会直接报错，不会臆造主机。这一约定同时写在系统提示词「限制」段（`packages/dsh-ssh/src/index.ts:67`）与 `packages/dsh-ssh/README.md:27`。

**Q: 远程输出会脱敏吗？比如 `env` 这种命令？**

A: 不会。`exec` / `cluster` 的 stdout/stderr 原样返回（不做 secret 替换），`env` 这类命令可能把远端环境变量里的密钥带回对话记录。这是 ssh-skill 同样的语义——传输 / 执行消耗真实远程资源、Agent 使用前需用户确认。语义在 `packages/dsh-ssh/src/index.ts:67`、`packages/dsh-ssh/README.md:29`。

**Q: 下载文件怎么操作？能不能下载整个目录？**

A: GUI 与 `ssh_download` 工具都只能下载单文件：先在「传输」面板的远程文件浏览器里挑文件，再走 `/api/dsh-ssh/download`（返回二进制流，浏览器保存）。上传支持本地目录递归（`walk` 后逐文件传）；下载暂不支持整目录批量。限制见 `packages/dsh-ssh/README.md:69`、`packages/dsh-ssh/src/tools.ts:194-198`。

**Q: 跳板机能用 `~/.ssh/config` 里的别名吗？**

A: 不能。ProxyJump 链上的每一跳必须是本插件已配置的主机别名（不能跨插件引用 `~/.ssh/config` 里的别名）；导入时若某跳不在本插件主机表中，会在创建时报错跳过。限制见 `packages/dsh-ssh/README.md:71`、`packages/dsh-ssh/src/protocol.ts:30-32`。

**Q: 自动重连会不会重放我的命令？**

A: 会。`exec` 在断线时会自动重连（`keepaliveCountMax=3`），连接重置后命令会重新发送；非幂等命令（如 `rm`、追加写文件）会有副作用，请优先用幂等命令或预先确认。详见 `packages/dsh-ssh/src/engine/connection-pool.ts:72`、`packages/dsh-ssh/README.md:70`。

## 上手难度
入门 — 一行命令安装、重启 `dsh web` 即可见 SSH 入口；主机可在 GUI 里手动填或一键导入 `~/.ssh/config`，无需写配置文件或命令行。但因涉及远程主机、密钥、跳板机，建议先试一两条 exec / 隧道再批量使用。

## 已知问题与限制
- 上传的远程目标路径必须为绝对路径，相对路径会被拒绝（`packages/dsh-ssh/README.md:68`、`packages/dsh-ssh/src/tools.ts:158-162`）
- 下载仅支持单文件；上传支持本地目录递归（`walk` 后逐文件传），下载整目录未实现（`packages/dsh-ssh/README.md:69`）
- `exec` 断线自动重连（`keepaliveCountMax=3`）会重发同一命令，非幂等命令需自行注意副作用（`packages/dsh-ssh/src/engine/connection-pool.ts:72`、`packages/dsh-ssh/README.md:70`）
- ProxyJump 链上的每一跳必须是本插件已配置的主机别名，不能跨插件引用 `~/.ssh/config` 别名（`packages/dsh-ssh/README.md:71`）
- 断点续传（resume）暂未实现；单次上传请求体上限 4 GiB（`packages/dsh-ssh/src/routes.ts:27-28`）
- Agent 工具的传输路径是宿主机器本地路径（与 ssh-skill 同一语义）——`ssh_upload` / `ssh_download` 以宿主进程权限直接读写本机任意路径，不经 bash 沙箱，请注意该权限面（`packages/dsh-ssh/README.md:28`、`packages/dsh-ssh/src/index.ts:67`）
- 主机配置 `~/.dsh/dsh-ssh.json` 以明文保存密码与 passphrase，文件 0600、目录 0700，不做加密（`packages/dsh-ssh/src/store.ts:347-355`、`packages/dsh-ssh/README.md:24`）
- `exec` / `cluster` 远程输出原样返回（不脱敏），`env` 等命令可能带回远端环境密钥（`packages/dsh-ssh/src/index.ts:67`、`packages/dsh-ssh/README.md:29`）
- 本地端口转发隧道只监听 `127.0.0.1`，外部网络无法访问；这是围栏设计而非可配置项（`packages/dsh-ssh/src/loopback.ts:44-62`）
- 主机别名仅允许字母、数字、点 `.`、连字符 `-`、下划线 `_`（首字符不能是分隔符）；不符合的正则会在创建/导入时报错跳过（`packages/dsh-ssh/src/store.ts:62-68、296-303`）
- 配置文件损坏时会被重命名为 `.corrupt-<timestamp>` 备份、随后启动为空表，不会被静默覆盖（`packages/dsh-ssh/src/store.ts:334-342`）

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-web-ui](https://deepseek-plugin.org/plugins/zhu1090093659/dsh-web-ui/packages/dsh-ssh)
Wiki generated by AI (model: `MiniMax-M3`)
