从「装个插件」到 1 GB 工作台:一次 DSH 插件安装的四层坑

作者:

在

需求只有一句话:「安装这个插件 github.com/dsh-market/dsh-market」。原以为是一条 npm install 的事,实际连续踩了四层坑:仓库名和包名不一致、pnpm 需要 git 而机器上没有、git 协议连不上、目标插件根本不是「npm 式插件」,而是一套 1 GB 的自包含逆向分析工具链。

本次操作在本人持有的 Windows 11 机器上进行,插件来自公开仓库、按其许可证使用;文中路径与账号相关部分已脱敏,不包含任何令牌、密钥或个人数据。

一、先看环境

项值
宿主DeepSeek Harness Desktop 0.2.1-rc.3
运行时Node 24.21.0 / pnpm 11.28.5(随包提供)
DSH_HOMEC:\Users\<用户名>\.dsh
profiledesktop,由 Electron 应用托管
网络中国大陆,github.com 的分域名可达性不一致
磁盘C: 剩余 118 GB,D: 剩余 716 GB

先记住一个关键认知:DSH 的插件不是「丢进 plugins 目录」就生效,而是 profile 的 package.json 依赖 + dsh.profile.bundles 加载栈 + node_modules 三者配合,cordis.patch.yml 负责补丁层。

二、第一层:仓库名不等于包名

仓库叫 dsh-market/dsh-market,但 package.json 里的 name 是 dshmarket,没有连字符。npm 上搜 dsh-market 是 404,真正的包是 dshmarket@1.70.2,由 GitHub Actions 以 OIDC trusted publisher 发布并带 SLSA provenance 签名——这类可验签的社区包值得优先信任。

dsh plugin --profile desktop add dshmarket

输出 + dshmarket 1.70.2 与 Done in 5.6s using pnpm v11.28.5。dsh plugin add 不只会改依赖,还会把它追加进 bundle 加载栈:

// profiles/desktop/package.json(安装后)
{
  "dependencies": { "dshmarket": "^1.70.2" },
  "dsh": { "profile": { "bundles": [
    "@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dshmarket"
  ]}}
}

装完三秒内,插件自己写出了 .dsh-market\log.ndjson 和 state.json,说明宿主已经热加载,不需要重启。

这里有个容易忽略的警告:插件的 README 明确写着桌面端要用应用内的「设置 → 插件」安装,如果对 web profile 执行 dsh plugin add,会装进 profiles/web,桌面端根本看不到。本次直接对 desktop profile 操作,才避开了这个坑。

三、第二层:pnpm 需要 git,而机器上没有

接着在市场里装另一个插件,报错:

[ERR_PNPM_GIT_FETCH_FAILED] Failed to fetch from the git repository
"https://github.com/Minglink/dsh-infinite-gen-4.git":
'git' 不是内部或外部命令,也不是可运行的程序

定位很明确:pnpm 在拉一个 git+https 依赖,调用的是系统 git,而 PATH、Program Files\Git、%LOCALAPPDATA%\Programs\Git 全是空的——不是网络问题,也不是插件作者的问题。好消息是失败的安装没有留下残留,package.json 和 pnpm-lock.yaml 里都没有半个条目。

为什么市场会走到 git?它的安装策略是三档兜底:npm 包 → 作者提供的 Release 预构建 tarball → 整个 GitHub 源码仓库。这个插件只提供了 GitHub 源码,于是落到最后一档,必须调用 git。

四、第三层:git 装好了,git 协议还是连不上

选型上,winget install Git.Git 会走机器级安装并弹 UAC,在当前会话里容易卡住;MinGit 只有 40 MB,够 pnpm 用但功能不全;最后选了 PortableGit(约 60 MB,bash / ssh / lfs 都在),解压即用、不需要管理员权限。

装完立刻遇到第二个问题:Windows 进程的环境变量在启动时就固定了,正在运行的 DSH 不会因为你改了用户 PATH 就看见 git。做法很土但有效——在一条已经在宿主 PATH 上、且用户可写的目录里放个 shim:

@echo off
"C:\Users\<用户名>\AppData\Local\Programs\PortableGit\cmd\git.exe" %*

之后 cmd /c git --version 立刻返回 git version 2.56.0.windows.4,不用重启宿主。

但 git 装上后,git ls-remote 依旧失败:Recv failure: Connection was reset。实测是分域名的——api.github.com、codeload.github.com 可达,github.com 的 git smart HTTP 连接会被重置。pnpm 调用 git,所以 pnpm 一起失败。

镜像站要测三件事,不是测速

镜像HEAD 延迟支持 git 协议支持 HTTP Range
gh-proxy.com661 ms是否
ghfast.top1860 ms是是(206)
github.moeyy.xyz超时——
直连 github.com1034 ms(假象)否,连接被重置—

HEAD 快不代表能下载,更不代表能断点续传。这张表里的第三列,就是后面最耗时那个坑的伏笔。

先确认镜像确实能跑 git 协议(而不是只能下载 raw 文件):git ls-remote https://ghfast.top/https://github.com/Minglink/dsh-infinite-gen-5.git 在 1.7 秒内返回了分支哈希。然后做全局 URL 重写,让所有 GitHub 的 HTTPS 操作透明走镜像:

git config --global url."https://ghfast.top/https://github.com/".insteadOf "https://github.com/"

效果很直接:普通 GitHub URL 的 ls-remote 从 24.7 秒超时变成 1.5 秒成功;git clone --depth 1 拉一个 75.7 MB 的仓库只用 15 秒。副作用要知道:这条配置对所有 GitHub HTTPS 操作生效,镜像不可用时记得撤销:

git config --global --unset-all url.https://ghfast.top/https://github.com/.insteadOf

五、第四层:源码包不等于完整包

到这一步 git 已经可用,但继续调查发现:就算装成功也是残废的。

  • 仓库已经从 dsh-infinite-gen-4 改名为 dsh-infinite-gen-5,GitHub 会自动 301;
  • 插件自称「无限五代 IG5」,实际是一套逆向分析工作台:Ghidra 静态分析、x64dbg 原生调试、HTTP/HTTPS 采集、去混淆,Core 8 / Full 40 个工具;
  • package.json 的 files 列了 runtimes/、third_party/,但仓库里根本没有这两个目录;
  • 没有任何 postinstall 脚本,pnpm 装完就是一堆源码,runtimes/ 空着,引擎跑不起来;
  • 作者在 README 里写得很清楚:源码 ZIP 与 git clone 是维护源码,不是完整离线安装包。

也就是说,市场里那条 git 路径对这个插件本身就不适用。它的官方安装器 install.ps1 会做市场不会做的两件事:把资产铺进 ~/.dsh/plugins,并在 profile 里建 junction、改 package.json。于是只剩两条路:

路线下载量风险
市场 git 装源码,再用 install.ps1 联网补资产75 MB + 约 1 GB多一步,中途失败概率高
官方 v1.1.0 完整包离线安装1.02 GiB官方支持路径,确定性高

六、1 GB 的下载,才是硬仗

第一次用 Invoke-WebRequest 下 1.02 GB,到 707.8 MB 被镜像掐断:An existing connection was forcibly closed by the remote host。改用 curl -C - 续传,直接报:

curl: (33) HTTP server does not seem to support byte ranges. Cannot resume.

这就是前面那张表第三列的兑现:gh-proxy.com 不支持 Range。换成支持 206 的 ghfast.top,从 888 MB 处一次续到底:

curl.exe -L --fail --retry 2 --connect-timeout 20 --speed-limit 1024 --speed-time 90 -C - -o $zip $url

最终文件 1097399607 字节,SHA-256 为 83c32513533ce86a79c64db43eaaa421f4fa669520b75b4291eb9aca93fd3203,与 GitHub Release API 公布的 asset digest 逐字节一致。跨镜像续传的 1 GB 文件,没有哈希就没有「装的是什么」,这一步是整个流程里最值得做的校验。

七、安装器 15 分钟没进入复制阶段

官方安装器 install.ps1 写得很严谨,逻辑大致是六步:

  1. 读每个 profile 的 package.json,算 SHA-256 快照,防止并发改写;
  2. 校验整个运行包,52,000+ 个文件逐个哈希;
  3. 复制到 ~/.dsh/plugins/dsh-infinite-gen-5,先 staging,失败可回滚;
  4. 在每个 profile 的 node_modules 里建 Junction 指向物理目录;
  5. 改写 package.json:加入 file: 依赖并追加到 bundles;
  6. 旧版本移到 ~/.dsh/ig5/artifacts/ 备份。

问题出在第 2 步:单线程 PowerShell 逐文件哈希 5 万多个文件,跑了 15 分钟、CPU 时间 700 秒还没进入复制阶段。既然 zip 的 SHA-256 已经和官方发布值对齐,这里选择跳过逐文件校验,按安装器的语义做等价手工安装:

robocopy "D:\IG5\extract\plugin" "C:\Users\<用户名>\.dsh\plugins\dsh-infinite-gen-5" /E /MT:16 /R:1 /W:1

New-Item -ItemType Junction -Path "C:\Users\<用户名>\.dsh\profiles\desktop\node_modules\dsh-infinite-gen-5" -Target "C:\Users\<用户名>\.dsh\plugins\dsh-infinite-gen-5"

# 再改 profile/package.json:
# "dsh-infinite-gen-5": "file:../../plugins/dsh-infinite-gen-5"

53,362 个文件、2.19 GB,robocopy 多线程复制用了 0.9 分钟。安全性上有个巧合:完整包里 runtimes\manifest.json、runtimes\ghidra\runtime.json、runtimes\x64dbg\runtime.json 本来就是齐的,安装器的清单写入只是复制一份,所以跳过校验不影响运行时读取。另外做了两件保险:安装前把 profile 的 package.json、cordis.patch.yml、pnpm-lock.yaml 备份到另一个盘;装完清掉 2.2 GB 的临时解压目录和 75 MB 的克隆,只保留 1.02 GB 的 zip 供离线重装。

八、验证:不是「文件存在」,是引擎真的启动

改完 package.json 后宿主热重载,当前会话里直接出现了 ig5_* 工具和 8 个 ig5-* 技能(ig5_doctor、ig5_open、ig5_decompile、ig5_triage 等)。调用 ig5_doctor(engine="ghidra") 返回:

{
  "ok": true,
  "engine": "ghidra",
  "version": "12.1.4",
  "python": "3.12.10",
  "pyghidra": "3.1.0",
  "runtimeReady": true,
  "startupVerified": true,
  "unsupported": ["dbg"]
}

startupVerified: true 意味着 Ghidra 12.1.4 真正启动成功,而不是「文件存在」。如实记录两个未验证项:x64dbg 调试车道那次 doctor 调用被中断,没有拿到结论;安装器被中断,所以包内 5 万多个文件没有逐文件哈希,完整性由 zip 的官方 SHA-256 锚定。

九、最终状态

项值
Git2.56.0.windows.4(PortableGit,用户级)+ 全局镜像重写
插件市场dshmarket@1.70.2
IG5dsh-infinite-gen-5@1.1.0,53,362 文件 / 2.14 GB
接入方式Junction + file: 依赖 + bundle 加载栈
引擎Ghidra 12.1.4 已验证;x64dbg 未验证
体积成本插件 2.14 GB + 离线 zip 1.02 GB

十、可复用的经验

  1. 先确认包名。GitHub 仓库名和 npm 包名经常不一致;npm 上带签名或 provenance 的包优先。
  2. pnpm 的 git+https 依赖强依赖系统 git。Windows 上的表现是「’git’ 不是内部或外部命令」,很容易被误判成网络问题。
  3. 镜像测速要测三件事。HEAD 能否 200、能否跑 git 协议、是否支持 Range——三者不等价,这次就栽在第三项。
  4. url.insteadOf 是治本方案。一条 git 配置让所有 GitHub 依赖透明加速,比逐个替换 URL 靠谱,但要知道怎么撤销。
  5. 已运行进程不会刷新 PATH。想让工具「立刻生效」,要么重启宿主,要么在宿主 PATH 里放 shim。
  6. DSH 插件 = 依赖 + bundle 栈 + node_modules。Junction 让物理目录与 profile 解耦,file: 依赖绕开 pnpm 重新解析。
  7. 区分「源码仓库」和「发布完整包」。带原生运行时的插件几乎都只有 Release 完整包能跑,源码版装了也是空壳。
  8. 校验要挑成本。逐文件哈希 5 万个文件超过 15 分钟,收益有限;但对下载物做一次 SHA-256 是必需且便宜的,1 GB 文件约 5 秒。
  9. 改宿主配置前先备份。package.json、cordis.patch.yml、lockfile 各留一份,第三方安装器虽然也备份,自己有一份更安心。

十一、合规与卸载

这个插件是 CC BY-NC-SA 4.0 许可,自带原生二进制与调试能力:转载或再分发需要署名、以相同方式共享,且不得用于商业用途;工具本身面向的是自己有权分析的目标,不要用它处理别人的程序或数据。整篇记录均在本机、本人账号环境下完成,不含任何令牌、密钥与个人数据。

卸载是三步:删掉 junction、从 package.json 去掉依赖与 bundle 项、删除 ~/.dsh/plugins/dsh-infinite-gen-5;插件也自带 uninstall.ps1,但删之前记得先备份 profile。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注