dsh-app-updater:检测本机已装应用并从官网一键静默更新

前言

DSH 的插件体系里,能跑的插件很多,能替你动本机环境的少。智能体经常需要知道「这台机器上装了哪些应用、有没有新版」,但常见的做法要么是让模型自己拼 winget upgrade 命令,要么是写一段只针对某个软件的脚本,装完一个再补一个。这类做法的问题是:列不全、不认架构、不知道静默参数,而且一旦执行就是以当前进程权限直接装。

下面介绍 dsh-app-updater。它把「列出已装应用 → 比对新版本 → 从发布方官网下载全量安装包 → 按官方静默参数安装」这条链路做进一个 DSH 插件,入口在设置界面里,不需要命令行也能用。

这是什么

dsh-app-updater,作者 ccgo26,MIT 协议,当前包版本 1.3.15,分类在社区插件目录的「联网工具」下,GitHub 仓库 1 个星标。

一句话定位:它是 DSH 的应用更新插件,检测本机所有已安装应用是否有新版本,并从各自官网下载完整安装包一键静默更新。检测过程不需要打开被检测的软件;插件自动识别 Windows / macOS 与 ARM64 / x64(Intel)架构,按当前系统与芯片挑选对应的安装包。

已知边界先说清楚:Windows 上它只驱动 winget 目录收录的应用,另用注册表补齐未收录的;macOS 上只对 Homebrew 管理的应用提供一键更新,拖 DMG 装的应用能列出、能查到官网有没有新版,但不能一键升级。

核心功能

四个步骤,README 里写得很明确:

  1. 列出已装应用:Windows 走 winget list(覆盖 winget 目录收录的软件 + 商店/MSIX 包),再用 Windows 注册表三处 Uninstall 键补齐 winget 未收录的软件。全程只读文件与注册表,不启动任何应用。
  2. 判断有没有新版本:winget upgrade --include-unknown,由 winget 官方软件目录给出「已装版本 → 可升级版本」,再与第 1 步的清单按 Id 合并。
  3. 一键更新:winget download 把发布方官网的完整安装包下载到指定目录(下载时按清单核对 SHA-256);执行前插件会再在本地复核一次哈希(1.3.5 起),不一致就不执行、改用兜底;接着解析随包落盘的 winget 清单,取出发布方自己声明的静默参数,执行静默安装;装完用 winget list --id 复核版本号。
  4. 失败兜底:若官网安装包被发布方重新签发导致哈希与目录记录不一致、或清单禁止直接下载,自动退回 winget upgrade --id <id> --exact --silent——同样是从官网拉完整安装包,只是下载与安装过程交给 winget。

「从对应官网下载」这一点值得单独讲:安装包地址来自 winget 清单里的 InstallerUrl,例如 Git 指向 https://github.com/git-for-windows/git/releases/download/...,CPU-Z 指向 https://download.cpuid.com/cpu-z/...,哔哩哔哩指向 https://dl.hdslb.com/...,Chrome 指向 https://dl.google.com/dl/chrome/install/...。下载目录(默认 %USERPROFILE%\Downloads\DSH App Updater)里会同时留一份 .yaml 清单,里面有发布方声明的 InstallerUrl / InstallerSha256 / InstallerSwitches.Silent,可以自行核对来源。

macOS 侧的逻辑不同,也更容易误读。应用从 /Applications 等目录枚举出来,再按 cask 目录反查它是不是 Homebrew 装的;拖 DMG 或手动装的应用会如实列出并标注「非 Homebrew 管理」,不会被算成可更新。所以一台全是拖装应用的 Mac 上,界面显示「0 个可更新」是正确结论,不是 bug。

不过从 1.3.9 起,非 Homebrew 应用也能查到「官网最新版本」,因为 macOS 应用自己带着更新源:

类型 位置 读什么
Sparkle Info.plist 的 SUFeedURL → appcast.xml <sparkle:shortVersionString>(不是构建号)
Electron Contents/Resources/app-update.yml url + channel → 抓对应 mac 安装清单顶层的 version:

查到后,界面会在「可升级到」列显示官网版本并打上橙色「需手动」标签,状态列显示「官网有新版」,状态栏还会补一句「另有 N 个官网有新版,需手动升级」。这条路径只用于展示:hasUpdate / updateSupported 依然只由 Homebrew 决定,查到版本不等于能替你升级。查询全程只读,不下载、不安装、不启动被检测的应用。

1.3.10 起放宽了一点:能对上 cask 的非 Homebrew 应用可以直接一键更新,执行的是

brew install --cask --force <token>

它会移除现有 .app、装上 cask 版本,之后该应用自动变成「Homebrew 管理」,后续走常规 brew upgrade --cask。README 解释了为什么不是 --adopt:Homebrew 的 --adopt 只在已有 app 与要安装的版本完全一致时才接受,版本不同会直接报错,所以它只能「认领一个已经是最新的 app」,不能用来升级。对不上 cask 的应用仍然无法更新,界面显示「需手动」并提示去应用内或官网升级;1.3.8 起这类行的提示会直接把对应命令写出来,例如 brew install --cask --adopt iina。

安装与启用

安装走 dsh plugin,推荐从 npm 装,插件中心同样可一键安装:

dsh plugin --profile web add dsh-app-updater

刚发布的新版本可能被 pnpm 的 minimumReleaseAge 策略暂缓一段时间;想立刻装到指定版本就显式写版本号:

dsh plugin --profile web add dsh-app-updater@1.2.0

不想走 npm,也可以用源码仓库或预构建包:

# 源码仓库
dsh plugin --profile web add github:ccgo26/dsh-app-updater

# 预构建 tgz(见 Releases)
dsh plugin --profile web add https://github.com/ccgo26/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz

依赖要求:Node ^22.19.0 || >=24.0.0,DSH >=0.1.5-rc.1 <0.2.0-0 || >=0.2.0-rc.2 <0.3.0-0。

桌面端(Electron 托管的 desktop profile)不允许 dsh plugin 直接改写,需在 profile 的 node_modules 下建 junction 并手动补 bundles。装好后重启 DSH,入口在 设置 → 应用更新。

典型用法

整个交互流程都在设置页里,没有后台任务:

  1. 打开 设置 → 应用更新。
  2. 点 刷新检测。插件不会自动扫,只有你点了才扫。
  3. 列表默认按「有新版本优先」排序,可用搜索框和 全部 / 仅可更新 / 未收录 / 需手动 分段筛选。
  4. 勾选要更新的应用,或点 全部更新 一次处理当前筛选下所有可更新项;点行尾 更新 可单个更新。
  5. 更新期间有进度条、当前应用名与逐条日志,取消 会停止尚未开始的项目。
  6. 打开下载目录 可以在资源管理器中查看已下载的官网安装包。
  7. 同一页的 插件设置 面板里可以改下载目录、保留安装包、演练模式等开关。
  8. 表格列宽默认给足以完整显示应用名,拖动表头右侧分隔条可调宽度,双击恢复默认,偏好记在浏览器本地。

状态列的含义需要分清:有新版本 表示软件目录给出了更高版本号;已是最新 表示目录里没有更高版本;未收录 表示目录里找到了更新但本插件无法驱动(原因可能是软件目录未收录、由应用商店管理、版本未知,或属于系统组件)。红色警告横幅出现时说明这次扫描的判断依据本身失效(Homebrew 命令失败,或 cask 目录抓不下来),此时「0 个可更新」不代表应用都是最新。

用 API 改设置

设置面板能做的,也能走 HTTP 接口,适合脚本化:

curl -X POST -H "Content-Type: application/json" \
  -d '{"confirm":true,"settings":{"dryRun":true}}' \
  http://127.0.0.1:<port>/dsh-app-updater/settings

downloadDir 会被校验:必须留空(用默认目录)或是绝对路径(D:\Apps)/ UNC 路径(\\server\share),否则返回 400,面板里显示红字提示,不会把安装包写到意料之外的位置。

设置项

状态文件:%USERPROFILE%\.dsh\dsh-app-updater\state.json

键 默认 说明
includeStoreApps true 是否把商店应用列进来(Windows 是 MSIX、macOS 是 App Store 应用;只展示,不会被更新)。
includeRegistryOnly true Windows:是否用注册表补齐 winget 未收录的应用(只展示,不能更新)。
includeUnmanagedApps true macOS:是否把非 Homebrew 管理的应用也列出来(只展示,不能更新)。
checkOfficialSources true macOS:是否读取非 Homebrew 应用自带的更新源判断官网有没有新版。只读;关闭后这些应用一律显示「不可判断」,且扫描不再发起对这些域名的请求。
downloadDir 空 自定义安装包下载目录;空 = %USERPROFILE%\Downloads\DSH App Updater(macOS 为 ~/Downloads/DSH App Updater)。
keepInstallers true 更新后保留安装包;false 则在安装完成后删掉该应用刚下载的包。
hashFallbackUpgrade true 下载失败时退回 winget upgrade 兜底。
dryRun false 演练模式:只下载官网全量安装包、不执行安装。用来先看会装什么。
matchSystemArch true 按识别到的系统与芯片架构挑选安装包(Windows 传 --architecture;macOS 由 Homebrew 决定)。
autoElevate true 自动提权安装:需要管理员的安装包改由管理员上下文执行;UAC 开启时每次安装会弹一次确认。

安全边界

插件要下载并安装可执行文件,安全约束写得比较细,值得单独看一节。

  • 路由默认拒绝:每个请求先过 ctx.connection.admit(request),即 DSH 用来做 Host/Origin 信任校验与浏览器会话 / API 认证的那套边界(宿主自己的 /api 路由用的就是它)。未通过的请求直接得到 401 / 403 / 409,响应头带 X-DSH-Admission: denied。
  • 兼容模式默认关闭(1.3.1 起):宿主没有 connection 服务时不再「仅凭回环地址放行」,一律 403。确实需要时必须显式开启,二选一:在 cordis.patch.yml 里覆盖 allowLegacyLoopback: true,或启动 DSH 前设置环境变量 DSH_APP_UPDATER_ALLOW_LEGACY_LOOPBACK=1。开启后放行的回环请求带 X-DSH-Admission: loopback-legacy-optin 与警告头,设置页顶部出现红色提示条,日志记一条 warn。
  • 来源围栏(1.3.5 起):兼容模式下拒绝跨站浏览器请求,Origin / Referer 存在且与本次请求的 Host 不同源、或带 Sec-Fetch-Site: cross-site 的一律 403。浏览器禁止脚本伪造这几个头,所以第三方网页里的无 Cookie fetch(…/update) 无法驱动「下载 + 提权安装」;本机脚本与宿主 IPC 桥不受影响。
  • 写操作显式确认:scan / update / cancel / settings / open-downloads 必须带 confirm:true。
  • 不猜测静默参数:优先用发布方在清单里声明的 Silent 参数;清单缺失时才按安装包类型用保守的默认开关。
  • 执行前本地复核 SHA-256(1.3.5 起):winget download 完成后,按随包清单的 InstallerSha256 对流盘的安装包重新计算一次哈希再执行——这个文件紧接着可能以管理员身份运行,中途被替换或损坏必须拦住;不一致就不执行、改用兜底。清单里配对不出本次架构的哈希时跳过并记录,绝不拿别的架构的哈希误判。
  • 需要管理员时自动提权:机器级安装包(MSI、写入 Program Files)交给 Start-Process -Verb RunAs 的提权副本执行,可在设置里关掉。

适用场景与注意

适合这类开发者:常常需要确认一台开发机或跳板机上的软件版本是否滞后,或者想把「检查更新 + 装更新」做成可复现的一步,又不想引入额外的更新守护进程。Windows 侧依赖 winget 可用、macOS 侧依赖 Homebrew 可用,插件本身不替代这两个包管理器。

注意三点:

  1. 插件以当前 dsh 进程的权限运行。用它批量更新系统组件或机器级应用包时,实际执行的是提权副本,UAC 会逐个弹确认;不想看到确认就把 autoElevate 关掉。
  2. 安装前应自己检查源码与许可证。本插件是 MIT 协议,仓库在 github.com/ccgo26/dsh-app-updater;它下载的是发布方官网的安装包,安全边界依赖 winget 目录里的清单是否可信。默认不开兼容模式、写操作要 confirm:true,这两个默认值建议保持。
  3. 非 Homebrew 应用的官网版本查询只读且可整开关(checkOfficialSources)。关掉后这些应用回到「不可判断」,扫描不再发起对相关域名的请求——对在意外联的开发环境可以直接关。

结尾

dsh-app-updater 解决的是一个具体问题:把「检测本机应用版本 → 从官网取全量安装包 → 按官方静默参数安装 → 复核版本号」串成一条可审计的链路,并在下载后、执行前加了一道本地哈希复核。它不是包管理器的替代品,而是把 winget 与 Homebrew 的能力整理成一个带界面、带状态、带安全边界的 DSH 插件。功能集中在 Windows 的 winget 与 macOS 的 Homebrew 两条路径上,非 Homebrew 应用能列出和查到版本,但升级要么收编进 Homebrew,要么手动处理。

目录页:https://www.skillhub.cn/plugins/ccgo26/dsh-app-updater
仓库:https://github.com/ccgo26/dsh-app-updater

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

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

小夜