dsh-ssh-remote
ZCode-style remote workspaces for DeepSeek Harness (dsh): SSH, WSL and Docker.
The remote dsh runs as a persistent systemd service (or setsid session) inside the SSH host, WSL distribution or Docker container, and remote sessions are executed by a detached dsh --profile headless runner there. The local app selects the workspace, shows the native chat, and monitors status; there is no local console proxy. Closing the local app does not stop the remote dsh.
Target: DeepSeek Harness Desktop 0.2.1-rc.5. Validated end-to-end against 203.0.113.10 (AlmaLinux 9.5).
Screenshots
The Add-connection dialog is a 1:1 rebuild of ZCode's renderer components (OVn / xVn / yVn / nVn / Smt / rmt / hmt / Cmt): the same DOM structure, spacing, type scale and ZCode design tokens (light palette by default, dark palette under body[data-ds-dark-theme]), with device-first interaction:
- saved device: click connects immediately; edit it from Settings;
- recent workspace chips under each device open the native workspace in one click;
- directory step: connected banner, device rename, recent paths and Will open …;
- minimize is available on every step; connecting/creating continues in a ZCode-style background card.
| New connection (Settings → Remote devices) |
SSH config import |
|
|
| Create project (device picker) |
Select folder |
|
|
The three surfaces — new connection, create project and the centered folder picker — are rebuilt from the 3.17.2 renderer with the same DOM structure, class values, type scale and copy as ZCode. The screenshots use documentation-range addresses (192.0.2.0/24, 203.0.113.0/24).
Features
| Area |
What it does |
| Targets |
SSH (password / private key / passphrase / agent / keyboard-interactive), WSL distributions on Windows (wsl.exe --exec bash -lc, default or named distro/user) and Docker containers (docker exec -i ... sh -lc, name/id/prefix, must be running) |
| SSH hosts |
TOFU host-key check; connection test with latency and remote Node detection |
| Add connection |
1:1 ZCode device flow: wizard steps 选择设备 / 连接 / 选择目录, 已保存的设备, SSH config 中的 Host, WSL 发行版 and 运行中的容器 sections, plus 新建 SSH 连接 / 手动输入 WSL / 手动输入 Docker; the settings step uses ZCode's exact per-kind forms (WSL distribution datalist + Linux user hints/root warning; Docker select/manual picker); minimize is available on every step and parks the wizard in a ZCode-style background card |
| Recent workspaces |
clicking a recent path opens that native workspace directly, including from the status panel; a stopped service is started in the background |
| Background connect |
both connecting and workspace creation can continue in the background; a bottom-right pill shows progress and opens the workspace on completion, and reopening it resumes the wizard |
| SSH config import |
parses ~/.ssh/config Host blocks and fills host, port, username and IdentityFile |
| One-click create |
the connect phase installs/reuses the shared dsh runtime; the directory step then only starts the service. Full chain: channel (SSH/WSL/Docker) → Node ≥ 22 (portable download when absent) → @deepseek-ai/dsh → credentials → launcher → systemd/setsid → health check |
| Persistence |
systemd Restart=always (system unit, or user unit + linger) or setsid nohup + @reboot cron. Survives SSH disconnect and local app exit |
| Status monitor |
running / starting / stopped / error / unreachable; PID, uptime, RSS, CPU, HTTP status, port, last check. CPU is sampled from /proc/<pid>/stat over 0.5 s and RSS from /proc/<pid>/status (no lifetime-average or fabricated values) |
| Native workspace |
each remote workspace is projected into the native sidebar; its agent is bridged to a detached remote headless session; device chips and the status panel select that same native row |
| Lifecycle |
start, stop, restart, logs (journalctl/tail), remove (service + runtime dir only; project dir kept) |
| Credentials |
optionally copies local $DSH_HOME/.credentials.yaml (0600) into each remote workspace's own DSH home |
| Legacy import |
imports hosts from ZCode's ~/.dsh/dsh-ssh.json on first load |
The local side stays a monitor and chat surface: the agent process, tools and files live on the server.
Install
After DeepSeek Harness has started at least once:
node C:workdsh-ssh-remote\bin\install.mjs --profile desktop
The script adds a link: dependency and the bundle to ~/.dsh/profiles/desktop/package.json, then creates a junction in the profile's node_modules. Fully restart DeepSeek Harness afterwards.
Manual install: add
"dsh-ssh-remote": "link:C:/work/dsh-ssh-remote"
to dependencies, append "dsh-ssh-remote" to dsh.profile.bundles, and create
cmd /c mklink /J "%USERPROFILE%\.dsh\profiles\desktop\node_modules\dsh-ssh-remote" "C:workdsh-ssh-remote"
Use
- Settings → Remote devices → add a host (host/port/user/password or key) → Test connection → Save. The wizard can also create it: New SSH connection.
- SSH Remote sidebar → New remote workspace (or Add workspace → SSH Remote tab). Saved devices are listed first; clicking one connects immediately. Up to three recent workspace paths appear under each device — click one to open that native workspace directly. The pencil opens the SSH form for editing.
- Connecting shows the SSH/runtime phases and can be sent to the background; a bottom-right pill keeps updating and opens the workspace when done. The directory step shows the connected banner, device rename, recent paths and Will open ….
- Open workspace here creates the service, closes the wizard and selects the new native workspace. The first create installs dsh on the host (about 1 minute, ~844 MB); later creates reuse the runtime.
- Open workspace on the status panel, a recent-path chip, or the native sidebar row all land on the same workspace. Stop / Restart / Logs / Remove are on the status card.
- Close DeepSeek Harness. The remote service keeps running. Reopen and the status panel reattaches to the same PID.
Data
Local: $DSH_HOME/ssh-remote/state.json (hosts, workspaces, settings; mode 0600), known_hosts.json.
Remote: ~/.dsh-remote/runtime/dsh-<version>/, ~/.dsh-remote/workspaces/<id>/{home,logs,run}/.
The remote Web listener binds 127.0.0.1 only; no LAN port is opened.
Verified on 203.0.113.10
- AlmaLinux 9.5, x86_64, 8 vCPU, 31 GB RAM, Node v22.23.2, systemd 252.
- Remote install of
@deepseek-ai/dsh@0.2.1-rc.5 in ~1 minute.
- Service prints
dsh web: ...?token=...; an unauthenticated HTTP request without the token returns 401 (used as readiness evidence only).
- Real metrics verified against
ps: RSS matches VmRSS in /proc/<pid>/status; CPU is a 0.5 s /proc/<pid>/stat delta, not the misleading lifetime pcpu average.
- Interaction chain verified end-to-end: saved-device click connects (364 ms probe) → runtime prepare hits cache → directory step → create → the new native workspace opens automatically.
- Recent-path chip and status-panel Open workspace select the same native workspace; Continue in background keeps updating the bottom-right pill, which opens the workspace when the job completes.
- After a workspace is removed outside the UI, the startup native projection drops the orphaned anchor row (no ghost remote project).
- After closing the local app for 10 s: same remote PID, uptime kept increasing,
systemctl is-active = active.
- Reopening the local app restored the same PID and continuous uptime.
- Stop / start / restart, logs, and remote model listing (
loaded 2 model(s)) all work.
- root services log to journal, not a file under
/root, to avoid SELinux 209/STDOUT on RHEL-family hosts.
- SSH/WSL/Docker share one command layer; on the development machine (no WSL optional feature, no Docker daemon) the WSL/Docker discovery and error paths were verified end-to-end in the UI (
当前系统未检测到可用的 WSL 环境 / 当前系统未检测到可用的 Docker 环境), including the per-kind forms and badges. Full deploy inside a live WSL distribution or container still needs a host that provides one.
Limitations
Linux remotes only: WSL distributions and Docker containers must provide a Linux bash/sh, Node ≥ 22 or a way to download the portable runtime (curl, wget or an existing Node).
WSL requires a Windows desktop with the WSL optional feature installed; Docker requires a running daemon and a running container.
The remote host needs access to an npm registry (default npmmirror).
Credentials are synced, but local model selection/settings are not yet mapped to the remote headless runner.
Background connect progress is in-memory: restarting the local app drops the pill (created workspaces are restored from state.json); there is no cancel button for an in-flight install/create.
No local mirror of remote project files; the agent and tools run inside the target.
Docker workspaces do not survive removal of their container; WSL workspaces survive until the distribution is terminated/unregistered.
WSL2 HTTP proxy auto-rewriting (ZCode maps a loopback proxy to the Windows host gateway) is not implemented; use a reachable registry/proxy inside the distribution.
Uninstall
node C:workdsh-ssh-remote\bin\uninstall.mjs --profile desktop
This removes the local link/dependency selection only. Delete remote workspaces from the UI to stop their services.
Reverse-engineering notes
See docs/zcode-ssh-reverse-engineering.md for the ZCode 3.17.2 SSHBackend / WSL / Docker backends, remote asset deployment, the 3.17.2 connect wizard (stages, install confirmation, error card, log panel), the device-management "新建连接" dialog, the "创建项目" dialog and the embedded "选择文件夹" picker, plus the rea evidence summary.
License
MIT