dsh-plugin-sync:把 DSH 插件清单搬进你的私有 GitHub 仓库

前言

DSH 的理念是「一切皆插件」。实际用过几个 profile 之后,问题会慢慢变成第二类问题:插件装在哪台机器上就跟着哪台机器走。换新机器要重新找仓库、重新 clone、重新填一份配置;从本地目录(link: / file:)装的插件甚至连「重新找」这一步都没有,因为本来就没有 registry 版本可重装。

dsh-plugin-sync 给出的办法是:把插件清单、版本、启用状态和配置写进你自己的 GitHub 私有仓库,不经过任何第三方云。插件由 Jfreey 维护,在社区目录里归在「联网工具」分类下,仓库目前星标仍是 0,属于早期项目。

下面介绍它做什么、怎么装、怎么用、以及有哪些必须知道的边界。

这是什么

一句话定位:它用你自己的私有 GitHub 仓库当 DSH 插件的清单和源码备份,跨机器同步靠「推送 / 拉取」两端自己确认。

它不替代插件管理器。安装、卸载、升级仍然在 DSH 的插件管理器里做;本插件只负责把「当前装了什么、什么版本、哪些配置、来源在哪」记录下来,推送到仓库,再在别的机器上按相同的记录装回来。

维护者是 Jfreey,许可证为 MIT。源码托管在 GitHub 的 Jfreey/dsh-plugin-sync,目录页是 skillhub.cn 上的插件索引页(社区目录,与 DeepSeek / 幻方无官方从属关系)。

已核实的核心能力

按 README 的「做什么」一节整理,下面这些能力都能在仓库里核对。

清单:记录所有非安装自带的 bundle,包括名称、版本、启用状态、安装来源、声明的 row id。

来源追溯:每个插件从哪来,优先级是本地 checkout 的 .git remote、其次包自身的 repository 字段,monorepo 会一直显示到子目录;查不到就标「来源未知」,不猜。

配置备份:备份 profile 里这些 row 的 override 层。离开本机前,令牌形态的值统一替换成 <redacted>;apiKeyEnv 这类「指向密钥的环境变量名」原样保留,因为它本身不是密钥。

源码备份:link: / file: 安装的插件没有 registry 版本,源码会以真实文件提交到同一个私有仓库,一次推送一个提交,可以在 GitHub 上直接读、看 diff、用仓库搜索。还原时下载、校验 SHA-256、写盘,再 link 安装。

还原:缺失安装、版本替换、写入配置、切换启用状态,分组勾选。README 明确写了「你没点,它不动」,每条结果回报宿主真正做了什么。

更新检查:来源发布了比本机新的版本时给提示;npm 装的能一键更新,本地目录的留给你自己处理。

清理:每行一个「同步」开关(只影响这台机器);「清理备份」能删掉清单条目,也能连带删掉仓库里的源码文件。

推送与拉取:推送在插件变更后自动触发(30 秒防抖)加手动;拉取是定时加手动,只算差异,不会自己动手。

安装与启用

README 给出的安装方式是把插件管理器指向仓库目录,本仓库本身就是可安装 bundle(package.json 声明了 dsh.bundle):

dsh plugin --profile desktop add /绝对路径/plugin-sync

注意命令里的路径是本地 checkout 或提取出来的目录,不是 github: 形式。装完刷新页面,设置里会出现「插件同步」入口。

要求 DeepSeek Harness 0.2.x,依赖 settings.section、ctx.locale、ctx.pluginManager、ctx.configEditor、ctx.credentials 这几个契约。低于这个版本不会可用。

绑定仓库与登录

插件需要一个私有 GitHub 仓库来承载数据。默认数据仓库名是 dsh-plugin-sync-data,dsh-plugin-sync 这个名字留给插件自己的公开仓库,两者不撞名。

1、建私有仓库:打开 github.com/new?name=dsh-plugin-sync-data&visibility=private,点击 Create。README 特别提示要建空仓库,不要勾 README。

2、生成细粒度令牌:页面提供了预填链接,令牌的名称、描述、90 天有效期、Contents: write 都已设置好。Repository access 选 Only select repositories,勾上刚建的仓库,Generate,复制 github_pat_…。

3、登录:把令牌粘进第一个输入框,点「从我的仓库里选」——这一步令牌只用于列仓库,不落盘——挑一个仓库,再点「校验并保存」,这时才真正登录并存令牌。

README 里有两条容易被忽略的行为。第一,登录后先拉取、不会把本机状态推上去,所以新机器用空清单或旧版本覆盖共享 manifest 的风险被规避了;要让仓库第一次有内容,登录后点一次「推送」。第二,仓库必须是私有的,配置会离开本机,公开仓库等于公布;公开仓库在登录时和每次推送前都会被拒,除非显式勾选允许公开仓库的确认项。

选到一个不是同步仓库的仓库会被拦住:非空且没有 manifest 的仓库是别人的项目,页面会列出其中几个文件名,你确认后才写;同名文件但格式不对则直接拒绝。经典令牌(repo scope)也能用,但权限更宽,README 不推荐。插件不读 git 凭据助手,也不读钥匙串。

仓库里实际有什么

根目录是一个 dsh-plugin-sync.json,记录设备和插件条目。结构如下,字段名和示例都来自 README:

{
  "schema": 1,
  "updatedAt": "2026-11-05T05:10:00.000Z",
  "devices": {
    "3f2a91c0d4e8": { "name": "workstation", "updatedAt": "…" }
  },
  "plugins": {
    "dsh-searxng-search": {
      "version": "0.5.0",
      "enabled": true,
      "source": "local:dsh-searxng-search",
      "repository": "https://github.com/octocat/dsh-searxng-search",
      "repositoryFrom": "git-remote",
      "config": { "searxng-search": { "proxy": "http://127.0.0.1:7890" } },
      "snapshot": {
        "format": "tree",
        "path": "snapshots/dsh-searxng-search/0.5.0",
        "commit": "9c33e2c…",
        "files": 18,
        "bytes": 292546,
        "sha256": "…"
      },
      "redacted": 1,
      "updatedAt": "…",
      "by": "3f2a91c0d4e8"
    }
  }
}

README 的截图用的也是 octocat / workstation 这类虚构示例数据,不含真实账号或机器信息。

源码备份放在 snapshots/<名字>/<版本>/ 下,通过 Git Data API 提交,所以一次推送就是一个提交,仓库保持可读。文件按内容寻址,没变的文件不重复上传;被删掉的文件在同一个提交里删除;可执行位保留。收哪些文件看 package.json 的 files 白名单(没写就全收),外加 package.json、cordis.patch.yml、locale/、assets/、scripts/、图标和 README。node_modules、.git、.env*、日志、软链永远不收。单文件超过 1 MiB,或整份快照超过 maxSnapshotBytes,整份拒绝,不会上传半份。

两台机器写同一个文件时,每次推送先读当前版本、把本机条目合进去、再用读到的版本号写回;返回 409 说明别人先写了,就重读再合并,最多三次。本机没有的远端条目原样保留,文件是并集,空机器推上去不会删掉任何东西。

主要配置项

全部字段和默认值都写在 README 的配置表里,下面挑出最常用的几项:

字段 默认值 含义
repo 空 私有仓库的 owner/name。
apiBase https://api.github.com GitHub Enterprise 时修改。
manifestPath dsh-plugin-sync.json 清单文件的位置。
snapshotsPath snapshots 源码备份的位置。
snapshotFormat tree tree 存真实文件;json 退回单文件 base64 快照。
deviceName 主机名 写进清单的设备名。
deviceId 自动派生 稳定身份,为空时按主机、平台、home 派生。
autoPush true 插件变更后自动推送,30 秒防抖。
autoPull true 定时拉取清单。
intervalMinutes 15 定时周期,限制在 1–1440。
includeSnapshots true 上传本地插件的源码。
maxSnapshotBytes 4194304 单个插件的源码总量上限。
maxManifestBytes 2097152 清单上限,超过直接拒绝。
excludedRows [] 这台机器永不发布的 row id。
excludedPlugins [] 这台机器不推送、也不参与差异的插件。
allowPublicRepo false 允许公开的同步仓库。

页面里改的设置存在 ~/.dsh-plugin-sync/settings.json,在 profile 之外,不重写任何 profile 文件;cordis.patch.yml 里的 row 配置提供默认值。

安全边界

README 有一节「安全说明」,有四条值得在动手前读完。

一是监听范围:插件只接受回环浏览器的请求,/state 和 /api 要求自定义头,且要求对端是 127.0.0.1 或 ::1。README 直接提示「请让 Harness 只监听回环」。

二是令牌绑定主机:登录时调 GET /user,并把 base 和令牌一起写进凭据记录;之后所有请求都用这个 base,改设置不可能把令牌送去别处,换 API 主机必须重贴令牌。

三是脱敏分两层:敏感形态的键名和敏感形态的值(令牌形态、私钥等)都变成 <redacted>,apiKeyEnv 这类引用保留。源码里出现令牌形态的值,整份拒绝并点名文件。清单不含本机绝对路径,本地插件的路径被替换成 local:<name>,还原靠源码备份。

四是令牌存储:存在凭据记录 dsh-plugin-sync/github(存储文件权限 0600),插件自己的设置文件同样是 0600,没有路由会返回令牌。

典型流程

结合上面的能力,换机器可以按这个顺序走。

1、新机器上装好本插件,按上面的命令指向 checkout 目录,刷新页面,进入设置 → 插件同步。

2、登录:建好或选好私有仓库,粘贴细粒度令牌,选仓库,校验并保存。这一步只有拉取,不会把新机器的空状态推出去。

3、点「推送」给仓库播种;之后这台机器上的插件变更会自动推送(30 秒防抖),也可以随时手动推。

4、回到原来的机器或任意新机器,插件同步页会显示差异,勾选要还原的条目(缺失安装、版本替换、配置写入、启用状态),点一次应用。每条结果都回报宿主实际做了什么。

5、从本地目录装的插件会连带还原源码:下载、校验 SHA-256、写盘、link 安装。不需要手动再 clone 一次。

适用场景与注意

适合这样的场景:DSH 有多个 profile 或多台机器,插件主要从 GitHub 仓库装、希望自己掌控备份位置,并且能接受「私有仓库 + 手动确认」这层约束。两个独立存储等于两个 DSH profile,适合 profile 级隔离。

需要注意的地方也明确:插件以当前 dsh 进程的权限运行,能读写凭据记录和本地文件,安装前应检查源码与许可证(本项目为 MIT)。它要求 Harness 0.2.x 的几项契约,版本不符就用不了。同步仓库必须保持私有,配置会离开本机。另外它是个人备份工具,不是发布通道——作者的调研结论里写明,发布分发是下一版要做的事,市场 PR 仍需手动。

结尾

dsh-plugin-sync 解决的是一个很具体的问题:把「DSH 装了哪些插件、什么版本、什么配置、源码在哪」放进你自己的私有 GitHub 仓库,换机器时不用重新找一遍。清单、版本、配置、源码备份、还原、更新提示、清理都在一个设置页里完成,密钥不外发,公开仓库和不可识别的 manifest 都会被拒绝。

目录页:https://www.skillhub.cn/plugins/Jfreey/dsh-plugin-sync
GitHub:https://github.com/Jfreey/dsh-plugin-sync

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜