需求只有一句话:「安装这个插件 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_HOME | C:\Users\<用户名>\.dsh |
| profile | desktop,由 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.com | 661 ms | 是 | 否 |
| ghfast.top | 1860 ms | 是 | 是(206) |
| github.moeyy.xyz | 超时 | — | — |
| 直连 github.com | 1034 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 写得很严谨,逻辑大致是六步:
- 读每个 profile 的
package.json,算 SHA-256 快照,防止并发改写; - 校验整个运行包,52,000+ 个文件逐个哈希;
- 复制到
~/.dsh/plugins/dsh-infinite-gen-5,先 staging,失败可回滚; - 在每个 profile 的
node_modules里建 Junction 指向物理目录; - 改写
package.json:加入file:依赖并追加到 bundles; - 旧版本移到
~/.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 锚定。
九、最终状态
| 项 | 值 |
|---|---|
| Git | 2.56.0.windows.4(PortableGit,用户级)+ 全局镜像重写 |
| 插件市场 | dshmarket@1.70.2 |
| IG5 | dsh-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 |
十、可复用的经验
- 先确认包名。GitHub 仓库名和 npm 包名经常不一致;npm 上带签名或 provenance 的包优先。
- pnpm 的
git+https依赖强依赖系统 git。Windows 上的表现是「’git’ 不是内部或外部命令」,很容易被误判成网络问题。 - 镜像测速要测三件事。HEAD 能否 200、能否跑 git 协议、是否支持 Range——三者不等价,这次就栽在第三项。
url.insteadOf是治本方案。一条 git 配置让所有 GitHub 依赖透明加速,比逐个替换 URL 靠谱,但要知道怎么撤销。- 已运行进程不会刷新 PATH。想让工具「立刻生效」,要么重启宿主,要么在宿主 PATH 里放 shim。
- DSH 插件 = 依赖 + bundle 栈 + node_modules。Junction 让物理目录与 profile 解耦,
file:依赖绕开 pnpm 重新解析。 - 区分「源码仓库」和「发布完整包」。带原生运行时的插件几乎都只有 Release 完整包能跑,源码版装了也是空壳。
- 校验要挑成本。逐文件哈希 5 万个文件超过 15 分钟,收益有限;但对下载物做一次 SHA-256 是必需且便宜的,1 GB 文件约 5 秒。
- 改宿主配置前先备份。
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。
发表回复