☰
macOS自动更新不再难:Sparkle在FluxMarkdown中的实战落地
2026/10/14 15:03:19 网站建设 项目流程

【免费下载链接】flux-markdown

Markdown Preview Enhanced for macOS QuickLook

项目地址:https://gitcode.com/gh_mirrors/fl/flux-markdown
点击查看免费下载

FluxMarkdown 是一款运行在 macOS 上的 Markdown 快速预览工具(QuickLook 扩展 + 独立 App),它的"检查更新"能力完全由 Sparkle 框架驱动。本文结合 FluxMarkdown 的真实代码,讲清楚 Sparkle 自动更新的完整落地流程:从依赖接入、appcast.xml更新源、沙盒权限配置,到 EdDSA 签名与密钥轮换的避坑经验。

Sparkle 是什么,为什么适合 FluxMarkdown

Sparkle 是 macOS 生态中最成熟的自动更新框架,负责:

  • 定时/手动检查远端更新源(appcast.xml)
  • 下载新版安装包并校验EdDSA 数字签名
  • 通过安装器进程(Installer)替换/Applications下的应用并重启

对于 FluxMarkdown 这样的开源项目(未使用 Mac App Store 分发渠道),Sparkle 是实现"应用内一键更新"的标准选择。

FluxMarkdown 的 QuickLook 预览效果如下,用户可以直观看到 Markdown 文档在 Finder 中的渲染结果:

三步接入:依赖、控制器、更新源

第 1 步:用 Swift Package 引入 Sparkle

在 Package.swift 中声明依赖,并锁定版本(Xcode 工程侧由 project.yml 同步声明):

.package(url: "https://github.com/sparkle-project/Sparkle", exact: "2.8.1")

锁定精确版本很关键:Sparkle 2.x 的 XPC 服务名、权限模型与 1.x 完全不同,随意升级可能导致已安装用户无法更新。

第 2 步:初始化 SPUStandardUpdaterController

在 MarkdownApp.swift 的AppDelegate中,只需几行代码即可启动更新器:

let updaterController = SPUStandardUpdaterController( startingUpdater: true, updaterDelegate: UpdateDelegate.shared, userDriverDelegate: nil )

Sparkle 2.x 对 SwiftUI 应用推荐的正是SPUStandardUpdaterController(1.x 时代的SUUpdater手动组装已被官方放弃)。菜单栏中的"检查更新"按钮通过NSApp.sendAction(#selector(SPUStandardUpdaterController.checkForUpdates(_:)))触发同一套逻辑。

第 3 步:配置 Info.plist 更新源

所有 Sparkle 配置集中在 Info.plist 中,关键字段一览:

键作用
SUFeedURLappcast.xml的托管地址(必须指向返回 XML 原文的 URL)
SUPublicEDKey当前公钥,用于校验更新包签名
SUPublicEDKeyChain新旧公钥链,支持密钥轮换过渡
SUEnableAutomaticChecks开启自动检查
SUScheduledCheckInterval检查间隔(本项目为 86400 秒,即每天一次)
SUEnableInstallerLauncherService沙盒应用必需的 XPC 安装服务

appcast.xml:更新的"发布清单"

Sparkle 检查更新时拉取的 appcast.xml 是一个 RSS 文件,每个<item>对应一个版本,核心是<enclosure>节点:

<enclosure url=".../FluxMarkdown.dmg" sparkle:edSignature="hu1PmNe+X3dpse5+...==" length="36738769" type="application/octet-stream" />

其中sparkle:edSignature是用私钥对 DMG 生成的 EdDSA 签名。应用端用SUPublicEDKey公钥验签,签名不匹配的安装包会被直接拒绝——这就是为什么"下载更新"不等于"信任更新"。

📌 常见坑:SUFeedURL若指向网页(HTML)而非 XML 原文,Sparkle 会解析失败。本项目使用 raw 文件地址直接返回 XML,避免了这个问题。

发布时的更新包安装窗口如下,用户只需将应用拖到 Applications,后续版本交给 Sparkle:

沙盒应用的关键:entitlements 权限例外

FluxMarkdown 启用了 App Sandbox,而 Sparkle 的安装器是独立进程,必须显式放行才能工作。相关配置在 Markdown.entitlements 与 MarkdownRelease.entitlements 中:

<!-- 允许 Sparkle XPC 服务通信 --> <key>com.apple.security.temporary-exception.mach-lookup.global-name</key> <array> <string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string> </array> <!-- 允许写入 /Applications 完成安装 --> <key>com.apple.security.temporary-exception.files.absolute-path.read-write</key> <array> <string>/Applications</string> </array>

⚠️ 注意服务名必须使用$(PRODUCT_BUNDLE_IDENTIFIER)-spks/-spki的标准格式。本项目早期曾用过自定义服务名com.xykong.Markdown.Installer,结果更新时报错An error occurred while running the updater——Sparkle 2.x 只认标准后缀。这个教训完整记录在 LESSONS_SPARKLE_AUTO_UPDATE.md 中。

发布流程:签名、生成 appcast、发布

一次完整的版本发布围绕三个脚本:

  1. 生成密钥:generate-sparkle-keys.sh 生成 Ed25519 密钥对,私钥严禁提交到仓库;
  2. 签名并更新 appcast:generate-appcast.sh 调用 Sparkle 的sign_update工具对 DMG 签名,自动把版本号、签名、文件大小写入 appcast.xml;
  3. 可恢复发布:publish-release.sh 校验清单与公证、生成签名后的 appcast 并创建 Release,失败可断点重跑,流程细节见 RELEASE_PROCESS.md。

用户侧的体验由 UpdateDelegate.swift 的回调串联:发现新版本、用户点击下载、即将安装、更新失败……每个状态都有对应的 delegate 方法,便于打日志和埋点。

细节体验:更新后自动恢复上次打开的文件

自动更新常打断用户工作流,FluxMarkdown 的解法在 UpdateRestorationManager.swift 中:

  • 打开文档时,把文件路径写入UserDefaults;
  • Sparkle 回调触发下载/安装前,markUpdatePending()打一个"更新进行中"标记;
  • 更新完成、应用重启后检测到该标记,自动重新打开上次的 Markdown 文件(0.5 秒延迟确保窗口就绪);
  • 文件已被删除等异常情况会优雅降级,并清理标记。

这套机制让"重启安装更新"对用户几乎无感。

避坑清单:从 5 轮调试总结的教训

以下问题都来自 LESSONS_SPARKLE_AUTO_UPDATE.md 的真实排障记录,建议对照自查:

现象根因解决
An error occurred while launching the installer沙盒缺少安装器启动服务开启SUEnableInstallerLauncherService+ entitlements 例外
An error occurred while running the updaterMach 服务名不是 Sparkle 标准格式改用-spks/-spki后缀
The update is improperly signed重新生成密钥后,旧版本内嵌旧公钥用SUPublicEDKeyChain同时信任新旧公钥,完成渐进式迁移
检查更新解析报错SUFeedURL指向 HTML 页面改为返回 XML 原文的 raw 地址

两条最重要的经验:

  • 密钥是单点故障:私钥丢失 = 所有旧用户永久无法更新,务必备份到密码管理器或加密存储;
  • 一定要测"旧版本 → 新版本"路径:项目提供了 test-update-from-old-version.sh、test-update-locally.sh 等脚本,模拟真实升级场景,只测"最新版覆盖安装"会漏掉大量兼容性问题。

总结:一套可复用的 Sparkle 落地清单

把 FluxMarkdown 的做法抽象出来,就是五步:

  1. SPM 引入固定版本的 Sparkle 2.x;
  2. SPUStandardUpdaterController一行启动,delegate 接管状态回调;
  3. Info.plist配好SUFeedURL、公钥(必要时SUPublicEDKeyChain)、检查间隔;
  4. entitlements放行-spks/-spki服务与/Applications写入;
  5. 发布时用sign_update签名 DMG,脚本化生成appcast.xml并做旧版本升级回归。

更多细节可查阅 AUTO_UPDATE.md 的官方说明与 CHANGELOG.md 中的历次更新记录。按这套清单执行,你的 macOS 应用也能拥有稳定、安全、可追溯的自动更新体验。

【免费下载链接】flux-markdown

Markdown Preview Enhanced for macOS QuickLook

项目地址:https://gitcode.com/gh_mirrors/fl/flux-markdown
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询