huangfuren/dsh-sync

dsh-sync

把整台机器的 dsh 搬到另一台并逐条核对。三层一起走:声明层(设置、凭据、插件声明与 link: 插件源码)、状态层(会话、storages、skills、附件),以及可选的工作区项目代码。会话只改写 zstd 首帧,工作区路径按显式映射改写,最后核对 14 项检查(I1–I13,另含 I6b)并给出 PASS / FAIL / WARN。另可局域网配对直传;merge 模式把 bundle 与「已经在用的机器」逐项比对,分歧未决议就停在写盘之前。

Uncategorizedbackupconfigdeepseek-harness

dsh-sync

中文 | English

把整台机器的 dsh 状态搬到另一台机器。换机后新机不是"能跑",而是跟旧机一样: 同样的设置、同样的插件、同样的历史会话、同样的工作区列表、同样的 skills 链接。

  • 三层一起走:声明层(设置 / 凭据 / 插件源码)→ 状态层(会话 / 工作区记录 / skills / 附件)→ 可选的工作区代码。
  • 会话改不坏:zstd 多帧日志只重写首帧里的 cwd,事件帧逐字节原样保留。
  • 落地即核对:15 项检查逐条给 PASS / FAIL / WARN,不给"应该没问题"。
  • 能再迁一次:第二次开始先出分歧清单,未决议就停在写盘之前。
  • 可撤销:dshsync_undo 按落地账本回退一次导入,内容改过的文件自动跳过。

安装

dsh plugin --profile web add github:huangfuren/dsh-sync

桌面端把 web 换成 desktop。新旧两台都要装,发送端与落地端在同一个包里;装完重启 dsh (9 个工具与 3 条同源路由在启动时注册)。也可在插件市场搜 dsh-sync。

要求 node ≥ 22.19(包声明);会话改写依赖内建 zstd(22.15+ 或 24)。两台需在同一网段。

迁移步骤

两台机器装同一个插件,但做的事不一样:

· 旧机(源) · 新机(目标) 你说什么 · "把本机 dsh 迁移到另一台" · "把旧机的迁移包拉过来" 它做什么 · 打包 + 起发送端,不动本机任何文件 · 接收 + 写入 + 自检 要配置吗 · 不用 · 不用

旧机不需要预先配置任何东西:插件装好、重启过 dsh,在会话里说一句话就开始。

旧机   ① 说一句:打包 + 起发送端
            ↓ 同一个局域网
新机   ② 说一句:填配对码拉过来、自动落地、自动核对
            ↓
       ③ 完全退出 dsh 再打开   →   ④ 核对结果

① 旧机:说一句话

把本机 dsh 迁移到另一台机器:项目代码一起带上,包放到 D:/sync,
口令用 abc123,打包完直接开着等新机来拉。

这句话里其实决定了四件事,少说一件就走默认值:

最好说到的 · 对应参数 · 不说会怎样 项目代码一起带上 · includeWorkspaces=true · 只搬配置和会话,新机的会话打开是空目录 包放到 D:/sync · outDir · 落到系统临时目录(%TEMP%\dsh-sync-bundle),可能被系统清掉 开着等新机来拉 · autoHost=true · 只打包,不起发送端,新机连不上,得再补一句 口令用 abc123 · credentialPassphrase · 明文密钥被剔除、凭据不带,新机要重新填 API 密钥

不给口令时,settings.yaml 里的明文密钥行会被删除并逐条告警 —— 这是有意为之,不是丢了。

dsh 会先给一份清单(要搬的插件、会话数、工作区候选各多大),问你要不要继续;这一步是预演, 没写任何文件。确认后开始打包,完事输出里有一个 6 位配对码和进度页地址。

打包出来的 D:/sync 长这样:

manifest.json        清单,落地前逐项核对
state/               会话 / 工作区记录 / skills / 附件
plugins/             link: 插件的源码
apply.ps1 一键恢复.bat   不装插件也能落地的脚本

② 新机:说一句话

先装插件、重启 dsh,然后:

从局域网把旧机的迁移包拉过来,配对码是 123456,项目代码放到 E:/code。

不想打字也行:设置 → 内置插件 → dsh-sync 迁移 → 点「扫描」→ 点中旧机 → 填 6 位配对码 → 「开始迁移」,效果一样。

它会逐文件核 sha256 → 落地 → 自检,一条命令走完。中途断了就说一次"继续拉",已经收好的文件自动跳过。

`E:/code` 必须是新机上你真要放项目的位置 —— 会话里的路径会被改写到这个目录下面,位置不对
会话就打不开文件。没说这个路径,它会停在写文件之前,不会瞎猜。
两台不在同一网段时:把旧机的 D:/sync 整个目录拷到新机,再说一句"把这个目录落地到 E:/code",
其余一样。

③ 新机:完全退出 dsh,再打开

桌面版从托盘图标退出(点窗口的叉只是收起来),确认进程没了再重开 —— 宿主这时才按新的配置 重建依赖、注册插件和工具。

重开后看三处:

  1. 插件列表:你的插件都在吗?空的话去插件管理器点一次「重新安装 / 修复」,再重启。
  2. 会话列表:能打开,且里面的文件真在(不是空目录)。
  3. 先别改任何设置 —— 宿主可能把内存里的旧配置写回磁盘,等核对完再动。

④ 核对

核对一下这次迁移。

15 项检查逐条给 PASS / FAIL / WARN。FAIL 的意思是这部分状态 dsh 看不到,不是"可能有点问题"; I9 profile 依赖 报 WARN 说明依赖还没装完,重启后通常会补上。

核对通过之前别删 D:/sync(旧机)和收到的包(新机) —— 核对和撤销都还要读它。

出问题了

现象 · 原因 · 怎么做 新机说 bundle 来自本机 · 两台跑反了,或同一台跑了两遍 · 换到另一台重说。只有重装后原地恢复才加 allowSameMachine=true 落地说工作区目录已存在且非空 · 上次落到一半 · 确认里面没有要留的东西,再说"覆盖已有目录"(等价 overwriteWorkspaces=true) 会话在列表里,打开却是空的 · 打包时没带项目代码 · 回旧机带 includeWorkspaces 重打;或接受,代码自己 clone 解密失败 · 口令记错,或包被改过 · 口令没法猜,回旧机重打一次,这次记牢 插件列表空 · 依赖没重建 · 插件管理器「重新安装 / 修复」→ 重启 想退掉这次落地 · —— · 说"撤销这次迁移",先预演一次再真退。落地之后又用过的文件会被跳过并列出来,不会拿旧快照盖掉新内容 连卡片都没有 · 插件没装上或没重启 · dsh plugin list 确认,再完全退出 dsh 重开

再迁一次(第二次及以后)

新机已经用过一段时间时,它后来装的插件、加的工作区、改过的设置不在旧机快照里,直接覆盖就没了。 所以第二次开始说一句"这次别覆盖我这台机器上新增的东西",它会先比一遍再停手,把分歧逐项列出来。

然后按清单回一句就行,例如:

插件 dsh-outline 留我这台的,设置用旧机的,会话只补我缺的那些。

类别 · 可选值 插件 · 留本机(keep-target)/ 用旧机的(take-source) 工作区 · 留本机 / 用旧机的 设置 · 用本机(target)/ 用旧机的(source) 会话 · 不动(skip)/ 只补本机缺的(append-missing)/ 用旧机的 / 用本机的

没提到的那类不会替你决定 —— 宁可让你多看一眼,也不猜。第一次迁移时新机是空的,不用说这句。

工具

工具 · 在哪跑 · 说明 dshsync_export · 旧机 · 导出 bundle(源机只读)。autoHost=true 打包完就地起发送端 dshsync_apply · 新机 · 落地 + 工作区解包 + 路径映射 + 会话改写 + 软链重建 + 依赖重装。支持 dryRun、merge dshsync_undo · 新机 · 按落地账本撤销最近一次 apply:新建的删、覆盖的还原、软链还原。内容变过的自动跳过 dshsync_verify · 新机 · 逐条核对一致性 dshsync_lan_host · 旧机 · 起发送端,给配对码与进度页。action="renew" 换码、"stop" 关停 dshsync_lan_discover · 新机 · 找局域网里开着的发送端(UDP 广播,兜底逐地址扫描) dshsync_lan_pull · 新机 · 配对 → 接收 → 落地 → 核验,一条命令;后台执行,立即返回 jobId dshsync_lan_status · 两端 · 查进度、速率与最终结果 dshsync_remap · 本机 · 迁移完成后目录又动了:就地重映射五个落点,不拷数据

会搬走的:settings.yaml、凭据(见下)、每个 profile 的插件声明与 link: 插件源码、 全部历史会话(只改路径)、工作区记录、skills、附件,以及可选的项目代码。 不搬的:node_modules、dsh-runtimes、cache、logs —— 新机重启时自己生成, 所以包通常只有一百多 MB,带了项目代码才涨到 GB 级。

配置

不用配也能跑。 想改默认值再去 设置 → 内置插件 → dsh-sync 迁移,四项可编辑: profile(* = 全部)、outDir、includePlugins、includeState。其余按次说明:

参数 · 默认 · 说明 includeCredentials · false · 是否带 .credentials.yaml(真实密钥) credentialPassphrase · 空 · ≥6 位:settings 与凭据加密搬运(AES-256-GCM) includeWorkspaces · false · 是否把会话的项目目录一起打包 includeGit · true · 打包工作区时保留 .git workspaceRoot · 空 · 新机上的项目根目录 dshHome · $DSH_HOME / ~/.dsh · harness home

前两项故意不进表单:前者一键把明文凭据打进包,后者若进表单会把口令明文写进 settings.yaml。

密钥策略:推荐给口令(口令错或文件被改,解密直接失败);默认不带凭据(settings 里的明文密钥行 剔除并告警);仅在可信信道才明文携带,用完立刻删。

已知限制

  • `node_modules` 不随行:新机要重建依赖(桌面版退出重开自动重建;web 跑 pnpm install 或 dsh plugin add)。
  • 新机 node 要够新:包要求 ≥ 22.19,会话改写还需要内建 zstd(22.15+ 或 24)。不满足时会话不迁移并明确告警,不静默跳过。
  • 工作区代码默认不带:不打开 includeWorkspaces 时会话能列出但打不开;打开后体积从百 MB 涨到 GB 级。

落位目录已存在且非空时拒绝覆盖,要显式覆盖。

  • 外部软链要有对应路径:skills/ 下指向 $DSH_HOME 外的链接,新机缺目录时如实报 WARN(I6b),不偷改指向。
  • 宿主跨 `major.minor` 时插件可能被启动时拒装,需在插件管理器逐个授予精确版本豁免。
  • 传输任务状态在内存里:插件重载或重启 dsh 后任务失效,重跑拉取按哈希续传。
  • Windows 首次监听会弹防火墙,选「专用网络」允许;进度页是浏览器页面,不在 DSH 界面里。

开发

npm run check   # node --check 全部源文件
npm test        # 261 个测试

测试用合成数据(自建 zstd 多帧 + 临时 dshHome),不读真实 $DSH_HOME、不访外网;局域网用例只走 127.0.0.1。

License

MIT。原创 JavaScript,零第三方运行时依赖(仅 Node 内置模块);设计上参考过若干公开 MIT 项目的结论而非源码, 详见 THIRD_PARTY_NOTICES.md。