@asong111/dsh-cloud-sync
v0.3.12
Published
Cloud sync for the dsh web GUI: bidirectionally sync plugins, config, agent-presets, skills and task history between two machines through a private Git remote (default SSH). Auto-installs dependencies after pull; excludes plaintext secrets (credentials/SS
Readme
dsh-cloud-sync
在两台不同网络的电脑之间,通过一个私有 Git 远端(默认 SSH 免密)做 DSH 云端同步:把 ~/.dsh 下的插件清单、配置、agent presets、skills、任务历史双向同步,做到换台电脑接着干。拉取后自动安装依赖,并默认排除明文密钥。
独立插件项目(不依赖 dsh-web-ui 全家桶),与 DSH 本身同款双 face(host + client)插件范式,热插拔、不改 dsh 源码。
特性
- 双向同步:
push(本机 → 云端远端)、pull(云端远端 → 本机)。 - 自动接力:
autoSync定时「先拉后推」——autoPush默认开,A/B 机顺序切换时本机变更自动上传,无需手动点「上传」。 - 自动装依赖:
pull后检测到package.json/pnpm-lock.yaml变化时,自动dsh plugin install复现插件(installAfterPull默认开,可在 GUI 关)。 - 安全:默认排除
.credentials.yaml、dsh-ssh.json(含明文 SSH 密码)、设备匿名 ID、pet.json;node_modules/.git一律不进镜像。 - 冲突保守:不静默覆盖;冲突时先备份本机旧文件,标记「待处理」,绝不
--force推送。 - GUI:侧边栏「云同步」面板(配置远端 / 一键上传 / 一键拉取 / 自动同步开关与间隔 / 拉后自动推送开关)。
- 动作日志:面板「日志」页按天展示每次 push/pull/resolve 新增/修改/删除的具体文件;原始数据落盘
~/dsh-sync/logs/<profile>/sync-<日期>.jsonl。 - 同步介质:Git(有版本历史、可冲突检测、可
age/git-crypt加密)。
支持的同步内容
| 内容 | 路径 | 说明 |
|---|---|---|
| 插件清单 | profiles/<profile>/package.json + pnpm-lock.yaml | 不拷 node_modules,拉取后自动 install |
| 配置 | profiles/<profile>/cordis.patch.yml、settings.yaml、cordis.patch.yml(home) | settings.yaml 已确认无明文密钥 |
| agent presets | .agent-presets/ | 梁神等预设 |
| skills | skills/ | |
| 任务历史 | sessions/、storages/、task-board/ | |
明确不同步(不计划纳入):
attachments/(会话附件对象,换机后旧会话中的贴图/附件不迁移)——需要的话请自行用云盘同步目录(Drive/坚果云)管理;skill 内容(含其展示/媒体资产)照常同步,作为 skill 的组成部分跟随版本。运行时/临时态同样永不入镜像(见安全红线)。
架构 — 同步是怎么工作的
给开发者看的三张图,覆盖整个设计。引擎(host 半)在 src/engine.ts,面板(browser 半)在 src/client/。
1. 数据流(单机)
~/.dsh (live dsh data)
+------------------------------------------------------------------+
| profiles/<profile>/ package.json cordis.patch.yml |
| pnpm-lock.yaml pnpm-workspace.yaml |
| .agent-presets/ skills/ sessions/ storages/ task-board/ |
| settings.yaml cordis.patch.yml |
+---------------------------------+--------------------------------+
|
push: content-hash copy ① | pull: copy back ③ (union rules,
(skip node_modules/.git, | backup before overwrite,
secret blacklist, | active files <60s skipped)
active files <60s) |
v
+------------------------------------------------------------------+
| ~/dsh-sync/<profile>/ mirror working tree (a git repo) |
+---------------------------------+--------------------------------+
|
git pull --rebase ② | git add -A && commit ②
(remote -> mirror) | && git push (mirror -> remote)
v
+------------------------------------------------------------------+
| private Git remote [email protected]:<user>/dsh-sync.git |
| (SSH; history + conflict detection come free with git) |
+------------------------------------------------------------------+
per-machine side stores (never synced):
~/dsh-cloud-sync-backup/<ts>/<dsh-rel> pre-overwrite backups
~/dsh-sync/logs/<profile>/sync-YYYY-MM-DD.jsonl action log- ①
push:把~/.dsh增量复制进镜像(按 sha256 内容哈希,相同则跳过;排除黑名单与近 60 秒内仍在变动的文件),然后git add -A && commit && push。 - ② git 负责镜像 ↔ 远端,pull 用
--rebase,冲突时保留镜像原地不动、提示处理,绝不--force。 - ③
pull:镜像复制回~/.dsh;覆盖/删除前先备份到~/dsh-cloud-sync-backup/<时间戳>/(保留 dsh 相对路径);sessions/storages/task-board/是并集语义——远端没有的本地内容绝不删除(防止误删未推送的会话)。
2. 双机接力(顺序切换,时间段不重叠)
autoSync(定时)+ autoPush(默认开)让同步循环变成全自动接力:A 机在本机周期内就把变更推上云端,B 机下次拉取即可接上,无需手动点「上传」。
┌──────────────┐ ┌──────────────┐
│ Machine A │ │ Machine B │
│ (works) │ │ (works) │
│ │ │ │
│ autoSync │ │ autoSync │
│ every N min │ │ every N min │
└──────┬───────┘ └──────┬───────┘
│ │
│ ┌─────────────────────────────┐ │
└──►│ private Git remote (SSH) │◄────┘
│ [email protected]:<user>/sync │
└─────────────────────────────┘
A pulls (enter) then pushes its B pulls A's work on
work every tick (autoPush ON) boot, then pushes back3. 单次 autoSync 的决策流程
autoSync tick (every autoSyncIntervalMin)
│
▼
fetch probe (1 network round-trip)
│
├── fetch failed? ──────────► skip — retry next tick (no log)
│
▼
remote has new commits (behind > 0)?
│
├── no: nothing new on remote
│ ├─ autoPush on ──► local content-hash scan, push if changed
│ └─ autoPush off ─► done — full no-op, no log
│
▼ yes
pull --rebase (remote → mirror)
│
├── conflict? ──────────────► STOP — mirror left pending;
│ resolve via GUI「以远端为准」
├── hard error? ─────────────► STOP — retry at the next tick
│
▼ ok
copy mirror → ~/.dsh
• backup before every overwrite / delete
• sessions/ storages/ task-board/ are a UNION — never pruned
• files modified within 60s are skipped (reported as skipped)
│
├─ deps changed && installAfterPull? ──► dsh plugin install
│
▼
autoPush on? ── no ──► done
│ yes
▼
copy ~/.dsh → mirror (content-hash aware)
│
├─ nothing changed? ────────► skip — no empty commit
├─ only active files? ──────► skip them too, push nothing
▼
git add -A && commit && push关键行为
- 复制以内容哈希(sha256)为准,而非 mtime:字节相同的文件不会重复拷贝,空闲机器不产生空提交。
- 60 秒活跃窗口保护(
DSH_CLOUD_SYNC_ACTIVE_MS可覆盖):push/pull 双向都不拷贝、不覆盖、不删除「可能正在写入」的文件;在 mtime 窗口之上加 size 稳定判定(DSH_CLOUD_SYNC_SETTLE_MS,默认 10s)——同一文件连续多次同步观测字节数不变即视为写完放行,长流式会话写入不再被永久跳过;跳过结果进入skipped并在面板/日志显示。 - 定时 tick 探测优先:每个 tick 只做 1 次 fetch + 本地 rev-list 判断
behind;远端无新提交且无需推送时完全 no-op——不扫描文件树、不写日志(日志只记录真实发生的 pull/push)。 autoPush只在「拉取成功且无冲突」后触发:失败或冲突的 pull 绝不自动推送(镜像保持待处理,等待 GUI 处理)。- 本机改动保护(基线):本机自上次成功同步后改动过的文件(如手动卸载插件改动的
package.json)不会被 pull 覆盖或删除,而是保留并在下次 push 推上云端——「卸载了又被拉回来」不再发生;首次同步(无本机基线)仍以云端为准。 - 密钥与运行时态永不进镜像:
.credentials.yaml、dsh-ssh.json、.anonymous-user-id、pet.json、node_modules/、.git/。
单写者契约(接力模型)
插件按顺序接力设计:任何时刻只有一台机器在写,另一台 pull 后接管。在该约束下:
storages/workspace.json是 dsh 的全局权威注册表(工作区列表与archivedSessionIds归档标签)。当前使用的机器独占它,另一台 pull 时整文件覆盖——不做字段级合并。合并且要猜测 dsh 的 schema,且无法表达「删除」(归档=加标签、恢复=删标签,纯 union 会把已经恢复的会话又归档回去)。- 若
workspace.json(或任何文件)出现冲突,说明接力断了一环:某台机器改了它却没先 push。解决方式:确认「后使用的那台」的版本为最新后「以远端为准」(本机冲突版本已备份在~/dsh-cloud-sync-backup/)。
安装与使用
# A 机 / B 机都执行一次
dsh plugin --profile web add @asong111/dsh-cloud-sync
# GUI 侧边栏「云同步」:
# 1) 填远端仓库地址(如 [email protected]:ASong111/dsh-sync.git)
# 2) 点「↑ 上传到云端」完成首次 push
# 3) 另一台电脑点「↓ 从云端拉取」→ 自动装依赖两机 SSH 免密(Git 鉴权,一次配置)
- 同一把密钥:把 A 机
~/.ssh/id_rsa+id_rsa.pub复制到 B 机~/.ssh/;或 - 各自密钥:在 B 机新生成密钥,把公钥加到 GitHub 账号。
installAfterPull走 npm registry 网络,与 Git 的 SSH 鉴权相互独立。
完整的两机部署走查(首次上传/拉取、link: 依赖注意事项、ERR_PNPM_UNEXPECTED_STORE 修复、冲突恢复)见 DEPLOY.md。
开发
pnpm install
pnpm test # vitest:excludes / store / engine(双机模拟)
pnpm build # tsc + tsdown -> lib/
pnpm watch安全红线
以下文件永不进入同步(硬编码黑名单,无法通过配置强行纳入):
~/.dsh/.credentials.yaml~/.dsh/dsh-ssh.json~/.dsh/.anonymous-user-id~/.dsh/pet.jsonnode_modules/、.git/- 运行时/临时态永不同步:
*.lock(如ledger-v2.lock)、*.tmp、*.writing后缀,以及scheduler-v2.json(task-board 调度心跳)——每机临时状态(ledger-v2.json任务数据仍正常同步)
许可证
Apache-2.0
