メインコンテンツへスキップ
A

dsh-ssh-manager

andreasseidl/dsh-ssh-manager

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

インストール

dsh plugin --profile web add github:andreasseidl/dsh-ssh-manager

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 git@github.com: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.jslib/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.jslib/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.

関連プラグイン