dsh-config-manager 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/lib/adapters/file-collection.js +16 -0
- package/lib/adapters/file-collection.js.map +1 -1
- package/lib/adapters/plugin-files.js +13 -1
- package/lib/adapters/plugin-files.js.map +1 -1
- package/lib/adapters/self.js +3 -0
- package/lib/adapters/self.js.map +1 -1
- package/lib/cli/index.d.ts +3 -1
- package/lib/cli/index.js +198 -20
- package/lib/cli/index.js.map +1 -1
- package/lib/client.d.ts +725 -24
- package/lib/client.js +10807 -5565
- package/lib/client.js.map +1 -1
- package/lib/core/analyzer.d.ts +17 -1
- package/lib/core/analyzer.js +99 -2
- package/lib/core/analyzer.js.map +1 -1
- package/lib/core/backup.d.ts +42 -1
- package/lib/core/backup.js +158 -5
- package/lib/core/backup.js.map +1 -1
- package/lib/core/consult-source.d.ts +58 -0
- package/lib/core/consult-source.js +260 -0
- package/lib/core/consult-source.js.map +1 -0
- package/lib/core/importer.d.ts +3 -1
- package/lib/core/importer.js +1 -0
- package/lib/core/importer.js.map +1 -1
- package/lib/core/index.d.ts +2 -1
- package/lib/core/index.js +3 -1
- package/lib/core/index.js.map +1 -1
- package/lib/core/journal.d.ts +194 -0
- package/lib/core/journal.js +412 -0
- package/lib/core/journal.js.map +1 -0
- package/lib/core/messages.d.ts +3 -0
- package/lib/core/messages.js +6 -0
- package/lib/core/messages.js.map +1 -1
- package/lib/core/migration-consult.d.ts +117 -0
- package/lib/core/migration-consult.js +363 -0
- package/lib/core/migration-consult.js.map +1 -0
- package/lib/core/migration-history.d.ts +153 -0
- package/lib/core/migration-history.js +405 -0
- package/lib/core/migration-history.js.map +1 -0
- package/lib/core/model-tools.js +53 -23
- package/lib/core/model-tools.js.map +1 -1
- package/lib/core/phase3-child-crash.d.ts +1 -0
- package/lib/core/phase3-child-crash.js +87 -0
- package/lib/core/phase3-child-crash.js.map +1 -0
- package/lib/core/phase3-host.d.ts +138 -0
- package/lib/core/phase3-host.js +358 -0
- package/lib/core/phase3-host.js.map +1 -0
- package/lib/core/phase3-prod-child.d.ts +1 -0
- package/lib/core/phase3-prod-child.js +35 -0
- package/lib/core/phase3-prod-child.js.map +1 -0
- package/lib/core/phase4-crash-child.d.ts +1 -0
- package/lib/core/phase4-crash-child.js +103 -0
- package/lib/core/phase4-crash-child.js.map +1 -0
- package/lib/core/plugin-cli.d.ts +1 -1
- package/lib/core/plugin-cli.js +3 -2
- package/lib/core/plugin-cli.js.map +1 -1
- package/lib/core/reconcile.d.ts +120 -0
- package/lib/core/reconcile.js +389 -0
- package/lib/core/reconcile.js.map +1 -0
- package/lib/core/recovery-orchestrator.d.ts +71 -0
- package/lib/core/recovery-orchestrator.js +282 -0
- package/lib/core/recovery-orchestrator.js.map +1 -0
- package/lib/core/reinstall.d.ts +34 -0
- package/lib/core/reinstall.js +45 -0
- package/lib/core/reinstall.js.map +1 -1
- package/lib/core/restore.d.ts +54 -0
- package/lib/core/restore.js +155 -7
- package/lib/core/restore.js.map +1 -1
- package/lib/core/rollback.d.ts +3 -0
- package/lib/core/rollback.js +8 -2
- package/lib/core/rollback.js.map +1 -1
- package/lib/core/run-registry.d.ts +2 -2
- package/lib/core/run-registry.js +3 -1
- package/lib/core/run-registry.js.map +1 -1
- package/lib/core/startup-barrier.d.ts +53 -0
- package/lib/core/startup-barrier.js +77 -0
- package/lib/core/startup-barrier.js.map +1 -0
- package/lib/core/transaction-coordinator.d.ts +115 -0
- package/lib/core/transaction-coordinator.js +213 -0
- package/lib/core/transaction-coordinator.js.map +1 -0
- package/lib/core/types.d.ts +81 -0
- package/lib/core/types.js.map +1 -1
- package/lib/core/verify-recovery.d.ts +21 -0
- package/lib/core/verify-recovery.js +256 -0
- package/lib/core/verify-recovery.js.map +1 -0
- package/lib/index.js +1284 -75
- package/lib/index.js.map +1 -1
- package/lib/market/git-file-writer.js +2 -1
- package/lib/market/git-file-writer.js.map +1 -1
- package/lib/market/market-config.js +2 -1
- package/lib/market/market-config.js.map +1 -1
- package/lib/market/types.d.ts +2 -0
- package/lib/profiles/profile-manager.d.ts +5 -1
- package/lib/profiles/profile-manager.js +20 -4
- package/lib/profiles/profile-manager.js.map +1 -1
- package/lib/sync/autosync-config.js +2 -12
- package/lib/sync/autosync-config.js.map +1 -1
- package/lib/sync/autosync-scheduler.d.ts +26 -0
- package/lib/sync/autosync-scheduler.js +51 -3
- package/lib/sync/autosync-scheduler.js.map +1 -1
- package/lib/sync/backup-files.d.ts +25 -4
- package/lib/sync/backup-files.js +82 -3
- package/lib/sync/backup-files.js.map +1 -1
- package/lib/sync/backup-schedule-config.d.ts +29 -3
- package/lib/sync/backup-schedule-config.js +52 -14
- package/lib/sync/backup-schedule-config.js.map +1 -1
- package/lib/sync/backup-scheduler.d.ts +39 -0
- package/lib/sync/backup-scheduler.js +110 -47
- package/lib/sync/backup-scheduler.js.map +1 -1
- package/lib/sync/fs.js +3 -1
- package/lib/sync/fs.js.map +1 -1
- package/lib/sync/git/git-transport.js +2 -1
- package/lib/sync/git/git-transport.js.map +1 -1
- package/lib/sync/review-queue.js +2 -14
- package/lib/sync/review-queue.js.map +1 -1
- package/lib/sync/sync-config.js +2 -1
- package/lib/sync/sync-config.js.map +1 -1
- package/lib/sync/sync-engine.d.ts +33 -1
- package/lib/sync/sync-engine.js +64 -0
- package/lib/sync/sync-engine.js.map +1 -1
- package/lib/sync/sync-history.js +2 -12
- package/lib/sync/sync-history.js.map +1 -1
- package/lib/sync/sync-selection.js +2 -12
- package/lib/sync/sync-selection.js.map +1 -1
- package/lib/sync/ui-prefs.d.ts +4 -0
- package/lib/sync/ui-prefs.js +10 -12
- package/lib/sync/ui-prefs.js.map +1 -1
- package/lib/sync/webdav/webdav-transport.js +59 -21
- package/lib/sync/webdav/webdav-transport.js.map +1 -1
- package/lib/ui/backup-inspect.d.ts +53 -0
- package/lib/ui/backup-inspect.js +54 -0
- package/lib/ui/backup-inspect.js.map +1 -0
- package/lib/ui/backup-schedule.d.ts +14 -3
- package/lib/ui/backup-schedule.js +39 -4
- package/lib/ui/backup-schedule.js.map +1 -1
- package/lib/ui/conflict-view.d.ts +4 -0
- package/lib/ui/conflict-view.js +11 -0
- package/lib/ui/conflict-view.js.map +1 -1
- package/lib/ui/export-flow.d.ts +16 -0
- package/lib/ui/export-flow.js +15 -0
- package/lib/ui/export-flow.js.map +1 -1
- package/lib/ui/history-model.d.ts +60 -0
- package/lib/ui/history-model.js +90 -0
- package/lib/ui/history-model.js.map +1 -0
- package/lib/ui/i18n.d.ts +30 -6
- package/lib/ui/i18n.js +60 -11
- package/lib/ui/i18n.js.map +1 -1
- package/lib/ui/migration-consult-view.d.ts +57 -0
- package/lib/ui/migration-consult-view.js +65 -0
- package/lib/ui/migration-consult-view.js.map +1 -0
- package/lib/ui/next-steps.d.ts +56 -0
- package/lib/ui/next-steps.js +29 -0
- package/lib/ui/next-steps.js.map +1 -0
- package/lib/ui/profiles-view.d.ts +26 -0
- package/lib/ui/profiles-view.js +38 -0
- package/lib/ui/profiles-view.js.map +1 -0
- package/lib/ui/release-notes-prompt.d.ts +30 -0
- package/lib/ui/release-notes-prompt.js +30 -0
- package/lib/ui/release-notes-prompt.js.map +1 -0
- package/lib/ui/types.d.ts +88 -0
- package/lib/utils/atomic-write.d.ts +88 -0
- package/lib/utils/atomic-write.js +399 -0
- package/lib/utils/atomic-write.js.map +1 -0
- package/lib/utils/env-lock.d.ts +349 -0
- package/lib/utils/env-lock.js +827 -0
- package/lib/utils/env-lock.js.map +1 -0
- package/lib/utils/paths.d.ts +11 -0
- package/lib/utils/paths.js +57 -0
- package/lib/utils/paths.js.map +1 -1
- package/lib/utils/zip.d.ts +1 -1
- package/lib/utils/zip.js +3 -3
- package/lib/utils/zip.js.map +1 -1
- package/package.json +1 -3
- package/src/adapters/file-collection.ts +16 -0
- package/src/adapters/files.test.ts +140 -0
- package/src/adapters/plugin-files.ts +13 -1
- package/src/adapters/self.ts +3 -0
- package/src/cli/index.ts +198 -22
- package/src/client/ConfigManagerSection.tsx +196 -38
- package/src/client/about/AboutPanel.tsx +41 -3
- package/src/client/about/ReleaseNotesDialog.tsx +460 -0
- package/src/client/about/about-view.test.ts +1 -0
- package/src/client/about/about-view.ts +25 -1
- package/src/client/about/release-notes-view.test.ts +174 -0
- package/src/client/about/release-notes-view.ts +310 -0
- package/src/client/api.ts +205 -1
- package/src/client/client-types.ts +8 -0
- package/src/client/config-manager.module.css +300 -3
- package/src/client/consult/ConsultCard.tsx +82 -0
- package/src/client/export/ExportView.tsx +99 -4
- package/src/client/history/HistoryPanel.tsx +231 -0
- package/src/client/history/history-api.ts +151 -0
- package/src/client/history/history-locales.ts +172 -0
- package/src/client/import/ConflictList.tsx +4 -3
- package/src/client/import/ImportWizardView.tsx +79 -1
- package/src/client/index.ts +21 -1
- package/src/client/locales.ts +253 -6
- package/src/client/market/MarketPanel.tsx +46 -4
- package/src/client/market/market-locales.ts +18 -2
- package/src/client/market/market-view.test.ts +60 -4
- package/src/client/market/market-view.ts +73 -2
- package/src/client/profiles/ProfilesPanel.tsx +591 -0
- package/src/client/recovery/RecoveryPanel.tsx +544 -0
- package/src/client/recovery/recovery-api.ts +140 -0
- package/src/client/recovery/recovery-locales.ts +227 -0
- package/src/client/recovery/recovery-view.test.ts +161 -0
- package/src/client/recovery/recovery-view.ts +151 -0
- package/src/client/run-store.test.ts +180 -0
- package/src/client/run-store.ts +259 -14
- package/src/client/snapshots/SnapshotsPanel.tsx +515 -64
- package/src/client/sync/SyncConfirmView.tsx +48 -13
- package/src/client/sync/SyncSettingsView.tsx +343 -131
- package/src/client/sync/sync-api.ts +12 -1
- package/src/client/sync/sync-locales.ts +19 -3
- package/src/client/sync/sync-push-preview.test.ts +68 -0
- package/src/client/sync/sync-view.ts +48 -1
- package/src/core/analyzer.ts +106 -2
- package/src/core/backup.ts +173 -6
- package/src/core/consult-source.test.ts +199 -0
- package/src/core/consult-source.ts +293 -0
- package/src/core/importer.ts +4 -1
- package/src/core/index.ts +14 -2
- package/src/core/journal.test.ts +257 -0
- package/src/core/journal.ts +507 -0
- package/src/core/messages.ts +6 -0
- package/src/core/migration-consult.test.ts +315 -0
- package/src/core/migration-consult.ts +494 -0
- package/src/core/migration-history.test.ts +309 -0
- package/src/core/migration-history.ts +476 -0
- package/src/core/model-tools.test.ts +27 -0
- package/src/core/model-tools.ts +62 -24
- package/src/core/phase3-child-crash.ts +95 -0
- package/src/core/phase3-consistency.test.ts +247 -0
- package/src/core/phase3-host.ts +410 -0
- package/src/core/phase3-p1.test.ts +212 -0
- package/src/core/phase3-p2b-fingerprint.test.ts +181 -0
- package/src/core/phase3-prod-child.ts +36 -0
- package/src/core/phase3-production-integration.test.ts +286 -0
- package/src/core/phase3-security.test.ts +95 -0
- package/src/core/phase4-crash-child.ts +101 -0
- package/src/core/phase4-crash-injection.test.ts +166 -0
- package/src/core/plugin-cli.ts +3 -2
- package/src/core/reconcile.test.ts +392 -0
- package/src/core/reconcile.ts +498 -0
- package/src/core/recovery-orchestrator.ts +308 -0
- package/src/core/reinstall.ts +72 -0
- package/src/core/restore-manage.test.ts +94 -0
- package/src/core/restore.ts +204 -7
- package/src/core/rollback.ts +10 -2
- package/src/core/run-registry.test.ts +20 -0
- package/src/core/run-registry.ts +5 -3
- package/src/core/startup-barrier.ts +99 -0
- package/src/core/transaction-coordinator.test.ts +239 -0
- package/src/core/transaction-coordinator.ts +303 -0
- package/src/core/types.ts +86 -0
- package/src/core/verify-recovery.ts +265 -0
- package/src/index.ts +1224 -83
- package/src/market/git-file-writer.ts +2 -1
- package/src/market/market-config.ts +2 -1
- package/src/market/types.ts +2 -0
- package/src/profiles/profile-manager.ts +24 -5
- package/src/sync/autosync-config.ts +2 -11
- package/src/sync/autosync-scheduler.ts +65 -3
- package/src/sync/backup-files.test.ts +64 -0
- package/src/sync/backup-files.ts +84 -6
- package/src/sync/backup-schedule-config.ts +68 -16
- package/src/sync/backup-scheduler.test.ts +49 -1
- package/src/sync/backup-scheduler.ts +134 -46
- package/src/sync/fs.ts +3 -1
- package/src/sync/git/git-transport.ts +2 -1
- package/src/sync/review-queue.ts +2 -12
- package/src/sync/sync-config.ts +2 -1
- package/src/sync/sync-engine.test.ts +1 -1
- package/src/sync/sync-engine.ts +93 -3
- package/src/sync/sync-history.ts +2 -11
- package/src/sync/sync-selection.ts +2 -11
- package/src/sync/ui-prefs.test.ts +18 -0
- package/src/sync/ui-prefs.ts +14 -11
- package/src/sync/webdav/webdav-transport.test.ts +7 -5
- package/src/sync/webdav/webdav-transport.ts +70 -20
- package/src/ui/backup-inspect.test.ts +142 -0
- package/src/ui/backup-inspect.ts +100 -0
- package/src/ui/backup-schedule.test.ts +51 -0
- package/src/ui/backup-schedule.ts +49 -7
- package/src/ui/conflict-view.test.ts +24 -2
- package/src/ui/conflict-view.ts +12 -0
- package/src/ui/export-flow.test.ts +10 -1
- package/src/ui/export-flow.ts +24 -1
- package/src/ui/history-model.test.ts +120 -0
- package/src/ui/history-model.ts +130 -0
- package/src/ui/i18n.ts +60 -11
- package/src/ui/migration-consult-view.test.ts +82 -0
- package/src/ui/migration-consult-view.ts +119 -0
- package/src/ui/next-steps.test.ts +117 -0
- package/src/ui/next-steps.ts +62 -0
- package/src/ui/profiles-view.test.ts +77 -0
- package/src/ui/profiles-view.ts +55 -0
- package/src/ui/release-notes-prompt.test.ts +27 -0
- package/src/ui/release-notes-prompt.ts +49 -0
- package/src/ui/types.ts +93 -0
- package/src/utils/atomic-write.test.ts +312 -0
- package/src/utils/atomic-write.ts +422 -0
- package/src/utils/env-lock.test.ts +882 -0
- package/src/utils/env-lock.ts +977 -0
- package/src/utils/paths.ts +54 -0
- package/src/utils/zip.ts +3 -3
- package/dsh.bundle.patch +0 -21
- package/dsh.client +0 -39
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Backup, restore, export, import, migrate and sync your complete DeepSeek Harness
|
|
|
7
7
|
- 🔄 **Backup & Restore** DeepSeek Harness configuration
|
|
8
8
|
- 📦 **Export / Import** complete DSH configuration
|
|
9
9
|
- 🚚 **Migrate** DSH to another machine
|
|
10
|
-
- ⏰ **Scheduled full backups** — automatic, on your own cadence (6h / 12h / 24h / 7d), secrets never included
|
|
10
|
+
- ⏰ **Scheduled full backups** — automatic, on your own cadence (6h / 12h / 24h / 7d / custom weekly), secrets never included
|
|
11
11
|
- 🔌 Backup installed **plugins** and plugin configuration
|
|
12
12
|
- 🧩 Backup **MCP servers** and **Skills**
|
|
13
13
|
- 🔐 Encrypted backups with optional credentials
|
|
@@ -56,7 +56,7 @@ Keep portable configuration synchronized between machines through a private Git
|
|
|
56
56
|
|
|
57
57
|
### Schedule automatic full backups
|
|
58
58
|
|
|
59
|
-
Turn on scheduled backups (6h / 12h / 24h / 7d) and DSH quietly keeps a fresh full backup of your configuration in the background — secrets are never included, so it stays safe on disk without a password.
|
|
59
|
+
Turn on scheduled backups (6h / 12h / 24h / 7d — or a **custom weekly weekday & time**) and DSH quietly keeps a fresh full backup of your configuration in the background — secrets are never included, so it stays safe on disk without a password. Consecutive failures are highlighted in red in the settings card.
|
|
60
60
|
|
|
61
61
|
### Discover & install configurations from the marketplace
|
|
62
62
|
|
|
@@ -79,7 +79,7 @@ Browse the built-in official market for ready-made configurations (model provide
|
|
|
79
79
|
| 🔄 | **Remote Sync** | Push/pull portable config via **Git private repo or WebDAV** (secrets never sync) |
|
|
80
80
|
| ⏰ | **Scheduled backups** | Full backup on a fixed cadence (6h / 12h / 24h / 7d) — set-and-forget, secrets never included |
|
|
81
81
|
| 🛒 | **Config Marketplace** | Browse & one-click install community configs — supply-chain warnings + per-section approval |
|
|
82
|
-
| 🗂️ | **Profiles** | Save multiple setups (Work / Personal) and switch anytime |
|
|
82
|
+
| 🗂️ | **Profiles** | Save multiple setups (Work / Personal) and switch anytime — preview + auto-backup + rollback |
|
|
83
83
|
| 🌐 | **Bilingual UI** | Interface, reports and error details follow the DSH app language (中文 / English) |
|
|
84
84
|
| 🤖 | **Agent tools** | Backup / snapshot / restore / sync right from an agent session |
|
|
85
85
|
|
|
@@ -186,11 +186,16 @@ Copy the ZIP to Machine B (import)
|
|
|
186
186
|
|
|
187
187
|
> Output: `dsh-config-<date>.zip` with manifest + per-category data + SHA-256 checksums.
|
|
188
188
|
|
|
189
|
+
**Export extras:**
|
|
190
|
+
- **Preview before export** — see what will be packaged (section count + estimated size, no secrets) before anything is written
|
|
191
|
+
- **Custom file name & note** — name the ZIP yourself (auto-naming is the default) and attach an optional note that shows in the **Backup Files** list (the note travels with the self section when you sync/backup the config)
|
|
192
|
+
|
|
189
193
|
### 📥 Import (safe flow)
|
|
190
194
|
|
|
191
195
|
- **Nothing is written before confirmation** — analyze & preview are zero-write
|
|
192
196
|
- **Backup before applying** — the target config is snapshotted automatically
|
|
193
197
|
- **Automatic rollback on failure** — full rollback or skip-and-continue, your choice
|
|
198
|
+
- **Next-steps checklist after import** — the result page lists what needs a DSH restart (per plugin/MCP), credentials to re-enter, and failed/skipped items you can retry
|
|
194
199
|
|
|
195
200
|
### 👀 Import Preview (dry run)
|
|
196
201
|
|
|
@@ -242,13 +247,15 @@ Push / pull your portable config between machines through **either of two channe
|
|
|
242
247
|
- **Switching channels starts fresh**: Git and WebDAV do **not** share snapshots or a common ancestor. When you switch transport, sync begins again from the new remote's empty baseline — push a fresh snapshot first.
|
|
243
248
|
- **WebDAV auth** uses HTTP Basic: the `username` is stored in the config and may be echoed back into the UI, while the `password` is read live from the DSH credentials slot `DSH_CONFIG_MANAGER_SYNC_WEBDAV_PASSWORD` — it never appears in any sync file or log.
|
|
244
249
|
- **Plugins auto-install**: when pulling diffs, plugins that are new in the backup are **installed automatically** on confirm — no manual per-item ticking in the diff list. Only **version-conflict** plugins still ask you to pick "Keep Current / Use Imported".
|
|
250
|
+
- **Push preview before uploading** — the Push button first shows a read-only preview of what will be sent (sections + per-section counts + changed-vs-baseline markers, first-baseline notice) and only writes the remote after you confirm.
|
|
245
251
|
|
|
246
252
|
### 🛒 Configuration Marketplace
|
|
247
253
|
|
|
248
254
|
Browse and install ready-made configurations (model providers, plugins, MCP servers, skills, agent presets…) shared by the community:
|
|
249
255
|
|
|
250
256
|
- **Built-in official market** — read-only, bound to the official public repo (official badge shown, not editable); first open auto-refreshes, manual refresh also available
|
|
251
|
-
- **Search & filter** — keyword search, category filter, source filter (Official / Community), sorting (recently updated / most starred / name A–Z), and a ⭐ badge showing the **source repo's** star count (queried anonymously, no token involved)
|
|
257
|
+
- **Search & filter** — keyword search (matches name / description / author / **categories**), category filter, **section filter** (items already downloaded list their sections; others are excluded with a hint), source filter (Official / Community), sorting (recently updated / most starred / name A–Z), and a ⭐ badge showing the **source repo's** star count (queried anonymously, no token involved)
|
|
258
|
+
- **Impact preview** — the detail view shows "what installing this will change" (items updated / identical / conflicts / secrets to re-enter / DSH restart needed) before you approve anything
|
|
252
259
|
- **Supply-chain warnings always shown** — source repo URL, "not officially reviewed", download time; **per-section approval** — high-risk sections (sessions / arbitrary files) are banned from listing outright, and every remaining section must be explicitly approved before the import is confirmed
|
|
253
260
|
- **Install reuses the safe import pipeline** — analyze → preview → auto-backup → apply → rollback; nothing is written before you confirm
|
|
254
261
|
- **"My Configs"** — sign in with GitHub (device flow), upload a config to **your own public repo** in one click, and an **auto listing PR** is opened against the official market repo; manage your listings (status badges: not listed / PR pending / listed), update in one click, install back locally, or delist (auto de-listing PR)
|
|
@@ -257,6 +264,11 @@ Browse and install ready-made configurations (model providers, plugins, MCP serv
|
|
|
257
264
|
|
|
258
265
|
Save multiple configurations (Work / Personal) and switch anytime; switching includes preview + auto-backup + rollback.
|
|
259
266
|
|
|
267
|
+
- **Save current config** — pick a name and store the current DSH configuration (settings / providers / plugins / skills / agent presets…; no secrets, file sections embedded)
|
|
268
|
+
- **Switch with safety** — preview first (zero writes) → confirm → automatic snapshot → staged apply; any failure rolls back fully
|
|
269
|
+
- **Manage** — rename / delete (confirmed) / import an exported profile.json
|
|
270
|
+
- Library is an independent tab: Settings → "Backup & Migration" → **Profiles**
|
|
271
|
+
|
|
260
272
|
### 📸 Snapshot restore (undo an import)
|
|
261
273
|
|
|
262
274
|
Every import creates a **safety snapshot** first. If something feels off afterwards, restore the target back to its pre-import state:
|
|
@@ -270,6 +282,16 @@ Every import creates a **safety snapshot** first. If something feels off afterwa
|
|
|
270
282
|
|
|
271
283
|
**GUI**: Settings → "Backup & Migration" → **Snapshots & Restore** tab → pick a snapshot → preview the plan (dry-run, zero writes) → confirm.
|
|
272
284
|
|
|
285
|
+
**Snapshot management:**
|
|
286
|
+
- **Retention is visible** — up to **10** snapshots are kept automatically (oldest pruned); the hint is shown in the list
|
|
287
|
+
- **Pin important snapshots** — a pinned snapshot is exempt from auto-pruning and can only be deleted manually
|
|
288
|
+
- **Manual delete** — remove any snapshot (danger, confirmed) when you no longer need that rollback point
|
|
289
|
+
|
|
290
|
+
**Backup Files management** (same tab → "Backup Files"):
|
|
291
|
+
- List every export ZIP (manual + scheduled) with source badge, size, time and your custom **note**
|
|
292
|
+
- **Search** by file name or note
|
|
293
|
+
- **Inspect / Compare** — read-only preview of what the backup contains (sections + per-section counts) and the diff against your current config (zero writes) before deciding to import
|
|
294
|
+
|
|
273
295
|
---
|
|
274
296
|
|
|
275
297
|
### 🚨 CLI — the first line of defense when DSH is broken
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { sha256Hex } from "../utils/hashing.js";
|
|
12
|
+
import { isReservedInternalRel, normalizePath } from "../utils/paths.js";
|
|
12
13
|
import { msgOf, zhMsg } from "../core/messages.js";
|
|
13
14
|
export class FileCollectionAdapter {
|
|
14
15
|
async export(ctx, _options) {
|
|
@@ -40,6 +41,16 @@ export class FileCollectionAdapter {
|
|
|
40
41
|
const items = [];
|
|
41
42
|
for (const file of data.files) {
|
|
42
43
|
const id = `${this.id}:${file.relativePath}`;
|
|
44
|
+
// F23 修复:不可信 import 不得写内部 control-plane namespace。
|
|
45
|
+
// 检查 baseDir+ref 解析后的 homeDir 相对路径(self 适配器 baseDir='dsh-config-manager' 是主投毒向量)。
|
|
46
|
+
const resolvedRel = normalizePath(path.join(this.baseDir, file.relativePath));
|
|
47
|
+
if (isReservedInternalRel(resolvedRel)) {
|
|
48
|
+
items.push({
|
|
49
|
+
id, kind: 'Error', adapter: this.id,
|
|
50
|
+
description: msg('adapter.fileReserved', { path: file.relativePath }), severity: 'error',
|
|
51
|
+
});
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
43
54
|
let current = null;
|
|
44
55
|
try {
|
|
45
56
|
current = await ctx.target.fs.readFile(path.join(this.baseDir, file.relativePath));
|
|
@@ -72,6 +83,11 @@ export class FileCollectionAdapter {
|
|
|
72
83
|
const ref = item.target?.ref;
|
|
73
84
|
if (!ref)
|
|
74
85
|
return { ok: false, message: msg('adapter.missingTargetRef') };
|
|
86
|
+
// F23 修复:apply 前拒绝写内部 control-plane namespace(纵深防御,analyzeImport 已标 Error)
|
|
87
|
+
const resolvedRel = normalizePath(path.join(this.baseDir, ref));
|
|
88
|
+
if (isReservedInternalRel(resolvedRel)) {
|
|
89
|
+
return { ok: false, message: msg('adapter.fileReserved', { path: ref }) };
|
|
90
|
+
}
|
|
75
91
|
const data = ctx.sections.get(this.id);
|
|
76
92
|
const file = data?.files.find((f) => f.relativePath === ref);
|
|
77
93
|
if (!file)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-collection.js","sourceRoot":"","sources":["../../src/adapters/file-collection.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file-collection.js","sourceRoot":"","sources":["../../src/adapters/file-collection.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { sha256Hex } from "../utils/hashing.js";
|
|
10
10
|
import { zhMsg } from "../core/messages.js";
|
|
11
|
-
import { isPathSafe } from "../utils/paths.js";
|
|
11
|
+
import { isPathSafe, isReservedInternalRel } from "../utils/paths.js";
|
|
12
12
|
export const DEFAULT_PLUGIN_FILE_WHITELIST = ['dsh-ssh.json', 'pet.json'];
|
|
13
13
|
export class PluginFilesAdapter {
|
|
14
14
|
id = 'pluginFiles';
|
|
@@ -72,6 +72,14 @@ export class PluginFilesAdapter {
|
|
|
72
72
|
const items = [];
|
|
73
73
|
for (const file of data.files) {
|
|
74
74
|
const id = `pluginFile:${file.relativePath}`;
|
|
75
|
+
// F23 修复:不可信 import 不得写内部 control-plane namespace(snapshots/transactions/locks/safe-mode)
|
|
76
|
+
if (isReservedInternalRel(file.relativePath)) {
|
|
77
|
+
items.push({
|
|
78
|
+
id, kind: 'Error', adapter: 'pluginFiles',
|
|
79
|
+
description: msg('adapter.pluginFileReserved', { path: file.relativePath }), severity: 'error',
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
75
83
|
let current = null;
|
|
76
84
|
try {
|
|
77
85
|
current = await ctx.target.fs.readFile(file.relativePath);
|
|
@@ -103,6 +111,10 @@ export class PluginFilesAdapter {
|
|
|
103
111
|
const ref = item.target?.ref;
|
|
104
112
|
if (!ref)
|
|
105
113
|
return { ok: false, message: ctx.msg('adapter.missingTargetRef') };
|
|
114
|
+
// F23 修复:apply 前拒绝写内部 control-plane namespace(纵深防御,analyzeImport 已标 Error)
|
|
115
|
+
if (isReservedInternalRel(ref)) {
|
|
116
|
+
return { ok: false, message: ctx.msg('adapter.pluginFileReserved', { path: ref }) };
|
|
117
|
+
}
|
|
106
118
|
const data = ctx.sections.get('pluginFiles');
|
|
107
119
|
const file = data?.files.find((f) => f.relativePath === ref);
|
|
108
120
|
if (!file)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-files.js","sourceRoot":"","sources":["../../src/adapters/plugin-files.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin-files.js","sourceRoot":"","sources":["../../src/adapters/plugin-files.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAQtE,MAAM,CAAC,MAAM,6BAA6B,GAAsB,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;AAE7F,MAAM,OAAO,kBAAkB;IACpB,EAAE,GAAG,aAAsB,CAAC;IAC5B,WAAW,GAAG,cAAc,CAAC;IAC7B,eAAe,GAAG,KAAK,CAAC;IACxB,WAAW,GAAG,gBAAyB,CAAC;IAChC,SAAS,CAAW;IACrC,wEAAwE;IACvD,UAAU,CAAU;IAErC,YAAY,YAAsB,CAAC,GAAG,6BAA6B,CAAC,EAAE,UAAmB;QACvF,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7E,MAAM,IAAI,KAAK,CAAC,kDAAkD,UAAU,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAgB,EAAE,QAAuB;QACpD,MAAM,KAAK,GAA0B,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,yCAAyC;QACzC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBACxC,KAAK,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACtE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;QACH,CAAC;QACD,2CAA2C;QAC3C,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,IAAI,GAAa,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,WAAW;YACb,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC5B,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;oBACxC,KAAK,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACxE,CAAC;gBAAC,MAAM,CAAC;oBACP,SAAS;gBACX,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO;YACL,SAAS,EAAE,aAAa;YACxB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE;YAC3B,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE;YAC/B,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAAkB,EAAE,GAAkB;QACxD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;QACpB,MAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,EAAE,GAAG,cAAc,IAAI,CAAC,YAAY,EAAE,CAAC;YAC7C,0FAA0F;YAC1F,IAAI,qBAAqB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7C,KAAK,CAAC,IAAI,CAAC;oBACT,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa;oBACzC,WAAW,EAAE,GAAG,CAAC,4BAA4B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO;iBAC/F,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,IAAI,OAAO,GAAsB,IAAI,CAAC;YACtC,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,KAAK,CAAC,IAAI,CAAC;oBACT,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa;oBAC1C,WAAW,EAAE,GAAG,CAAC,0BAA0B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM;oBAC3F,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE;iBAC3D,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,SAAS,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;gBACnD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;YAChJ,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC;oBACT,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa;oBAC5C,WAAW,EAAE,GAAG,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS;oBACtF,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE;iBAC3D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAc,EAAE,GAAkB;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;QAC7B,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC7E,2EAA2E;QAC3E,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,4BAA4B,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;QACtF,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAA6B,CAAC;QACzE,MAAM,IAAI,GAAG,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,yBAAyB,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;QACtF,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAkB,EAAE,MAAe,KAAK;QACrD,MAAM,MAAM,GAA+B,EAAE,CAAC;QAC9C,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,yBAAyB,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAC3I,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACjI,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAClH,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACtF,CAAC;CACF"}
|
package/lib/adapters/self.js
CHANGED
|
@@ -33,6 +33,9 @@ export const SELF_CONFIG_FILES = [
|
|
|
33
33
|
'sync/ui-prefs.json',
|
|
34
34
|
'sync/backup-schedule.json',
|
|
35
35
|
'market/market-config.json',
|
|
36
|
+
// P0-④:导出产物备注清单(exports/.backup-notes.json)——随 self 分区迁移,
|
|
37
|
+
// 换机器后备份列表仍能看到手动导出时填写的备注
|
|
38
|
+
'exports/.backup-notes.json',
|
|
36
39
|
];
|
|
37
40
|
export class SelfAdapter extends FileCollectionAdapter {
|
|
38
41
|
id = 'self';
|
package/lib/adapters/self.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self.js","sourceRoot":"","sources":["../../src/adapters/self.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,uBAAuB;IACvB,yBAAyB;IACzB,0BAA0B;IAC1B,oBAAoB;IACpB,2BAA2B;IAC3B,2BAA2B;
|
|
1
|
+
{"version":3,"file":"self.js","sourceRoot":"","sources":["../../src/adapters/self.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,uBAAuB;IACvB,yBAAyB;IACzB,0BAA0B;IAC1B,oBAAoB;IACpB,2BAA2B;IAC3B,2BAA2B;IAC3B,0DAA0D;IAC1D,yBAAyB;IACzB,4BAA4B;CAC7B,CAAC;AAEF,MAAM,OAAO,WAAY,SAAQ,qBAAqB;IAC3C,EAAE,GAAG,MAAe,CAAC;IACrB,WAAW,GAAG,oBAAoB,CAAC;IACnC,eAAe,GAAG,IAAI,CAAC;IACvB,WAAW,GAAG,UAAmB,CAAC;IAC3C,kEAAkE;IACzD,OAAO,CAAS;IAEzB,YAAY,OAAO,GAAG,oBAAoB;QACxC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;iGAC6F;IACpF,KAAK,CAAC,MAAM,CAAC,GAAgB,EAAE,QAAuB;QAC7D,MAAM,KAAK,GAA0B,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;YACpC,oEAAoE;YACpE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACnF,IAAI,IAAI,KAAK,IAAI;gBAAE,SAAS,CAAC,0BAA0B;YACvD,KAAK,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,EAAE;YAClB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE;YAC3B,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE;YAC/B,QAAQ;SACT,CAAC;IACJ,CAAC;CACF"}
|
package/lib/cli/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type SnapshotMeta } from '../core/restore.ts';
|
|
3
3
|
import { type ReinstallItemId } from '../core/reinstall.ts';
|
|
4
|
-
export type CliCommand = 'snapshots' | 'restore' | 'reinstall' | 'help';
|
|
4
|
+
export type CliCommand = 'snapshots' | 'restore' | 'reinstall' | 'recover-stale-lock' | 'help';
|
|
5
5
|
export interface CliOptions {
|
|
6
6
|
command: CliCommand;
|
|
7
7
|
/** --data-dir:快照数据目录覆盖 */
|
|
@@ -40,6 +40,8 @@ export declare function parseConfirmInput(text: string): boolean;
|
|
|
40
40
|
export declare function resolveDshHome(env?: Record<string, string | undefined>): string;
|
|
41
41
|
/** 快照数据目录:--data-dir 覆盖优先,缺省 $DSH_HOME/dsh-config-manager/snapshots */
|
|
42
42
|
export declare function resolveDataDir(flag: string | undefined, env?: Record<string, string | undefined>): string;
|
|
43
|
+
/** 检查 Phase 3 SAFE MODE:存在未恢复 transaction → CLI destructive 应拒绝(返回错误文案,否则 null)。 */
|
|
44
|
+
export declare function checkSafeModeBlocked(homeDir: string): string | null;
|
|
43
45
|
/** 快照 id 校验:拒绝路径分隔符/保留名(防 join 越界) */
|
|
44
46
|
export declare function validateSnapshotId(id: string): string;
|
|
45
47
|
/** 缺省快照选择:metas 已按 createdAt 倒序,取第一个 status !== 'rolled-back' */
|
package/lib/cli/index.js
CHANGED
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
* 缺省数据目录 = $DSH_HOME/dsh-config-manager/snapshots($DSH_HOME 缺省 ~/.dsh)。
|
|
16
16
|
*/
|
|
17
17
|
import os from 'node:os';
|
|
18
|
+
import fssync from 'node:fs';
|
|
18
19
|
import path from 'node:path';
|
|
19
20
|
import { pathToFileURL } from 'node:url';
|
|
20
21
|
import { execFile } from 'node:child_process';
|
|
21
22
|
import { createInterface } from 'node:readline/promises';
|
|
22
23
|
import { stdin as processStdin, stdout as processStdout } from 'node:process';
|
|
23
24
|
import { listSnapshots, planRestore, restore, } from "../core/restore.js";
|
|
24
|
-
import { REINSTALL_ITEMS, buildReinstallPlan, isWindows, } from "../core/reinstall.js";
|
|
25
|
+
import { REINSTALL_ITEMS, buildReinstallPlan, isWindows, detectInstalledDshVersion, writeReinstallRecoveryPoint, } from "../core/reinstall.js";
|
|
26
|
+
import { EnvironmentLockManager, runWithMutationLock, EnvironmentLockUnavailableError } from "../utils/env-lock.js";
|
|
27
|
+
import { Phase3Recovery } from "../core/phase3-host.js";
|
|
25
28
|
const VALUE_FLAGS = new Map([
|
|
26
29
|
['--data-dir', 'dataDir'],
|
|
27
30
|
['--id', 'id'],
|
|
@@ -32,6 +35,30 @@ const VALUE_FLAGS = new Map([
|
|
|
32
35
|
const RESTORE_ONLY_FLAGS = new Set(['--id', '--dry-run', '--profile', '--settings']);
|
|
33
36
|
/** 仅 reinstall 子命令允许的参数 */
|
|
34
37
|
const REINSTALL_ONLY_FLAGS = new Set(['--yes', '--list', '--wipe-config', '--version']);
|
|
38
|
+
/** recover-stale-lock 专用解析:只接受 --data-dir(用于定位 locks 目录),返回 dataDir 选项 */
|
|
39
|
+
function parseCliDataDir(argv) {
|
|
40
|
+
const options = { command: 'recover-stale-lock', dryRun: false, profile: 'web', yes: false, list: false, wipeConfig: false };
|
|
41
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
42
|
+
const flag = argv[i];
|
|
43
|
+
if (flag === '--data-dir') {
|
|
44
|
+
const value = argv[i + 1];
|
|
45
|
+
if (value === undefined || value === '' || value.startsWith('-')) {
|
|
46
|
+
return { ok: false, error: '参数 --data-dir 缺少值 / missing value for --data-dir' };
|
|
47
|
+
}
|
|
48
|
+
options.dataDir = value;
|
|
49
|
+
i += 1;
|
|
50
|
+
}
|
|
51
|
+
else if (flag.startsWith('--data-dir=')) {
|
|
52
|
+
options.dataDir = flag.slice('--data-dir='.length);
|
|
53
|
+
if (options.dataDir === '')
|
|
54
|
+
return { ok: false, error: '参数 --data-dir 缺少值 / missing value for --data-dir' };
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return { ok: false, error: `未知参数 / unknown flag: ${flag}` };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, options };
|
|
61
|
+
}
|
|
35
62
|
/** 解析 CLI 参数(纯函数;help 返回 command:'help',未知/缺值返回错误) */
|
|
36
63
|
export function parseCli(argv) {
|
|
37
64
|
const command = argv[0];
|
|
@@ -40,9 +67,18 @@ export function parseCli(argv) {
|
|
|
40
67
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
41
68
|
return { ok: true, options: { command: 'help', dryRun: false, profile: 'web', yes: false, list: false, wipeConfig: false } };
|
|
42
69
|
}
|
|
43
|
-
if (command !== 'snapshots' && command !== 'restore' && command !== 'reinstall') {
|
|
70
|
+
if (command !== 'snapshots' && command !== 'restore' && command !== 'reinstall' && command !== 'recover-stale-lock') {
|
|
44
71
|
return { ok: false, error: `未知子命令 / unknown subcommand: ${command}` };
|
|
45
72
|
}
|
|
73
|
+
if (command === 'recover-stale-lock') {
|
|
74
|
+
// recover-stale-lock:独立显式 recovery,不接受 destructive 执行参数(只能 --data-dir 定位锁目录)
|
|
75
|
+
for (const flag of argv.slice(1)) {
|
|
76
|
+
if (flag !== '--data-dir' && !flag.startsWith('--data-dir=') && !flag.startsWith('-')) {
|
|
77
|
+
return { ok: false, error: `recover-stale-lock 只接受 --data-dir / accepts only --data-dir` };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return parseCliDataDir(argv);
|
|
81
|
+
}
|
|
46
82
|
const options = { command, dryRun: false, profile: 'web', yes: false, list: false, wipeConfig: false };
|
|
47
83
|
const rest = argv.slice(1);
|
|
48
84
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -128,6 +164,26 @@ export function resolveDataDir(flag, env = process.env) {
|
|
|
128
164
|
return flag;
|
|
129
165
|
return path.join(resolveDshHome(env), 'dsh-config-manager', 'snapshots');
|
|
130
166
|
}
|
|
167
|
+
/** Phase 3 SAFE MODE durable 标记路径:<dataDir>/transactions/safe-mode(dataDir = $DSH_HOME/dsh-config-manager)。 */
|
|
168
|
+
function safeModeMarkerPath(homeDir) {
|
|
169
|
+
return path.join(homeDir, 'dsh-config-manager', 'transactions', 'safe-mode');
|
|
170
|
+
}
|
|
171
|
+
/** 检查 Phase 3 SAFE MODE:存在未恢复 transaction → CLI destructive 应拒绝(返回错误文案,否则 null)。 */
|
|
172
|
+
export function checkSafeModeBlocked(homeDir) {
|
|
173
|
+
try {
|
|
174
|
+
const p = safeModeMarkerPath(homeDir);
|
|
175
|
+
if (fssync.existsSync(p)) {
|
|
176
|
+
const text = fssync.readFileSync(p, 'utf8');
|
|
177
|
+
if (/blocked|true/i.test(text)) {
|
|
178
|
+
return '存在未恢复的配置 transaction(SAFE MODE 激活)。destructive 操作被阻断:请先用恢复流程处理(GUI 恢复 / 显式 recover)后再重试。';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return null; // 读不到标记不阻断(离线 CLI 保守放行读取类)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
131
187
|
/** 快照 id 校验:拒绝路径分隔符/保留名(防 join 越界) */
|
|
132
188
|
export function validateSnapshotId(id) {
|
|
133
189
|
if (id === '' || id === '.' || id === '..' || id.includes('/') || id.includes('\\')) {
|
|
@@ -334,22 +390,87 @@ async function runReinstall(options, io, env, deps) {
|
|
|
334
390
|
printReinstallPlan(plan, io);
|
|
335
391
|
return 0;
|
|
336
392
|
}
|
|
337
|
-
//
|
|
393
|
+
// Phase 3 SAFE MODE:存在未恢复 transaction → 拒绝 destructive(CLI 不旁路 SAFE MODE)。
|
|
394
|
+
const safeMsg = checkSafeModeBlocked(resolveDshHome(env));
|
|
395
|
+
if (safeMsg !== null) {
|
|
396
|
+
io.error(`拒绝执行:${safeMsg}`);
|
|
397
|
+
return 1;
|
|
398
|
+
}
|
|
399
|
+
// 执行(Phase 2 锁:reinstall 卸载/清扫/重装属 GLOBAL destructive,必须成功获取环境锁)
|
|
400
|
+
const lockHome = resolveDshHome(env);
|
|
401
|
+
const lock = new EnvironmentLockManager({
|
|
402
|
+
locksDir: path.join(lockHome, 'dsh-config-manager', 'locks'),
|
|
403
|
+
op: 'cli-reinstall',
|
|
404
|
+
target: plan.version,
|
|
405
|
+
lockVersion: '0.1.0',
|
|
406
|
+
});
|
|
338
407
|
const failed = [];
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
408
|
+
// Phase 3 P0-A:CLI reinstall 也建 intent journal(外部副作用不可证明 → crash 后 NEEDS_ATTENTION)。
|
|
409
|
+
const cliDataDir = path.join(lockHome, 'dsh-config-manager');
|
|
410
|
+
const recovery = new Phase3Recovery({ dataDir: cliDataDir, packageVersion: '0.1.0', fingerprintDataDir: cliDataDir });
|
|
411
|
+
await recovery.initFingerprint().catch(() => undefined);
|
|
412
|
+
try {
|
|
413
|
+
await runWithMutationLock(lock, { op: 'cli-reinstall', target: plan.version }, async (lockCtx) => {
|
|
414
|
+
const runReinstall = async (journalCtx) => {
|
|
415
|
+
io.log(`开始重装 / reinstall started — 版本 ${plan.version}`);
|
|
416
|
+
// Phase 4 F29/F30:在首个 destructive side effect(npm uninstall -g)前,写 durable
|
|
417
|
+
// operation-bound recovery point。若涉及 program 步但无法探测旧版本 → fail-closed,不继续 uninstall。
|
|
418
|
+
if (selection.has('program')) {
|
|
419
|
+
const prev = await detectInstalledDshVersion(exec);
|
|
420
|
+
if (prev === null) {
|
|
421
|
+
const message = '无法探测当前已安装 DSH 版本,拒绝执行 program 步(fail-closed):先手动确认 dsh --version 可用';
|
|
422
|
+
failed.push({ label: 'program recovery point', command: 'detectInstalledDshVersion', reason: message });
|
|
423
|
+
io.error(`✘ ${message}`);
|
|
424
|
+
// 不执行任何步骤(避免在无 recovery point 下 uninstall)
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const opId = journalCtx?.operationId ?? 'cli-reinstall';
|
|
429
|
+
await writeReinstallRecoveryPoint(cliDataDir, {
|
|
430
|
+
operationId: opId,
|
|
431
|
+
environmentFingerprint: recovery.recoveryEnvFingerprint,
|
|
432
|
+
previousInstalledVersion: prev,
|
|
433
|
+
requestedTargetSpec: plan.version,
|
|
434
|
+
createdAt: new Date().toISOString(),
|
|
435
|
+
recoveryHint: `如需手动恢复此版本:npm install -g @deepseek-ai/dsh@${prev}`,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch (rpErr) {
|
|
439
|
+
const message = `recovery point 写入失败,拒绝执行 program 步(fail-closed):${rpErr instanceof Error ? rpErr.message : String(rpErr)}`;
|
|
440
|
+
failed.push({ label: 'program recovery point', command: 'writeReinstallRecoveryPoint', reason: message });
|
|
441
|
+
io.error(`✘ ${message}`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
for (const step of plan.steps) {
|
|
446
|
+
if (step.dangerous) {
|
|
447
|
+
io.log(`⚠ 危险步骤 / dangerous step: ${step.label}`);
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
await exec(step.command);
|
|
451
|
+
io.log(`✔ ${step.label}`);
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
455
|
+
failed.push({ label: step.label, command: step.command, reason });
|
|
456
|
+
io.error(`✘ ${step.label} — ${reason}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
if (lockCtx !== null) {
|
|
461
|
+
await recovery.runExternalIntent({ operationType: 'cli-reinstall', lockCtx, intent: { adapter: 'dsh', ref: 'program', kind: 'Reinstall' }, fn: runReinstall });
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
await runReinstall();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
if (err instanceof EnvironmentLockUnavailableError) {
|
|
470
|
+
io.error(`拒绝执行:${err.message}`);
|
|
471
|
+
return 1;
|
|
352
472
|
}
|
|
473
|
+
throw err;
|
|
353
474
|
}
|
|
354
475
|
io.log(failed.length === 0 ? '重装完成 / reinstall done' : `重装完成但有 ${failed.length} 步失败 / done with ${failed.length} failed step(s)`);
|
|
355
476
|
for (const f of failed) {
|
|
@@ -382,7 +503,31 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
|
|
|
382
503
|
if (options.command === 'reinstall') {
|
|
383
504
|
return runReinstall(options, io, env, deps);
|
|
384
505
|
}
|
|
385
|
-
const
|
|
506
|
+
const lockDataDir = resolveDataDir(options.dataDir, env);
|
|
507
|
+
const lockHome = resolveDshHome(env);
|
|
508
|
+
// recover-stale-lock:独立显式 recovery(只 inspect + prove stale + 原子回收;不自动、无 --force)。
|
|
509
|
+
if (options.command === 'recover-stale-lock') {
|
|
510
|
+
const lock = new EnvironmentLockManager({
|
|
511
|
+
locksDir: path.join(lockHome, 'dsh-config-manager', 'locks'),
|
|
512
|
+
op: 'recover-stale-lock',
|
|
513
|
+
target: lockDataDir,
|
|
514
|
+
lockVersion: '0.1.0',
|
|
515
|
+
});
|
|
516
|
+
const insp = await lock.inspectLockState();
|
|
517
|
+
if (insp.state !== 'STALE_LOCK_DETECTED' && !(insp.state === 'UNKNOWN_STATE' && insp.detail?.includes('无有效 owner'))) {
|
|
518
|
+
// 非 stale 或无法证明 → 拒绝 recovery(owner healthy 的活锁绝不删)
|
|
519
|
+
io.error(`环境锁未判定为 stale(state=${insp.state}),拒绝 recovery。不会触碰活锁。/ lock not stale, recovery refused`);
|
|
520
|
+
return 1;
|
|
521
|
+
}
|
|
522
|
+
const result = await lock.recoverStaleLock();
|
|
523
|
+
if (result.ok && result.removed) {
|
|
524
|
+
io.log(`已回收 stale 环境锁 / stale lock recovered: ${result.detail ?? ''}`);
|
|
525
|
+
return 0;
|
|
526
|
+
}
|
|
527
|
+
io.error(`recovery 失败:${result.detail ?? '未知原因'}(可能二次验证失败,已保留 quarantine 文件供诊断)`);
|
|
528
|
+
return 1;
|
|
529
|
+
}
|
|
530
|
+
const dataDir = lockDataDir;
|
|
386
531
|
if (options.command === 'snapshots') {
|
|
387
532
|
const metas = await listSnapshots(dataDir);
|
|
388
533
|
printSnapshots(metas, io);
|
|
@@ -411,14 +556,47 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
|
|
|
411
556
|
homeDir: resolveDshHome(env),
|
|
412
557
|
profile: options.profile,
|
|
413
558
|
settingsPath: options.settings,
|
|
559
|
+
// Phase 4 统一恢复校验(与 Host/ModelTools 同强度:存在/READY/manifest/blob-hash/symlink/provenance)
|
|
560
|
+
snapshotsRoot: dataDir,
|
|
414
561
|
};
|
|
415
562
|
if (options.dryRun) {
|
|
416
563
|
printPlan(await planRestore(restoreOptions), io);
|
|
417
564
|
return 0;
|
|
418
565
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
566
|
+
// Phase 3 SAFE MODE:存在未恢复 transaction → 拒绝 destructive(CLI 不旁路 SAFE MODE)。
|
|
567
|
+
const safeMsg2 = checkSafeModeBlocked(resolveDshHome(env));
|
|
568
|
+
if (safeMsg2 !== null) {
|
|
569
|
+
io.error(`拒绝执行:${safeMsg2}`);
|
|
570
|
+
return 1;
|
|
571
|
+
}
|
|
572
|
+
// Phase 2 锁:真实 restore 是 destructive(覆盖/删除 $DSH_HOME + 卸载插件),必须成功获取
|
|
573
|
+
// GLOBAL 环境锁;被另一 DSH 任务持有 → 明确报错退出(无 --force 旁路)。无锁目录 → 用默认 dataDir。
|
|
574
|
+
const lock = new EnvironmentLockManager({
|
|
575
|
+
locksDir: path.join(dataDir, '..', 'locks'),
|
|
576
|
+
op: 'cli-restore',
|
|
577
|
+
target: id,
|
|
578
|
+
lockVersion: '0.1.0',
|
|
579
|
+
});
|
|
580
|
+
try {
|
|
581
|
+
// Phase 3 P0-A:CLI restore 也创建 durable journal(关闭 bypass)。
|
|
582
|
+
const recovery = new Phase3Recovery({ dataDir: path.dirname(dataDir), packageVersion: '0.1.0', fingerprintDataDir: path.dirname(dataDir) });
|
|
583
|
+
await recovery.initFingerprint().catch(() => undefined);
|
|
584
|
+
const report = await runWithMutationLock(lock, { op: 'cli-restore', target: id }, async (lockCtx) => {
|
|
585
|
+
if (lockCtx !== null) {
|
|
586
|
+
return (await recovery.runJournaled({ operationType: 'cli-restore', lockCtx, fn: () => restore(restoreOptions) })).result;
|
|
587
|
+
}
|
|
588
|
+
return restore(restoreOptions);
|
|
589
|
+
});
|
|
590
|
+
printReport(report, io);
|
|
591
|
+
return report.failed.length > 0 ? 1 : 0;
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
if (err instanceof EnvironmentLockUnavailableError) {
|
|
595
|
+
io.error(`拒绝执行:${err.message}`);
|
|
596
|
+
return 1;
|
|
597
|
+
}
|
|
598
|
+
throw err;
|
|
599
|
+
}
|
|
422
600
|
}
|
|
423
601
|
/* 直接运行(bin / node src/cli/index.ts)时进入主流程;被 import(测试)时不自动执行 */
|
|
424
602
|
const isDirectRun = process.argv[1] !== undefined
|