MacroSIP
EnglishРусский中文

发布

发布通过给 main 打标签来完成。标签会启动 .github/workflows/release.yml,它构建通用 DMG、发布 GitHub Release、签名并更新 Sparkle 更新源,并刷新 Homebrew cask。权威文本是 docs/DEVELOPMENT.md 的“Releasing”一节、README.md 中的检查清单、ADR 0010 和 ADR 0013。

版本号(ADR 0010)

版本号为 YY.MINOR.PATCH:YY 是发布线的两位年份(2026 年为 26),MINOR 随年内的功能发布增长,PATCH 随修复增长。首个发布版是 26.0.0;2027 年的发布从 27.0.0 开始,即使它本应只是一次次版本号提升。标签只带纯数字。

候选发布版的标签为 N.N.N-rc.N,例如 26.0.0-rc.1。release.yml 和 scripts/make-appcast.sh 拒绝任何其他后缀。任何带 - 的标签都会成为 GitHub 预发布版和 beta 频道上的 appcast 条目。

三个数字必须一致:

位置 值 来源
CFBundleShortVersionString “关于”中显示并由发布作业与标签比较的版本 make-app.sh:git describe --tags --abbrev=0,首个标签之前为 Product.version
CFBundleVersion Sparkle 用来为更新排序的构建号 make-app.sh:git rev-list --count HEAD
Product.version(Sources/MacroSIPCore/Product.swift) SIP User-Agent 头和“关于”面板 打标签前手动编辑

每个发布版都要在比前一个更晚的提交上打标签,正式版相对其候选版也是如此。CFBundleVersion 是提交计数,Sparkle 以它标识条目,如果正式版与候选版在同一提交上打标签,generate_appcast 会悄悄替换候选版的条目。make-appcast.sh --check 拒绝更新源中已在另一版本下存在的构建号,工作流在发布任何内容之前会运行该检查。

所有者的检查清单(来自 README)

  1. 本地一切通过:swift build、scripts/test.sh、scripts/selftest-call.sh、scripts/make-dmg.sh;挂载 DMG 并启动应用。

  2. 将 Product.version 提升到要发布的版本,更新 docs/ROADMAP.md,提交。

  3. 打标签并推送:

    git tag 26.0.0-rc.1 && git push origin 26.0.0-rc.1    # 候选版
    git tag 26.0.0 && git push origin 26.0.0              # 正式版,在更晚的提交上
  4. 关注 release.yml。它构建 DMG、创建 GitHub Release 并将其加入更新源;存在 TAP_TOKEN 时,同一次运行会更新 Homebrew cask。

  5. 检查:brew tap hsmm/tap && brew install --cask hsmm/tap/macrosip,启动(第一次右键点按、打开);curl -s https://raw.githubusercontent.com/HSMM/MacroSIP/gh-pages/appcast.xml 列出新条目;已安装的旧版本通过 MacroSIP › 检查更新能找到它(候选版仅在开启“包含候选发布版”时)。

release.yml 做了什么

该工作流仅在 HSMM/MacroSIP 中、在匹配 [0-9]*.[0-9]*.[0-9]* 的标签上运行,权限为 contents: write。故意不设置并发组:GitHub 为每个组只保留一个待处理的运行并取消其余的,这会丢掉一个标签;同时进行的发布改为在 gh-pages 上排队。

macos-15(arm64)上的作业 dmg:

  1. 检查标签:N.N.N 或 N.N.N-rc.N,并且 SPARKLE_PRIVATE_KEY 密钥必须存在(没有它,已安装的应用永远看不到这个发布)。
  2. 构建与测试,与 CI 相同:带缓存的 build-pjsip.sh、swift build、scripts/test.sh、原生以及 Rosetta 2 下的 scripts/selftest-call.sh。
  3. 构建 DMG:scripts/make-dmg.sh release;对二进制文件执行 lipo -info;如果 CFBundleShortVersionString 与标签不同,作业失败。DMG 作为产物上传供 cask 作业使用。
  4. 检查 appcast:scripts/publish-appcast.sh --check <tag> dist/MacroSIP.app 获取 gh-pages,并拒绝更新源中已在另一版本下存在的构建号。
  5. 创建 GitHub Release,使用 gh release create:标题 = 标签,生成的说明前面加一行固定文字,说明更新后 macOS 会再次请求麦克风访问权限,带 - 的标签使用 --prerelease。重新运行会保留 release 并用 --clobber 替换 DMG。
  6. 更新 appcast:用 GitHub 的 Markdown API 将 release 正文渲染为 HTML,scripts/publish-appcast.sh <tag> <dmg> 运行 scripts/make-appcast.sh,后者调用 Sparkle 的 generate_appcast(来自 .build/artifacts/sparkle/Sparkle/bin),使用 SPARKLE_PRIVATE_KEY 中的 EdDSA 密钥、release 资源的下载 URL、--embed-release-notes、候选版的 --channel beta,并保留所有早期条目(--maximum-versions 0,无增量更新)。结果提交到 gh-pages 分支(首次运行时作为孤立分支创建;当另一次发布先推送时,在重新获取后最多重试推送五次),并作为 appcast.xml 附加到 release。

ubuntu-latest 上的作业 homebrew,在 dmg 之后:TAP_TOKEN 密钥为空时跳过;否则用该令牌检出 HSMM/homebrew-tap,下载 DMG 产物,并在设置了 TAP_DIR 的情况下运行 scripts/update-cask.sh <tag> <dmg>,它将 packaging/homebrew/macrosip.rb(版本、SHA-256)填入 Casks/macrosip.rb,提交 macrosip <tag> 并推送。一次性设置:创建公开仓库 HSMM/homebrew-tap,创建一个仅对该仓库具有 Contents 读写权限的细粒度个人访问令牌,将其存为 HSMM/MacroSIP 的 Actions 密钥 TAP_TOKEN。cask 声明 auto_updates true 和 depends_on macos: :sonoma;Homebrew 接受 26.0.0-rc.1 作为版本。

手动、不经 CI:scripts/update-cask.sh <version> dist/MacroSIP-<version>.dmg 写出 dist/macrosip.rb 并打印 git 命令(设置 TAP_DIR=<clone> 时会提交并推送);SPARKLE_PRIVATE_KEY=... APPCAST_IN=<current appcast.xml> scripts/make-appcast.sh <version> <dmg> dist 生成 dist/appcast.xml,用于上传到 release 和 gh-pages。没有该环境变量时,generate_appcast 从 generate_keys --account macrosip 创建的登录钥匙串项中读取密钥。

更新源(ADR 0013)

应用在启动几秒后以及之后每天读取一次 https://raw.githubusercontent.com/HSMM/MacroSIP/gh-pages/appcast.xml(SUFeedURL,由 make-app.sh 写入;无需设置 GitHub Pages),并在安装前询问。每个条目都带有 DMG 的 EdDSA (ed25519) 签名;公钥是 make-app.sh 中的 SPARKLE_PUBLIC_KEY(Info.plist 中的 SUPublicEDKey)。私钥恰好存在于两处:Actions 密钥 SPARKLE_PRIVATE_KEY 和所有者的登录钥匙串(“MacroSIP Sparkle private key”,加上 Sparkle 自己的项,账户 macrosip)。恢复该密钥:security find-generic-password -s "MacroSIP Sparkle private key" -w | gh secret set SPARKLE_PRIVATE_KEY --repo HSMM/MacroSIP。丢失密钥意味着已安装的副本只能通过手动下载更新;泄露密钥则让任何同时能提供更新源的人都可以向用户推送代码。

候选版位于 beta 频道,仅在设置 › 通用 › 更新 › “包含候选发布版”开启时提供(当前运行的构建本身是候选版时默认开启)。正式版没有频道,面向所有人。appcast 保留发布时的说明;要更改它们,请编辑 release 并重新运行 appcast 作业。appcast 步骤失败的发布在 GitHub 上可见,但在重新运行该作业之前不会在应用中提供。

签名

构建使用临时签名加强化运行时(codesign --options runtime),从内到外:Sparkle 的 XPC 服务、Autoupdate、Updater.app、框架,然后是带 packaging/Entitlements.plist 的应用。entitlements 为 com.apple.security.device.audio-input、com.apple.security.personal-information.addressbook 和 com.apple.security.cs.disable-library-validation;最后一项之所以存在,是因为临时签名没有 Team ID,库验证会拒绝内嵌的 Sparkle 框架。不使用强化运行时的签名被否决:那也会允许向拥有麦克风权限的进程注入 DYLD_INSERT_LIBRARIES。

对用户的影响(ADR 0007):首次启动显示“身份不明的开发者”对话框(右键点按、打开,或 xattr -dr com.apple.quarantine),由 Sparkle 安装的更新通过 EdDSA 签名获得信任并且启动时不再出现该对话框,macOS 将麦克风权限绑定到确切的构建,因此每次更新后都会再次询问。Developer ID 签名和公证计划在以后实现(docs/ROADMAP.md);有了 Developer ID 后,请移除库验证的 entitlement。

另见 构建、测试、安装。

其他页面:首页 · Home (English)