# dsh-backup

[![dsh-plugin](https://img.shields.io/badge/ecosystem-dsh--plugin-8b5cf6)](https://github.com/topics/dsh-plugin)
[![npm](https://img.shields.io/npm/v/@xiaoyuyu6420/dsh-backup)](https://www.npmjs.com/package/@xiaoyuyu6420/dsh-backup)
[![Downloads](https://img.shields.io/npm/dw/@xiaoyuyu6420/dsh-backup)](https://www.npmjs.com/package/@xiaoyuyu6420/dsh-backup)
[![Publish to npm](https://github.com/xiaoyuyu6420/dsh-backup/actions/workflows/publish.yml/badge.svg)](https://github.com/xiaoyuyu6420/dsh-backup/actions/workflows/publish.yml)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Stars](https://img.shields.io/github/stars/xiaoyuyu6420/dsh-backup?style=flat&color=f5c518)](https://github.com/xiaoyuyu6420/dsh-backup/stargazers)
[![Listed on DSH Directory](https://dsh.directory/badges/listed.svg)](https://dsh.directory/plugins/xiaoyuyu6420/dsh-backup)

English | [简体中文](README.md)

**Your entire DeepSeek Harness (DSH) workspace lives in one folder: `~/.dsh`. One failed upgrade, one accidental delete, one new laptop — without a backup, sessions, settings and skills are all gone. dsh-backup gives them back with one command.**

```sh
dsh plugin --profile web add @xiaoyuyu6420/dsh-backup   # install
# restart dsh web, then type:
/backup                                                  # → a verified archive lands in ~/Desktop/DSH Backups/
```

Real output from a fresh v0.9.0 install:

```text
备份完成: dsh-20260826-195150036.tar.gz
sha256: 8f9ae6322ef782d21554981cf4547220d5bb3e64d7964a883317415ad54e3cbb
轮换删除 0 份（保留 7 份）
```

Prefer clicking? There's a visual panel in `dsh web` → Settings → Plugins → Backup: list, verify, restore, delete, edit settings — no restart.

![Backup panel](docs/assets/panel-backups.png)

**Three promises** (full parity & delivery standard in [ALIGNMENT.md](docs/ALIGNMENT.md)):

- 🔐 **Credentials never leave this machine** — API keys / tokens are redacted by default; plaintext lives only in the local vault. For more, opt into password-encrypted backups (AES-256-GCM).
- ↩️ **Restore never overwrites silently** — the current data is auto-snapshotted and the write-back is previewed first; a failed restore rolls back automatically.
- 🧭 **You'll know what to do from the start** — a three-step onboarding plus one plain-sentence explanation per advanced feature; three tabs (Backup / Restore / Settings) with a single primary action: "Back up now".

## Why you want this

| Fear | What dsh-backup does about it |
|---|---|
| "An upgrade broke my setup" | Auto-takes a `dsh-pre-upgrade-` snapshot the moment the host version changes — try the new release, roll back if it bites |
| "I deleted / broke something" | `/backup restore latest --dry-run` previews exactly what comes back; a failed restore auto-rolls-back and shows a result receipt |
| "DSH won't even boot anymore" | Every backup drops a zero-dependency **rescue console** (`dsh-rescue` / `rescue.mjs`, or double-click「点我恢复」) next to the archives — a web UI that restores outside of DSH |
| "My API keys will end up in a cloud backup" | Credentials are redacted from archives by default; plaintext only ever lives in a local vault on your machine |
| "My session logs got corrupted" | `/backup doctor` scans and repairs session logs from a known-good backup; corrupt files are quarantined before they rotate away |
| "Old sessions won't open after an upgrade" | `/backup migrate-check` statically predicts which sessions the new host will refuse (and under which rule) before you upgrade; the credential sentinel preserves pre-migration copies of legacy `.credentials.yaml` |
| "I got a new machine" | GitHub sync: `/backup github pull` fetches remote archives, `restore --sync-deps` reinstalls plugins |
| "Backups rot silently" | Every archive ships a sha256; `/backup verify all` checks integrity; daily/weekly tiered retention keeps useful history longer |
| "I'll forget to back up" | `/backup auto 12` — every 12 hours, survives restarts, rotates old copies (default keep 7) |
| "A new machine means reinstalling everything" | Single-file ZIP **export/import** + local `link:`/`file:` plugin migration; sync over WebDAV / S3-compatible / Gist too |
| "My config lives in another AI tool" | Import **skills & global instructions** from Claude Code / Cursor / Codex after a preview (MCP detected and reported only) |
| "I want to go back to an earlier point in a conversation" | Conversation **rewind fork**: branch at a chosen turn while the original stays intact |
| "Backups are eating my disk" | **Disk-usage report** + tiered cleanup: only regenerable caches and expired backups; snapshots and sync data are never touched |
| "I want a ready-made community config" | **Config market**: index validation, preview install, supply-chain notes — nothing is written until you confirm |

![Backup settings](docs/assets/panel-settings.png)

## Install

Requires macOS / Linux / Windows 10+ (ships `tar`) and DSH `0.1.1-rc.2`+ (the 0.1.1 / 0.1.2 / 0.1.5 / 0.1.6 / 0.1.7 trains are all tested; the 0.2.0 and 0.2.1 trains are supported since 0.13.2 / 0.14.0 — see the [compatibility matrix](docs/compatibility.md) for the current status).

```sh
dsh plugin --profile web add @xiaoyuyu6420/dsh-backup
# or straight from GitHub:
dsh plugin --profile web add github:xiaoyuyu6420/dsh-backup
```

Restart `dsh web` afterwards — the plugin only takes effect after a restart.

> The installer may print `✕ missing peer @deepseek-ai/...` warnings. These are
> expected: the peer packages are provided by the DSH host at runtime. As long
> as the command ends with `Done`, the plugin is installed.

## Quickstart

1. Install (above) and restart `dsh web`
2. Type `/backup`
3. Done — the archive lands in `~/Desktop/DSH Backups/`, timestamped, with a `.sha256` next to it

Want it on a schedule? `/backup auto 12` (every 12 hours; `off` stops it, `status` checks it).

## Command cheat sheet

| Task | Command |
|---|---|
| Back up now | `/backup` |
| Typed backup (selected types only) | `/backup --types skills,sessions` (types: `credentials`·`mcp`·`skills`·`sessions`·`settings`·`profiles`; `--only` works too) |
| Schedule (survives restarts) | `/backup auto 12` · `off` · `status` |
| Restore (preview first) | `/backup restore latest --dry-run` |
| Restore for real | `/backup restore latest` |
| Typed restore (merge, other types untouched) | `/backup restore <archive> --types skills` |
| List backups | `/backup list` |
| Verify integrity | `/backup verify [prefix\|all]` |
| Check & repair session logs | `/backup doctor` · `--repair [prefix\|latest]` |
| **Pre-upgrade migration precheck** | `/backup migrate-check` |
| **Rescue when DSH won't boot** | double-click「点我恢复」in the backup dir, or `dsh-rescue` / `node rescue.mjs` |
| Delete / retention | `/backup delete <prefix\|latest>` · `/backup --keep N` (default 7) |

## Typed backup

Only need certain kinds of data? Use `--types` (or `--only`) to operate on a subset. Available types: `credentials` (API keys), `mcp` (MCP config), `skills`, `sessions`, `settings`, `profiles`.

- **Back up**: `/backup --types skills,sessions` creates a `dsh-t-` subset archive; rotation is tracked separately from full backups
- **Restore**: `/backup restore <archive> --types skills` merges only skills back into your existing `~/.dsh` (preview with `--dry-run`; overwritten files are kept aside as `.pre-merge-*`). Everything else stays untouched.
- **Credentials caveat**: `--types credentials` puts API keys into the archive **in plaintext** (full backups redact them). Such archives never go to GitHub sync — keep them local or copy them to a new machine yourself.
- **Guardrail**: restoring a typed archive without `--types` is rejected (prevents accidental data loss); the rescue channel likewise won't list or fully restore them.
- The Settings panel supports this too: check types under the backup button; typed archives get their own section.

## New machine

Prerequisite: GitHub sync was configured on the old one ([setup](docs/advanced.zh.md#github-同步可选), Chinese).

1. Install the plugin, set the same `githubRepo`
2. `/backup github pull` — fetch the remote backups
3. `/backup restore latest --sync-deps` — restore and reinstall plugin dependencies
4. Restart `dsh`

## FAQ

**Are my API keys / credentials inside the archive?**
No. Known credential files are redacted before archiving; the plaintext stays in a local vault that never leaves the machine. Restoring puts them back.

**What exactly gets backed up?**
Everything under `~/.dsh` — sessions, settings, skills, plugin config — minus your exclude patterns and `node_modules`.

**I messed up `~/.dsh` and now `dsh` won't start. Am I out of options?**
No — that's exactly what the rescue channel is for. Every backup writes `rescue.mjs` and a double-clickable launcher (`点我恢复.command` / `.bat` / `.sh`) into the backup directory. It runs on plain Node, no DSH required, and serves a local web UI to browse and restore archives.

**Old sessions refuse to open after a host upgrade — what now?**
Don't keep clicking them. Run `/backup migrate-check`: it statically scans all session logs and tells you which sessions the new host will refuse and under which rule (legacy subagent descriptor versions, plugin-injected historical events, ...), plus whether your filesystem supports hardlink publishing. Take a `/backup` snapshot before upgrading; broken sessions can be repaired from an earlier archive with `/backup doctor --repair`.

**Windows support?**
Yes — Windows 10+ with the bundled `tar`. The rescue launcher becomes a `.bat` file.

**Where do backups go by default?**
`~/Desktop/DSH Backups/` — change it any time in the panel (Settings → Plugins → Backup) or via settings; takes effect immediately, no restart. Since 0.14.0 the default directory was renamed from the old `~/Desktop/dsh-backups`: users who never configured a destination get the old folder renamed in place on first start (same volume, atomic, no backups lost), while explicitly configured destinations are left untouched.

**What are all those files in the backup folder?**
Every backup writes a plain-language guide to the folder root (`使用说明.txt`, Chinese with a short English section) explaining the archives, `.sha256` sidecars, rescue kit, `vault/` credentials and `auto.json` — plus the three ways to restore. You'll know what can be deleted and where to start.

## Feedback

Tried it? Tell us what broke, what's missing, what you liked — it directly shapes the roadmap:

- 💬 [Share feedback (GitHub Discussions)](https://github.com/xiaoyuyu6420/dsh-backup/discussions)
- 🐛 [Report a bug](https://github.com/xiaoyuyu6420/dsh-backup/issues)

## What's new

<details>
<summary>Recent releases</summary>

- **0.14.2** — review fixes: encryption/decryption now **stream in chunks** and write to a temp file before an atomic rename (no whole-archive buffering, no truncated ciphertext on crash); added the missing client descriptor for `setGithubToken` plus a reverse assertion that every panel method has a descriptor; braced `redactSyncChannel` (s3/gist no longer report a bogus `passwordSet:false`); copy now mentions Codex, the import preview no longer promises per-item override UI, multi-channel sync is documented as push-only, About gains a feedback link, and `rescue --fix --map` requires `--yes`. Verified: smoke 596 / settings 47 / client 62 / real-host e2e 70.

- **0.14.1** — follow-up release: npm 0.14.0 shipped before Codex support landed; this version adds Codex chat-history import to the npm track (Claude Code / Codex: scan → preview → import as a new session, function calls paired by call_id into tool call/result, originals untouched) and syncs the verified numbers (smoke 587 / settings 47 / client 60 / real-host e2e 70).
- **0.14.0** — one big release: backup-folder UX + panel redesign + full capability parity. ① **Directory rename**: default `~/Desktop/dsh-backups` → `~/Desktop/DSH Backups`; users who never configured a destination get the old folder renamed in place on first start (atomic, no lost backups), explicit destinations are untouched, and a failed rename keeps the old folder working; every backup writes `使用说明.txt` (what each file is + three restore paths). ② **Panel redesign**: three tabs (Backup / Restore / Settings), three-step onboarding, typed-backup chips, plain-language copy, responsive. ③ **Backup upgrades**: optional password encryption (scrypt+AES-256-GCM, password never persisted), single-file ZIP export/import (section preview → conflicts keep existing by default → auto snapshot → auto rollback), local `link:`/`file:` plugin migration. ④ **Sync & ops**: WebDAV / S3-compatible / Gist channels, GFS monthly/yearly retention, operation lock + stale-lock reclaim, disk-usage report + tiered cleanup, weekly schedules. ⑤ **Ecosystem**: config market (index validation + preview install + supply-chain notes), import skills & global instructions from Claude Code / Cursor / Codex (MCP detected and reported only), Profiles management (built-ins read-only), conversation rewind fork (original untouched), About panel, agent tools 1→6, cross-tool chat-history import (Claude Code / Codex → new DSH session, originals untouched), offline CLI (reinstall / sessions repair / recover-stale-lock). ⑥ **Compatibility & acceptance**: real-host e2e 70/70 on DSH **0.2.1-rc.2**, smoke 587 / settings 47 / client 60 green; also fixed `doctor --repair` on Windows failing because bsdtar refuses to overwrite existing objects (#131) — it now extracts to a staging directory and copies only the targets, leaving non-target files untouched. peers/engines unchanged (`^0.2.0-rc.1` covers the 0.2.1 train, next peer boundary 0.3.0).
- **0.13.4** — Metadata-only release (**no code or behavior change**): the npm description and keywords now carry the terms people actually search for — migration, moving to a new machine, plugins, MCP, skills, sessions, workspace — so dsh-backup shows up for "upgrade went wrong" and "moving my DSH setup" queries. Features, archive format, settings and RPC are identical to 0.13.3; staying on 0.13.3 behaves exactly the same.
- **0.13.3** — Three fixes from real user reports plus two self-found defects, all re-verified on four host trains (0.1.5-rc.3 / 0.1.7-rc.2 / 0.2.0-rc.2 / 0.2.1-alpha.1) with e2e 41/41 each: ① **archives containing attachments could not be restored at all** (#112, @zevs281): DSH's attachment store uses hard links (tar typeflag `h`) while the restore validator only allowed `-`/`d` — full restore, dry-run, typed merge and doctor repair all refused on 0.13.2 and earlier. Hard links are now allowed when both the entry and its target stay inside the backup root (symlinks/devices still rejected). ② **large archives were falsely rejected on restore** (#119, @Yoshino-JF): when the `tar -tvzf` listing exceeds the host subprocess spill cap the host discards the spill file and only the last 8 KB remain; the validator then treats a mid-filename fragment as an unsafe entry and refuses the whole archive (backup succeeds, verify passes, only restore fails). The listing is now consumed as a raw stream line by line (no cap; terminate on the first violation). ③ **doctor/migrate were blind to v4 session logs** (#117, @xiuwufangoc-design): the host moved to format v4 (`session.v4.jsonl.zstd`) in 0.1.7 while filenames and the calibrated generation were hardcoded to generation 0/v3 — scans found nothing. Filenames are now matched by the host's own pattern, the calibrated generation moved to v4, and newer generations only warn. ④ **one-click update could not cross minors** (#115): pnpm `update` only upgrades within the declared range and the receipt claimed success regardless — now `update --latest`, with the actually-installed version read back and verified. ⑤ **migration precheck false alarms** (#113, @zevs281): unknown `source.kind` values are hints now instead of "will not open" verdicts, and the known set is aligned with the host's authoritative tables.
- **0.13.2** — dsh **0.2.0 train** adaptation: peers/engines gain `^0.2.0-rc.1` (the host rejects incompatible peers at install time since 0.1.7-rc.1); **settings form fixed for 0.1.7+** (the host derives forms from the entry's Config schema and only honors `volatile` fields — the plugin exports `Config` with per-field `.volatile()`); Windows scheduler boot hang fixed (#103: streaming hash + boot grace + persisted attempt anchor); review P2 debt cleared and an in-product Star entry.
- **0.13.1** — Two panel fixes, both from user reports: ① **the panel tab silently disappears on host 0.1.6+** (#94, @jackovibe): since 0.1.6 the host requires a `create()` lazy factory on strict codecs in client Remote contributions (older hosts only read `schema`); without it `$mount` throws during validation with only a console log, so the tab is never registered. Fix: `strictCodec()` now provides both `schema` and `create()` sharing one zod instance; `$mount` waits declaratively for the `remote` service (0.1.6 settles client modules by dependency graph); and a new `BackupTabFallback` **degraded tab** shows a visible hint plus command pointers if the mount ever fails — no more silent disappearance. peers/engines gain `^0.1.6-alpha.1 || ^0.1.7-alpha.1` (verified on real hosts 0.1.6-alpha.2 / 0.1.7-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2). ② **the "GitHub Token" save button errored** (#97, @CheeseTJ): the client `panel` proxy never re-exported `setGithubToken` (that RPC landed in the host half in 0.12.0 and the omission lay dormant while no UI called it). Fix: wire it up, plus a new smoke-client assertion that the panel surface must mirror the contribution descriptors one-for-one.
- **0.13.0** — Update awareness + one-command self-update: ① `/backup check-update` and a new "Plugin updates" card in the panel — checks the npm registry for a newer version (8s timeout, degrades silently offline, never blocks the plugin); when a newer version exists an "Update to x.y.z" button appears; the `updateCheck` setting (default off) silently checks on panel open. ② `/backup update [--profile <name>]` — takes a `dsh-pre-upgrade-` snapshot automatically before updating (roll back with `/backup restore`), then runs the official `dsh plugin --profile <name> update` (pnpm semantics; the plugin never rewrites its own dependency files), and tells you to restart dsh web afterwards. Note: the host settles the plugin Loader tree at startup and plugins are pnpm dependencies of the profile, so true hot-swap (no restart) is not possible in the current host architecture — one-command update + restart is the best form it can take.
- **0.12.2** — Fix a Windows data-safety hazard (#88, thanks @Liuyeyuyangy0 for the outstanding report): when `DSH_HOME` sits directly under a drive root (e.g. `D:\dsh_data`), the string-slice parent computation degrades to the bare drive letter `D:`, and `tar -C D:` fails on both bsdtar and GNU tar — worse, tar creates the `-f` target before writing, leaving a 29-byte empty shell that the panel lists as a normal backup (no `.sha256`/`.meta.json`; silently zero usable backups, and the restore / doctor repair / rescue channels are hit by the same bug). All 7 tar `-C` call sites (backup, typed & full restore, doctor point-repair, rescue snapshot/restore/repair) now receive a separator-completed parent; `paths()` normalizes trailing slashes off `DSH_HOME` like `HOME` (a trailing slash used to empty the tar positional and produce 0-byte archives); failed tar runs now delete the empty shell before rethrowing, so failures are visible. Smoke gains scenario 28 (four path-layout assertions + a trailing-slash real-backup e2e). Archive format, settings and RPC unchanged.
- **0.12.1** — Fix client bundle leaking top-level `var module` / `var exports` as globals (#85, thanks @geyonder): the CJS shim now lives inside the `__ModuleLoader__.load` factory body instead of creating `window.module`, which made Monaco's AMD loader (dsh-vscode-mode and friends) misdetect a Node environment and break sibling plugins on the same page. Client smoke suite gains a classic-script global-leak regression assertion (`vm.runInContext`). Client-half only; node-side behavior and archive format unchanged.
- **0.12.0** — Migration safety net, round one, aimed at the community-wide "old sessions won't open after upgrade" wave (#6151/#6297/#6355): 1) `/backup migrate-check` statically scans all session logs (v0/v1/v2/v3) before you upgrade and predicts which sessions will refuse to open and under which rule (descriptor v2, permission/preset origin, event types outside the frozen lists, custom source kinds, seq drift, filename/generation mismatch — rules baked from host 0.1.5-rc.2, with the coverage boundary stated in every report), plus a filesystem hardlink probe (the exFAT publish-failure scenario from #6358); 2) a credential sentinel — the legacy flat-layout `.credentials.yaml` gets atomically replaced by the host with an irreversible versioned form, so on detection it is preserved into the vault before the host touches it. 3) configure the GitHub sync token right in the Settings panel (or `/backup github token <token>`) — stored locally as `github.token` (0600) in the backup directory, never archived or synced, taking precedence over environment variables; re-enter after cross-machine restore. Also: `engines.dsh` declaration (read by plugin-market compatibility cards).
- **0.11.3** — Follow the dsh `0.1.5` train: peerDependencies now include `^0.1.5-rc.1` (0.11.2 raised peer warnings on 0.1.5 hosts). Verified against host `0.1.5-rc.2`: real-host e2e 32/32; all six node-side peer packages are **byte-identical** between rc.1 and rc.2 (tarball diff) and the client train hasn't moved — zero adaptation surface this time; a cross-train in-place upgrade e2e (rc.1 host + 0.11.2 → rc.2 host + 0.11.3) passed 14/14 with settings and archives intact. Also fixed a latent bug in the upgrade e2e itself: the expected new version is now derived from the tarball instead of hardcoded.
- **0.11.2** — doctor line-level SessionHeader validation aligned with the host `isHeaderLine` (closes the known gap from 0.11.0): `version`/`createdAt`/`delegationDepth` type + non-negative-safe-integer checks (including `-0` rejection), optional `seedLength`/`origin`/`agentPreset` branches, and retired `sandboxMode`/`approvalPolicy` fields the host reader rejects — headers the host refuses to load are now flagged instead of reported healthy. Verified field-by-field against the compiled host source (both trains; the rc.1 checker is the strict superset) with an independent review verdict of ALIGN; 11 bad-header negative samples + repair round-trip added to the suite.
- **0.11.1** — dsh `0.1.2-rc.1` compatibility: adapts to the removal of `settingsNamespace` from `@deepseek-ai/dsh-settings` (settings now register under the plain `dsh-backup` namespace — value-identical, so existing settings, backups and archives survive an in-place update), works behind the new forced web auth (303 + HttpOnly cookie), and widens peer ranges to `^0.1.1-rc.2 || ^0.1.2-rc.1`. Verified on both trains with real-host e2e (32/32 each) plus an in-place 0.11.0 → 0.11.1 upgrade test (settings preserved, old archives restorable).
- **0.11.0** — doctor container-contract check: the first zstd frame must decode to exactly one header line, byte-precise (non-empty, first newline at the last byte — aligned with the host reader). Single-frame rewrites, stray blank lines in the first frame, a missing trailing newline and skippable frames are now flagged corrupt (previously reported healthy while the host refused to load them); the rescue console checks in sync. Found via a community audit on deepseek-harness #1047.
- **0.10.0** — typed backups: back up just what you need (`/backup --types skills,sessions`) and merge-restore a subset (`/backup restore <archive> --types skills`); per-type archives rotate in their own bucket. Credential-type archives stay out of GitHub sync; cross-machine guardrails unchanged.
- **0.9.1** — feedback entry point in the panel; README overhaul. UX hardening from a six-agent review: restore-confirm button made visible again (missing theme fallback), snapshot self-deletion during snapshot-restore fixed, node discovery for the double-click rescue launcher, friendlier error messages with concrete next steps.
- **0.9.0** — `/backup doctor` session-log health check with targeted repair; out-of-process rescue channel (`dsh-rescue` / launcher in the backup dir) that works even when the host won't boot; restore auto-rollback with a result-oriented receipt; smart backup: pre-upgrade snapshots on host train changes, quarantine of corrupt session logs before they rotate away, tiered daily/weekly retention.
- **0.8.0** — edit backup settings right in the panel (destination, retention, exclude patterns), saved instantly to `settings.yaml`, no restart; stale edits get a conflict warning instead of silent overwrite.
- **0.7.x** — credential redaction with a local vault; cross-machine restore.

</details>

## More

Retention policy, credential redaction internals, GitHub sync, restore safeguards, config reference, troubleshooting, and development notes — in the [advanced guide](docs/advanced.zh.md) (Chinese). Portable install & config: [INSTALL.md](docs/INSTALL.md). Cross-runtime compatibility notes: [compatibility.md](docs/compatibility.md). Full feature-parity and acceptance standard vs. similar tools: [ALIGNMENT.md](docs/ALIGNMENT.md). Release notes & promo copy: [docs/promo/](docs/promo/).

## Acknowledgements

- [@beastrobin](https://github.com/beastrobin) — the reserved-method-name root cause analysis in #1 that directly led to the v0.5.1 fix
- [@mlosun](https://github.com/mlosun) — the thorough reproduction and root cause report in #2
- [@Choi-Peng](https://github.com/Choi-Peng) — triage help pointing affected users to the fix in #5

## Support this project

If dsh-backup has been useful: ⭐ [a star](https://github.com/xiaoyuyu6420/dsh-backup) is the simplest way to say thanks — and it helps other DSH users find it. Questions or ideas? Drop them in the [feedback thread](https://github.com/xiaoyuyu6420/dsh-backup/discussions/32).

## License

MIT
