Skip to content

dsh-ssh-manager

Verified

dsh-ssh-manager Β· v0.3.0 Β· MIT Β· Web UI

Manage SSH identity keys and per-host ssh config from the web settings page, so git-over-ssh works in headless server mode.

Install

dsh plugin add dsh-ssh-manager

Confirm the layer applied with dsh --profile default --dump-config β€” see the install guide.

Source

Tags

Creators

Readme

dsh-ssh-manager

A DeepSeek Harness plugin for managing SSH identities from the web settings page, so an unattended agent can git clone git@host:user/repo in headless server mode.

It adds one card to Plugins β†’ Plugin configuration β†’ SSH and turns what you enter there into real ssh files under $DSH_HOME/.ssh (~/.dsh/.ssh when DSH_HOME is unset) β€” always separate from your own ~/.ssh.

Install

Requires Node β‰₯ 20 and pnpm (which dsh plugin forwards to).

dsh plugin --profile web add dsh-ssh-manager        # from npm
dsh plugin --profile web add /path/to/dsh-ssh-manager   # or a local checkout

That installs the package into the profile and appends it to dsh.profile.bundles; the plugin's own cordis.patch.yml inserts its loader row from there. Restart the harness and the card appears β€” no web-app rebuild, and nothing to build in this package (lib/ ships prebuilt and self-contained).

Don't also add a - insert: - id: dsh-ssh-manager row to the profile's user patch: two rows with the same id are a fatal duplicate-entry error.

Alternative: symlink + user patch (for a checkout you edit in place)
ln -s /abs/path/to/dsh-ssh-manager "$DSH_HOME/profiles/<profile>/node_modules/dsh-ssh-manager"

then add the row to the profile's cordis.patch.yml:

- insert:
    - id: dsh-ssh-manager
      name: 'dsh-ssh-manager'

Profiles with patchReload: live pick the patch up without a restart; write it atomically (write tmp + rename) if so.

Using it

  1. Open Plugins β†’ Plugin configuration β†’ SSH.
  2. First time only: the card shows a warning and greys out Add SSH host until ~/.ssh/config imports the managed dir. Add the Include line appends that one line for you, preserving your existing config and its file mode.
  3. Add SSH host β†’ fill in host, URL/HostName, user, paste the private key, add any extra ssh-config options β†’ Save.
  4. git clone [email protected]:you/repo now works from the agent. The public key is derived to <host>.pub, ready to paste into GitHub/GitLab.

Fields. Host is the ssh Host alias, URL / HostName the machine to connect to, User the ssh user (git for GitHub/GitLab), Extra ssh-config options one directive per line (e.g. ProxyJump jump-host).

The private key behaves like a provider API key: write-only, never shown back or sent to the browser. A new host must carry one before it can be saved; editing an existing host shows Configured β€” paste a new key to replace and keeps the stored key if you leave the field blank. Renaming counts as new, since the old entry's key leaves with it.

Validation. A key must start with a PEM header (-----BEGIN OPENSSH PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----, …). Host names must start with a letter or digit and use only letters, digits, ., _, - β€” they double as directory and file names on disk.

What it writes

$DSH_HOME/.ssh/
  config                  flat ssh_config; only a managed Include region is
                          added, everything else is preserved
  config.d/<host>/
    config                the Host block, fenced with managed markers (0600)
    <host>                the private key (0600)
    <host>.pub            the derived public key (0644)

Each host owns its files, so concurrent editors never contend on one file. Removing a host deletes its whole config.d/<host>/ directory. Anything the plugin did not write β€” a seeded default config, known_hosts, manually placed keys β€” is never touched. In the DSH container, /etc/ssh/ssh_config.d/99-dsh-container.conf already Includes $DSH_HOME/.ssh/config, so the managed blocks take effect for git.

Keys live in the settings document as role('secret'): never serialized into a describing response, and written to disk with mode 0600.

Development

npm install       # tsdown + schemastery, build inputs only
npm run build     # host bundle (tsdown) + client wrap
  • src/index.js β†’ lib/index.js β€” host half: schema, validation, and the reconcile that writes and cleans the ssh files. Bundled self-contained (schemastery inlined): the harness imports it with plain Node resolution from wherever it is installed, so a non-bundled copy fails to load. build.mjs refuses to emit one that isn't.
  • src/client/index.js β†’ lib/client.js β€” browser half: the settings card in plain React.createElement (no JSX), wrapped into the loader's closure-factory artifact. package.json's dsh.client declares it.
  • cordis.patch.yml β€” the patch inserting the loader row.

The card talks to the harness through ctx.settingsScope (revision-fenced writes), the built-in client modules (react, @deepseek-ai/dsh-client-*), and one host route for the Include check: GET|POST /dsh-ssh-manager/ssh-import (loopback-guarded, same-origin).

Both halves are committed prebuilt, so keep lib/ in step with src/ β€” the prepublishOnly hook rebuilds and refuses to publish if that changes anything under lib/. Host-side changes need a harness restart; client-only changes apply on a page refresh.

License

MIT β€” see LICENSE.