# dsh-ui-web

> 为 DSH Web GUI 加入完整 SSH 运维：主机管理、Web 终端、SFTP 传输、本地端口转发隧道、集群并发执行，以及 6 个 Agent 工具，配置集中存于本机。

## Metadata

- Author: [@CAPTAIN1275](https://github.com/CAPTAIN1275)
- Repo: <https://github.com/CAPTAIN1275/dsh-ui-web.git>
- GitHub: [CAPTAIN1275/dsh-ui-web](https://github.com/CAPTAIN1275/dsh-ui-web)
- Stars: 34
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Topics: `dsh-plugin`, `dsh-plugin-market`, `dsh-plugins`
- Forks: 2
- Open Issues: 0
- Last push: 2026-08-16T18:08:27.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

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

## Wiki

## One-Line Positioning
Adds complete remote SSH operations capability to the DSH Web GUI: centrally manage multiple remote hosts from the web (CRUD, search, connection testing, one-click import from `~/.ssh/config`), reuse persistent ssh2 connection pools within the host process to execute commands, run Web terminals, transfer SFTP files, open local port forwarding tunnels, and concurrently run the same command across multiple hosts; also provides 6 tools for the Agent, sharing the same host configuration.

## Core Features
- Host CRUD, search, connection testing; supports key / password authentication, passphrase-protected keys, multi-level ProxyJump jump hosts (each hop must be an alias already configured in this plugin)
- One-click import from `~/.ssh/config`: parses Host / HostName / User / Port / IdentityFile / ProxyJump fields, skips existing aliases (wildcard mode / skips if HostName missing)
- Persistent ssh2 connection pool: each host reuses one long connection (no reconnect each time), auto-disconnects after 30 minutes idle, auto-reconnect on disconnect (max 3 times)
- Command execution: single host exec with timeout (default 60s), stdout / stderr separation, 2MB truncation protection per output
- Web terminal (xterm.js + WebSocket PTY, backpressure auto-pause / resume), SFTP file transfer (browser file selection → NDJSON progress stream), local port forwarding tunnel (listens only on 127.0.0.1, access remote DB / intranet services), cluster concurrent execution (default concurrency 8, filterable by alias / env / tag)
- Agent tools `ssh_list` / `ssh_exec` / `ssh_upload` / `ssh_download` / `ssh_tunnel` / `ssh_cluster`, GUI and Agent share the same host configuration

## Technical Implementation
- **Language**: TypeScript (host half `src/index.ts`) + TypeScript + React 18 + CSS Modules (browser half `src/client/`)
- **Key Dependencies**: `ssh2` ^1.17.0 (persistent ssh2 connection pool + SFTP), `@xterm/xterm` ^6.0.0 + `@xterm/addon-fit` ^0.11.0 (browser terminal), `ws` ^8.18.0 (WebSocket PTY)
- **Architecture Pattern**: Dual-half cordis bundle. `src/index.ts` is the host half (`SshEngine` connection pool + `/api/dsh-ssh/*` routes + 6 Agent tools + system prompt declaration), `src/client/index.ts` is the browser half (sidebar entry + host management / terminal / transfer / tunnel / cluster panels + locale registration). Bundle declaration in `packages/dsh-ssh/cordis.patch.yml:10-12` (inserts id `ssh`), browser side injects 3 official `@deepseek-ai/dsh-client-*` via `dsh.client.inject` (`client-runtime` / `client-connection` / `client-ui-settings`) + `platform: web`. Connection pool default parameters: 30 min idle, 15s handshake timeout, 15s keepalive interval, 2MB output cap, 60s exec default timeout, cluster concurrency 8, SFTP concurrency 8 (`src/engine.ts:33-41`)
- **Entry Files**: `packages/dsh-ssh/src/index.ts` (host apply), `packages/dsh-ssh/src/client/index.ts` (browser apply); bundle declaration `packages/dsh-ssh/cordis.patch.yml:1-12`, browser dependencies `packages/dsh-ssh/package.json:31-39`

## Use Cases
- Developers who need to manage multiple SSH servers daily (dev/test/prod mixed): want to centrally browse hosts, execute commands, transfer files, open tunnels to access remote DB / intranet services in DSH Web GUI, saving tool switching;
- Developers who want Agents to directly execute remote operations tasks: pre-configure hosts in GUI (manual entry or import from `~/.ssh/config`), then Agent can call the same host configuration via `ssh_list` / `ssh_exec` / `ssh_upload` / `ssh_download` / `ssh_tunnel` / `ssh_cluster`;
- Users with existing `~/.ssh/config`: use "Import ssh_config" to one-click sync existing SSH aliases to the plugin, no duplicate host/port/user/identityFile entry needed.

## Prerequisites & Compatibility
| Dependency | Min Version | Description |
|---|---|---|
| DSH Host | `0.1.0-rc.6+` | peerDependencies locked to `@deepseek-ai/dsh-* ^0.1.0-rc.6` |
| Node.js | `^22.19.0` or `>=24.0.0` | package.json `engines.node` |
| Platform | macOS / Windows / Linux | host half runs Node.js, client half runs browser; ssh2 is pure JS library, no OS restriction |
| Native Modules | None (runtime) | ssh2 needs no native bindings; upload/download uses ssh2's built-in SFTP |
| React | `^18.2.0` | peerDependency, injected by host runtime |
| SSH Server | Any standard OpenSSH | host half acts as ssh2 client, no server version dependency |

## Installation
```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/dsh-ssh
```

## Configuration Options
This plugin provides 2 toggles in the "SSH" section of the dsh settings panel:

| Config | Type | Description | Default |
|---|---|---|---|
| enabled | toggle | Plugin master switch; when off, does not register `/api/dsh-ssh/*` routes, does not register Agent tools, does not announce to Agent | true |
| announceToAgent | toggle | Whether to announce this plugin in system prompts (when off, Agent cannot see SSH tools) | true |

There are also some hardcoded constants not exposed to GUI in the engine (`src/engine.ts:33-41`): 30 min idle timeout, 15s handshake timeout, 15s keepalive interval, 3 keepalive retry limit, 2MB output cap, 60s exec default timeout, cluster default concurrency 8, SFTP concurrency 8; upload single request body cap 4 GiB, WebSocket terminal backpressure 1MB/512KB (`src/routes.ts:23-39`). Adjusting requires code changes and rebuild.

## FAQ

**Q: After installation, the SSH entry doesn't appear in the sidebar. What should I do?**

A: Restart `dsh web`. Bundle activation is done once at host startup; refreshing the browser is not enough. After restart, the "SSH" entry appears in the sidebar, and the Agent prompt will automatically include the plugin description (controlled by the `announceToAgent` toggle).

**Q: How are passwords stored? Is it secure?**

A: Stored in plaintext at `~/.dsh/dsh-ssh.json`, file permission 0600, directory permission 0700, atomic write (tmp + rename). This is the same trust model as ssh-skill storing passwords in `~/.ssh/config` comments - since `~/.ssh/config` often stores passwords as comments too, this plugin does not add extra encryption. File path and permissions in `src/store.ts:18-20`, `src/store.ts:328-336`.

**Q: Can others in the LAN access my SSH console?**

A: No. All `/api/dsh-ssh/*` routes only trust local loopback connections (socket address must match IPv4 127/8 / `::1` / IPv4-mapped `::ffff:127/8`, plus same-origin Host header check, `sec-fetch-site` / Origin), LAN neighbors get direct 403. WebSocket terminal upgrade also goes through the same loopback fence; local port forwarding tunnel only listens on `127.0.0.1`, never exposed to external network. See `src/routes.ts:42-62`, `src/engine.ts:709`.

**Q: Can the Agent execute commands on hosts I haven't configured?**

A: No. The Agent can only operate on host aliases already configured in GUI or imported from `~/.ssh/config`. Tools like `ssh_exec` / `ssh_upload` will directly error if the alias is not configured, never fabricate hosts. This agreement is also written in the system prompt "Constraints" section (`src/index.ts:58`) and README.

**Q: Will remote output be sanitized? Like `env` command?**

A: No. stdout/stderr from `exec` / `cluster` is returned as-is (no secret replacement), commands like `env` may bring back remote environment variables with secrets into the conversation history. This is the same semantics as ssh-skill - transmission/execution consumes real remote resources, user confirmation needed before Agent uses.

**Q: Can I download whole directories? How about upload?**

A: Download only supports single file (select file in the remote browser in the "Transfer" panel, then go through `/api/dsh-ssh/download`); upload supports local directory recursion (files transferred one by one after `walk`), whole directory download not implemented. Limitations in `src/engine.ts:519-533`, `README.md:74`.

**Q: Can I use aliases from `~/.ssh/config` for jump hosts?**

A: No. Each hop in the ProxyJump chain must be a host alias configured in this plugin (cannot cross-plugin reference `~/.ssh/config` aliases); when importing, if a hop is not in this plugin's host table, it will error and skip during creation. Limitation in `README.md:76`, `src/store.ts:240-260`.

**Q: Will auto-replay replay my commands?**

A: Yes. When `exec` disconnects, it auto-reconnects (max 3 times), command will be resent on new connection; non-idempotent commands (like `rm`, appending to files) will have side effects, please use idempotent commands first or confirm before executing. See `src/engine.ts:212-231`, `README.md:75`.

## Getting Started Difficulty
Beginner - one command install, restart `dsh web` to see SSH entry; hosts can be manually entered in GUI or one-click imported from `~/.ssh/config`, no config files or command line needed. However, since it involves remote hosts, keys, and jump hosts, recommended to try 1-2 exec/tunnel commands before batch usage.

## Known Issues & Limitations
- Upload remote target path must be absolute path, relative paths are rejected (`src/engine.ts:486-490`, `README.md:73`)
- Download only supports single file; upload supports local directory recursion (files transferred one by one after `walk`), whole directory download not implemented (`README.md:74`, `src/engine.ts:519-533`)
- `exec` auto-reconnect on disconnect (max 3 times) will resend same command, non-idempotent commands need to pay attention to side effects (`src/engine.ts:212-231`, `README.md:75`)
- Each hop in ProxyJump chain must be a host alias configured in this plugin, cannot cross-plugin reference `~/.ssh/config` aliases (`README.md:76`)
- Resume (断点续传) not yet implemented; single upload request body cap 4 GiB (`src/routes.ts:23-26`)
- Agent tool transfer paths are the host machine's local paths (same semantics as ssh-skill) - `ssh_upload` / `ssh_download` directly read/write any local path with host process permissions, not through bash sandbox, please note this permission surface (`README.md:30-31`, `src/index.ts:58`)
- Host configuration `~/.dsh/dsh-ssh.json` stores passwords and passphrases in plaintext, file 0600, directory 0700, no encryption (`src/store.ts:328-336`, `README.md:25-26`)
- `exec` / `cluster` remote output returned as-is (not sanitized), commands like `env` may bring back remote environment keys (`src/index.ts:58`, `README.md:32-33`)
- Local port forwarding tunnel only listens on `127.0.0.1`, external network cannot access; this is fence design, not a configurable option (`src/engine.ts:709`, `src/routes.ts:42-62`)
- Host aliases only allow letters, numbers, dots `.`, hyphens `-`, underscores `_` (first character cannot be separator); non-conforming regex will error and skip during creation/import (`src/store.ts:62-68`, `src/store.ts:296-303`)
- When config file is corrupted, it is renamed to `.corrupt-<timestamp>` backup, then starts with empty table, will not be silently overwritten (`src/store.ts:317-325`)

---

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