DSH Plugins Marketplace

DSH Plugins

Plugins

/

Development & Infrastructure

/

dsh-plugin-safe-upgrade

M

dsh-plugin-safe-upgrade

Manifest valid

Unofficial community plugin for DeepSeek Harness (dsh): safe config history, guarded upgrades, auto-rollback. Not affiliated with or endorsed by DeepSeek.

UI (client)hasBundlePatch

dsh-plugin-safe-upgrade

[!IMPORTANT] Unofficial community plugin. It is not made, endorsed or supported by DeepSeek or the DeepSeek Harness (dsh) team. It only uses dsh's public plugin interface. Report problems in this repository's issues, never to the dsh project. "DeepSeek" and "DeepSeek Harness" belong to their owners and are used here only to say what the plugin works with.

Safe config history, guarded upgrades and automatic rollback for DeepSeek Harness (dsh).

dsh ships release candidates often, and a single wrong row in a cordis.patch.yml can stop it from booting. This plugin makes both boring:

  • Config history. Every edit to the files that decide whether dsh boots (settings.yaml, profile patches and manifests, agent presets, AGENTS.md) is committed to a git repo in $DSH_HOME a few seconds after it happens, including profiles and preset folders created later. Sessions, credentials, caches and node_modules are never tracked, and a secret guard refuses any file that contains a literal key before it is written to git at all.
  • Known-good boots. After every start the plugin checks the plugin loader for failed plugins, plugins that never finish loading, and broken presets. A clean boot tags its config good-<time> along with the dsh version it ran, once per config and version.
  • /upgrade. Upgrades dsh without touching the running service until the last moment. The new version is installed into a side copy, every profile is validated with dsh --dump-config in a throwaway copy of $DSH_HOME, idleness is checked again, and only then does it stop dsh, swap directories and start. If the new version doesn't come back healthy, the previous install and config are restored automatically. Downtime is the swap plus one boot (a few seconds).
  • Your sessions keep working. Before any downtime, the plugin opens a copy of every stored session with both the running and the new dsh. If sessions that open today would not open after the upgrade, the upgrade stops. If sessions were recorded under an agent preset the new version no longer defines (for example a ~/.dsh/.agent-presets folder, which 0.1.7 stopped reading), the plugin adds a legacy copy of the default preset under the old name, so those conversations can still be continued. A rollback warns when sessions continued on the newer version would disappear from an older dsh.
  • Web UI check. After the new version boots, the real web app is loaded in headless Chromium. If dsh shows its "Failed to load plugins" screen, the upgrade rolls back. This catches browser-side plugin failures that the server never sees, such as a client half waiting for a service the new version removed.
  • Warnings, not surprises. Upgrades, dry runs and rollbacks report patches that no longer match anything in the new version and errors that plugins log without failing. Turn on failOnWarnings to treat those as failures: a dry run fails, an upgrade stops before dsh is touched (config warnings) or rolls back (boot warnings), and a manual rollback is undone. Automatic recovery is never undone by warnings.
  • /rollback. Go back to any known-good boot or config snapshot. The target config is validated before dsh is stopped. When the tag was recorded on another dsh version whose install copy still exists, that install is swapped back too.
  • Boot guard (optional). A systemd drop-in validates the config before every start. If dsh fails to start three times, the newest known-good config the machine can return to is restored and dsh is started again. This also catches broken hand edits, not just upgrades. It runs at most once per 15 minutes, so it can't loop, and it runs as the same account as dsh.
  • Optional Telegram alerts for every upgrade, rollback and recovery.

How an upgrade runs

/upgrade ─▶ host half (inside dsh): idle? no job running? ─▶ systemd-run a supervisor unit
                                                              │  (survives the dsh restart)
supervisor ─▶ take the job lock, snapshot + tag pre-upgrade-* │
           ─▶ cp -a install → install.next-<v>, pin @deepseek-ai/* to <v>, npm install
           ─▶ dsh --profile <each> --dump-config in a throwaway $DSH_HOME   (nothing live touched)
           ─▶ open a copy of every session with the old and the new dsh: none may stop opening;
              presets the new dsh lacks get legacy copies, composed with the new version first
           ─▶ still idle? (waits up to idleWaitMs for running turns)
           ─▶ stop dsh, confirm it is down ─▶ install → install.prev-<old>, install.next-<v> → install
           ─▶ write the legacy presets (if any) into the profile patch ─▶ start
           ─▶ wait for HTTP + a fresh boot marker: right version, no failed or stuck plugins
           ─▶ load the web UI in headless Chromium: composer, or "Failed to load plugins"?
           ─▶ collect warnings from the journal
   failure ─▶ stop ─▶ restore install.prev + pre-upgrade config ─▶ start ─▶ verify

dsh --dump-config is not read-only: dsh 0.1.7 rewrites each profile's cordis.yml, normalizes shipped profile manifests and deletes 0.1.5 link projections while it composes. Validation therefore runs against a copy of the config files, with each profile's node_modules linked in read-only fashion (minus the projection dsh would delete), and the live $DSH_HOME is never written by a dry run or a rejected upgrade.

Requirements

  • dsh 0.1.5 or later, installed with npm (the folder whose node_modules holds @deepseek-ai/dsh), running as a systemd service (system or user).
  • Linux with git, npm, cp and systemd-run. Node 22 or later.
  • Optional: Chromium or Chrome for the web UI check. Without it the check is skipped and reported as a warning.
  • The session check copies $DSH_HOME/sessions for the duration of the check, so it needs that much free disk space next to $DSH_HOME.
  • The plugin needs no dependencies of its own.

Config history and the boot health check also work without systemd. Upgrades and rollbacks need it.

Install

git clone https://github.com/MovieMaker93/dsh-plugin-safe-upgrade ~/.dsh/plugins/safe-upgrade
dsh plugin --profile web add link:$HOME/.dsh/plugins/safe-upgrade
sudo systemctl restart dsh          # or whatever your unit is called

dsh plugin add registers the package as a profile bundle, which inserts the safe-upgrade row. On the first boot the plugin starts the config history and tags the boot as good.

Optional boot guard (run once, as the user that owns the unit):

node ~/.dsh/plugins/safe-upgrade/bin/dsh-safe-upgrade.mjs install-guard --unit dsh

Use

In the web UI, type the command and press Enter:

CommandWhat it does
/upgradeLists newer latest/next releases, a dry run, "check now" and the last job. Refuses while a turn is running.
/rollbackLists known-good boots and recent config snapshots.

When a job finishes you get a notice: a browser notification if notifications are allowed, otherwise an in-page toast. From a shell:

dsh-safe-upgrade status
dsh-safe-upgrade upgrade latest --dry-run
dsh-safe-upgrade upgrade 0.1.7-rc.2
dsh-safe-upgrade rollback good-20260928-190522
dsh-safe-upgrade check-ui        # does the web UI actually load right now?
dsh-safe-upgrade sessions        # which sessions fail to open, and which can't be continued
dsh-safe-upgrade sessions --fix-presets   # add legacy presets for sessions whose preset is gone
dsh-safe-upgrade install-guard --unit dsh [--remove]

sessions works on a copy of $DSH_HOME/sessions and never changes a session. --fix-presets composes the legacy presets with the running dsh before it appends them to the profile's cordis.patch.yml; a profile with patchReload: live picks them up without a restart.

The CLI runs jobs in their own systemd unit and follows their log, so you can disconnect safely. Without a global install, run it as node ~/.dsh/plugins/safe-upgrade/bin/dsh-safe-upgrade.mjs <command>. --force skips both idle checks (when the job is requested and right before dsh is stopped).

Configuration

Everything is auto-detected: the unit from the process cgroup, the install directory from the running dsh binary, the health URL from --host/--port, and the profiles from $DSH_HOME/profiles. To override, add a row to your profile's cordis.patch.yml. A patch replaces the whole config, so list every key you want to keep.

- id: safe-upgrade
  config:
    channel: latest              # dist-tag that /upgrade offers first
    failOnWarnings: false
    telegram:                    # optional; credentials are read at send time
      envFile: /etc/dsh/alerts.env
      tokenVar: TELEGRAM_BOT_TOKEN
      chatVar: TELEGRAM_CHAT_ID
KeyDefaultMeaning
unit / scopedetectedsystemd unit and system/user scope
installDirdetectedfolder whose node_modules holds @deepseek-ai/dsh
profilesallprofiles validated with --dump-config
healthUrlfrom --portany HTTP answer except 5xx counts as up (401 is normal)
healthTimeoutMs150000how long a new boot may take
bootTimeoutMs120000how long plugins may stay loading before the boot counts as unhealthy
idleWaitMs300000how long an upgrade or rollback waits for running turns before it gives up without stopping dsh
channellatestdist-tag offered first; tags behind it are hidden
checkIntervalHours6npm dist-tag check interval
keepPrev2previous install copies kept for rollback
keepGoodTags10good-* tags kept
snapshotstrueauto-commit config edits
failOnWarningsfalsetreat warnings as failures (dry runs, upgrades, manual rollbacks)
uiCheckautoauto: roll back on dsh's failure screen, warn if the check can't run; true: also roll back when it can't run; false: skip
chromiumdetectedpath to a Chromium/Chrome binary for the UI check
uiTimeoutMs45000how long the UI may take to show the composer
sessionChecktrueopen every stored session with the old and new dsh before an upgrade; false skips it
legacyPresetstrueadd legacy copies of the default preset for sessions whose preset the new dsh lacks; false stops the upgrade instead
telegramoff{envFile?, tokenVar?, chatVar?}

What is tracked

The plugin records and restores these files only, whatever .gitignore says:

.gitignore  settings.yaml  AGENTS.md  cordis.patch.yml  .agent-presets/**
profiles/*/{package.json,pnpm-lock.yaml,pnpm-workspace.yaml,cordis.yml,cordis.patch.yml}

(node_modules, *.bak* and *.tmp-* are always excluded.) When the plugin creates the repo it writes a matching whitelist .gitignore so git status stays readable; an existing repo and .gitignore are adopted unchanged, and anything the owner committed outside the list is never rolled back.

Snapshots never run git add. Each file is read once, scanned, and exactly those bytes are committed, so a file that looks like it holds a literal secret, or is over 2 MiB, keeps its last recorded version and never reaches git's object store. When a rollback replaces a file whose current content is not in history (for example one skipped for a secret), a copy goes to $DSH_HOME/safe-upgrade/restore-backups/ first.

Safety notes

  • Upgrades run as the user that runs dsh (often root). The HTTP routes use dsh's own authentication (connection.requestRejection). Upgrade and rollback return 409 while any turn is running or another job holds the lock. The lock is taken atomically, so two jobs can never run at once.

  • Idleness is checked again right before dsh is stopped, since staging can take minutes. dsh has no stable way for a plugin to hold new turns, so a turn that starts during the systemctl stop call itself is still cut off.

  • If dsh cannot be stopped, the job aborts before any install or config change.

  • dsh running as an unprivileged system unit (User= set): the supervisor and the boot guard's recovery run as that same account, never as root (root must not execute plugin code the service account can write). That account then needs permission to manage its own unit and start transient dsh-safe-upgrade-* units, for example with a polkit rule like the one below (a starting point; not tested here, where dsh runs as root). Re-run install-guard whenever you change the unit's User=.

    // /etc/polkit-1/rules.d/50-dsh-safe-upgrade.rules
    polkit.addRule(function (action, subject) {
      if (action.id === "org.freedesktop.systemd1.manage-units" && subject.user === "dsh") {
        var unit = action.lookup("unit") || ""
        if (unit === "dsh.service" || unit.indexOf("dsh-safe-upgrade-") === 0) return polkit.Result.YES
      }
    })
    
  • Each previous install copy is the size of your dsh install (about 300 MB). Two are kept by default.

  • Rollback restores tracked config only. It never touches sessions, credentials, attachments or anything outside $DSH_HOME and the install directory.

  • testing: true enables fault injection (simulateFailure) through the API. Leave it off in production.

Found while upgrading a real install from 0.1.5-rc.1 to 0.1.7-rc.2

These are what the checks are for:

  • The web UI stopped loading while the server looked healthy. Smart-DSH's dsh-esc-stop browser half waits for the settingsScope service, which 0.1.7 removed, so the page stops at "Failed to load plugins — dsh-esc-stop: pending (waiting for service: settingsScope)". Every host plugin was active, so only the web UI check catches this. It was added after this exact incident and verified against a real 0.1.7 instance.
  • patch: entry "agent-presets" not found: 0.1.7 replaced directory-based agent presets ($DSH_HOME/.agent-presets/*) with agent-preset-registry and preset-* rows. A patch that targeted agent-presets is silently ignored, and custom preset folders are no longer read.
  • Old conversations could not be continued. A session resumes only under the preset it was recorded with, so 94 sessions created with a .agent-presets/standard-tools folder failed with Unknown agent preset: standard-tools, and dsh refuses to switch a started session to another preset. The session check and legacy presets exist for this (reported upstream in discussion #8320).
  • Some subagent transcripts cannot be opened at all. Sessions written in format v0 by dsh 0.0.1-rc.1 through 0.1.1-rc.2 carry a subagent record that 0.1.5 and later refuse (uses unsupported descriptor version 2). The session check reports them but does not count them against an upgrade, because the running version already can't open them.
  • [esc-stop] settings registration failed TypeError: settingsCtx.settings.register is not a function: the host settings API changed too. Plugins built for 0.1.5 log this error instead of failing.
  • On first boot 0.1.7 moves settings.yaml into the booted profile's cordis.patch.yml (keeping settings.yaml.imported). The config history records it as one "boot: config at startup" commit, and a rollback brings settings.yaml back. Other profiles, such as headless, don't get the imported model providers.

Development

npm test                                         # unit suites: engine, repo, host, client, systemd, UI check
scripts/install-dsh.sh latest /tmp/dsh           # a real dsh from npm, for the regression suite
DSH_E2E_INSTALL=/tmp/dsh DSH_E2E_UPGRADE_TO=next npm run test:e2e

The tests put stub systemctl, npm, systemd-run, journalctl and dsh binaries on PATH. The stub dsh --dump-config writes into $DSH_HOME the way 0.1.7 does, so the tests prove validation never touches the live home. They cover the real upgrade, rollback and recovery code paths, including failures at every stage (half-finished swaps, a stop that fails, concurrent jobs, a turn starting mid-upgrade), without touching a live service.

The regression suite (tests/e2e/) uses no stubs. It boots a real dsh from npm with this plugin linked into a throwaway profile, then checks the boot marker, the known-good tag, the authenticated routes, the web UI in headless Chrome and a live config snapshot. It proves --dump-config validation leaves the live $DSH_HOME byte-for-byte unchanged, and dry-runs a real upgrade (npm pins and all) to the next dsh release.

CI/CD

WorkflowWhenWhat
ci.ymlevery push and PRconventional-commit lint, syntax and package contents, unit suites on Node 22 and 24, regression suite against dsh latest
e2e.ymlnightly, and on demandregression suite against dsh latest (plus a dry-run upgrade to next) and next, so a dsh release that breaks the plugin is caught before you upgrade
pr-title.ymlPRsthe PR title (the squash-merge commit) is a conventional commit
release.ymlpush to mainrelease-please keeps a release PR with the next version and changelog; merging it tags vX.Y.Z, publishes the GitHub release and attaches the npm pack tarball

Dependabot keeps the workflow actions current.

Commits and releases

Commits follow Conventional Commits: feat: for a new capability (minor release), fix: for a bug fix (patch), feat!: or a BREAKING CHANGE: footer for a breaking change. docs:, test:, ci:, refactor: and chore: don't cut a release by themselves. Squash-merge PRs so the PR title becomes the commit on main.

Nobody edits the version or CHANGELOG.md by hand: release-please does both in its release PR. The UI check test drives a real headless Chromium when one is installed.

License

MIT. An unofficial community project, provided as-is with no warranty (see LICENSE). Not affiliated with DeepSeek.

Comments

Loading…

From the same category

awesome-dsh-plugin

by awesome-dsh-plugin

A curated list of plugins for DeepSeek Harness (dsh) · DeepSeek Harness 插件精选列表

Development & Infrastructure

★ 17.7k

CC0-1.0

Python

Oct 1, 2026

Index only — not installable

by 0xsline

DeepSeek Harness (DSH) ecosystem: curated plugins, tools, and infrastructure from dsh-external/hub and the public dsh-plugin topic.

Development & Infrastructure

★ 1.1k

CC0-1.0

Python

Sep 30, 2026

Index only — not installable

by pax-beehive

Open-source CLI, schemas, resolver, and DSH agent tools for DSH Plugin Hub

Development & Infrastructure

★ 458

MIT

TypeScript

Sep 22, 2026

Index only — not installable

by yjh051108

推荐组件(非必须):DeepSeek Harness 运行时注入器;已随 dsh-routing-suite 单仓库化保留,本仓库继续维护/发布。

Development & InfrastructureManifest valid

★ 167

TypeScript

Sep 18, 2026

dsh plugin --profile web add @dsh-external/dsh-super-injector

by xiajiajun516

DeepSeek Harness (DSH) backup & restore plugin — export, import, migrate and sync your complete DSH configuration, plugins, MCP servers, skills and workspace. One-click migration to another machine.

Development & InfrastructureManifest valid

★ 150

MIT

TypeScript

Sep 30, 2026

dsh plugin --profile web add dsh-config-manager

by jigjoy-ai

A CLI that turns a goal into a pull request - and a sandbox for testing concurrent AI coding agents on the Mozaik runtime.

Development & Infrastructure

★ 124

MIT

TypeScript

Oct 2, 2026

Index only — not installable