Homebrew Cask 定时自动发版:livecheck + bump-cask-pr 的成熟方案
Homebrew Cask 定时自动发版:livecheck + bump-cask-pr 的成熟方案
TL;DR - 点击跳转到结论
第三方 Homebrew tap 想要“上游发版 → cask 自动更新 → 用户brew upgrade可用”, 不需要手写 SHA-256。让brew livecheck负责检测.
让brew bump-cask-pr --write-only负责下载、计算双架构 SHA-256 和改写 cask, 再用 GitHub Actions / Forgejo Actions 的schedule做定时器即可。本文以 MoekaProject/community 和 MoekaProject/homebrew-moeka 为实例, 给出可直接复用的 cask 与 workflow、完整验证步骤,以及 GitHub / Codeberg 上值得借鉴的定时发布项目。
验证环境:macOS 27.2(Apple Silicon)、Homebrew
7.0.10-108-g8d79935、 示例上游最新正式版v0.1.29(2026-10-25 发布)。Homebrew 各版本的命令细节可能变化, 建议先brew update再按本文验证。
| # | 项目 | 本文实测值 |
|---|---|---|
| 1 | 系统 | macOS 27.2 / Apple Silicon |
| 2 | Homebrew | 7.0.10-108-g8d79935 |
| 3 | 上游仓库 | MoekaProject/community |
| 4 | Tap | MoekaProject/homebrew-moeka |
| 5 | 示例版本 | 0.1.29 |
| 6 | 调度频率 | 每小时(可改为每天/每周 |
0. 结论先行
| 问题 | 结论 |
|---|---|
| 官方 cask 为什么能自动更新? | Homebrew 官方仓库由 BrewTestBot 每 3 小时检查并开 PR;只覆盖官方仓库。 |
第三方 tap 能只靠 cask 的 .rb 自动更新吗? | 不能。.rb 是声明式安装配方,不会自己定时运行,也不会自己提交。 |
| 当前 Homebrew 的成熟做法是什么? | 调度器 + brew livecheck + brew bump-cask-pr --write-only。 |
| SHA-256 谁算? | Homebrew 官方工具在下载 DMG 后计算,不要手写校验和逻辑。 |
| 自动发布怎么落地? | 低风险 tap 可直推 main;需要审核则开 PR + auto-merge。 |
| Codeberg 能做吗? | Forgejo Actions 支持 cron;Woodpecker 也支持 cron。需要注意托管 runner 多为 Linux,而 Homebrew Cask 面向 macOS。 |
1. 问题:为什么第三方 tap 会停在旧版本
Homebrew 官方文档对 Autobump 的说明很明确: 在官方仓库中,BrewTestBot 每 3 小时检查一次 autobump list,必要时自动开 PR。 这套能力不覆盖第三方 tap,所以像 MoekaProject/homebrew-moeka 这样的独立 tap,如果没有自己的调度器,就会一直停在上一次手动提交的版本。
本文开始时,tap 里还是 0.1.11,而 MoekaProject/community 已经发布到 0.1.29。README 里描述的“源码仓库发版流程同步 cask” 并没有实际运行,最终表现就是用户通过 brew 永远拿不到新版本。
第三方 tap 需要补齐的链路是:
- 检测:知道上游出现了一个新的正式版本;
- 计算:下载 arm64 / x86_64 资产,分别计算 SHA-256;
- 改写:更新 cask 的
version与两个sha256; - 发布:提交到 tap,让
brew update能拉到; - 复用:没有新版本时安静退出,不产生空提交。
这五步里,只有“调度”和“提交”需要自己写;中间的版本、下载、校验和都可以交给 Homebrew。
2. 当前 Homebrew 工具链
2.1 三个关键命令
# 1) 检测:读取 cask 的 livecheck 策略
brew livecheck --cask OWNER/TAP/CASK --json
# 2) 更新:官方工具下载资产、计算 SHA-256、改写 cask
brew bump-cask-pr --write-only --no-audit --no-style \
--version 0.1.29 OWNER/TAP/CASK
# 3) 本地/CI 注册第三方 tap 后,Homebrew 7 需要信任 tap
brew trust OWNER/TAPbrew bump-cask-pr 的 --write-only 很关键:它只做文件修改,不执行 fork、PR 或 Git 操作。 这样可以把“计算与改写”交给 Homebrew,把“提交与推送”留给 workflow 控制。
需要特别注意:当前版本的 bump-cask-pr 不会自动 livecheck 出版本。 如果既没有 --version,也没有 --url/--sha256,它会报错要求显式指定。 因此调度器需要先运行 brew livecheck,再把 latest 传给 --version。
2.2 为什么不用手写 shasum
手写脚本当然能下载两个 DMG 并 shasum -a 256,但会引入额外的维护面:
- 资产 URL、文件名和架构矩阵变化时要同步修改;
- 不同 Homebrew 版本的 cask DSL、审计规则和格式要求可能变化;
- 需要自己处理 404、断流、部分下载和错误资产;
bump-cask-pr已经是 Homebrew 官方维护的实现,能直接处理多架构 cask。
本文的方案只在“检测失败”时用 GitHub API 做兜底,不重新实现 SHA-256。
2.3 官方 autobump 与第三方 tap 的边界
| 场景 | 是否自动更新 | 说明 |
|---|---|---|
homebrew/cask 官方 cask | 是 | BrewTestBot 每约 3 小时检查并开 PR |
| 第三方 tap(本文) | 需要自建 | 官方 bot 不会替你维护 |
| 应用内自动更新 | 与应用有关 | cask 的 auto_updates true 不等于 tap 版本自动前进 |
auto_updates true 只表示应用自身可能自动更新;新用户安装、brew upgrade --cask 以及 cask 的版本元数据仍然需要 tap 里的 cask 文件保持最新。
3. cask 侧:让 livecheck 真正可用
Moeka 的 cask 有 arm64 和 x86_64 两个 DMG,因此使用多架构 sha256 声明。完整示例:
cask "moeka" do
arch arm: "arm64", intel: "x86_64"
version "0.1.29"
sha256 arm: "c579ed1b2909d730ed390f6efcf469095808ff00f92821939fde5963eaacdeae",
intel: "da1f7dfdadea684551280e6c34c656bdaec3d800ea6c378d69bb69c5769c4acc"
url "https://github.com/MoekaProject/community/releases/download/v#{version}/Moeka-macos-#{arch}.dmg"
name "Moeka"
desc "Markdown editor"
homepage "https://moeka.app/"
livecheck do
url "https://github.com/MoekaProject/community"
strategy :github_releases
end
auto_updates true
depends_on macos: :monterey
app "Moeka.app"
end3.1 显式 livecheck 块
最初的 cask 没有 livecheck 块。实测 brew livecheck 会使用默认策略, 在本机走了 git ls-remote,最终返回:
Error: moeka: Unable to get versions
git ls-remote timed out after 20 seconds.加上下面的块之后,Homebrew 才会明确地查询 GitHub Releases:
livecheck do
url "https://github.com/MoekaProject/community"
strategy :github_releases
end官方文档对 livecheck 的完整写法见 Brew Livecheck 和 Cask Cookbook 的 livecheck 章节。 如果上游不是 GitHub Releases,而是 Sparkle、Electron、普通网页或 Git tag, 可以换成对应的 strategy;不要直接照抄 :github_releases。
3.2 多架构校验和
cask 中两个 sha256 与 arch 变量对应:
arch arm: "arm64", intel: "x86_64"bump-cask-pr 会分别下载 Moeka-macos-arm64.dmg 和 Moeka-macos-x86_64.dmg,再改写对应的 sha256 arm: / intel:。 因此 cask 的 URL 模板必须保持稳定;如果资产改名,需要先改 cask 的 url。
3.3 用 brew livecheck 验证
在一个临时 tap 中注册 checkout 后执行:
mkdir -p "$(brew --repository)/Library/Taps/moeka-review"
ln -sfn "$PWD" "$(brew --repository)/Library/Taps/moeka-review/homebrew-moeka"
brew trust moeka-review/moeka
brew livecheck --cask moeka-review/moeka/moeka --json如果 cask 还是旧版本,会得到类似:
[
{
"cask": "moeka",
"version": {
"current": "0.1.11",
"latest": "0.1.29",
"outdated": true
}
}
]outdated: true 才是触发 bump 的条件;false 或空数组应当安静退出。
4. workflow:定时检测、官方更新、自动推送
下面是本文按同一命令链在本机验证过,并做了 YAML / shell 静态检查的完整 GitHub Actions 文件。它使用 macos-latest 是因为 Homebrew Cask 面向 macOS; 首次上线请先跑 dry_run=true。若改成 Forgejo/Woodpecker,需要把平台替换成 支持 Homebrew 的 macOS runner(见第 6 节)。
name: Auto-bump Moeka cask
on:
schedule:
# Hourly. Adjust as needed, e.g. "0 */6 * * *" for every 6 hours.
- cron: "0 * * * *"
workflow_dispatch:
inputs:
dry_run:
description: "Detect the latest release only; do not bump or push"
type: boolean
default: false
concurrency:
group: auto-bump-moeka
cancel-in-progress: true
permissions:
contents: write
env:
HOMEBREW_NO_AUTO_UPDATE: "1"
HOMEBREW_GITHUB_API_TOKEN: ${{ github.token }}
CASK_TOKEN: moekaproject/moeka/moeka
jobs:
bump:
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@main
- name: Register checkout as a tap
run: |
git checkout -B "${GITHUB_REF_NAME}"
tap_dir="$(brew --repository)/Library/Taps/moekaproject"
mkdir -p "$tap_dir"
ln -sfn "$GITHUB_WORKSPACE" "$tap_dir/homebrew-moeka"
if brew help trust >/dev/null 2>&1; then
brew trust moekaproject/moeka
fi
- name: Detect latest release
id: detect
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
cask="$CASK_TOKEN"
file_version="$(sed -nE 's/^[[:space:]]*version[[:space:]]*"([^"]*)"/\1/p' Casks/moeka.rb)"
if [[ -z "$file_version" ]]; then
echo "::error::Could not parse the current version from Casks/moeka.rb"
exit 1
fi
current="$file_version"
is_newer() {
awk -v a="$1" -v b="$2" 'BEGIN {
na = split(a, av, /[^0-9]+/)
nb = split(b, bv, /[^0-9]+/)
n = (na > nb ? na : nb)
for (i = 1; i <= n; i++) {
x = (i <= na ? av[i] + 0 : 0)
y = (i <= nb ? bv[i] + 0 : 0)
if (x > y) { print "true"; exit }
if (x < y) { print "false"; exit }
}
print "false"
}'
}
latest=""
livecheck_ok=false
if livecheck_json="$(brew livecheck --cask "$cask" --json --quiet 2>/dev/null)"; then
status="$(jq -r '.[0].status // "ok"' <<<"$livecheck_json" 2>/dev/null || echo error)"
outdated="$(jq -r '.[0].version.outdated // false' <<<"$livecheck_json" 2>/dev/null || echo false)"
if [[ "$status" == "ok" ]]; then
livecheck_ok=true
current="$(jq -r '.[0].version.current // empty' <<<"$livecheck_json" 2>/dev/null || true)"
current="${current:-$file_version}"
if [[ "$outdated" == "true" ]]; then
latest="$(jq -r '.[0].version.latest' <<<"$livecheck_json")"
fi
fi
fi
# Fallback for the case where the source repository's release
# workflow overwrites this cask and drops its livecheck block.
if [[ "$livecheck_ok" != "true" ]]; then
api_url="${GITHUB_API_URL:-https://api.github.com}"
auth=()
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
auth=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
latest="$(curl -fsSL --retry 3 \
-H "Accept: application/vnd.github+json" \
"${auth[@]}" \
"${api_url}/repos/MoekaProject/community/releases/latest" \
| jq -r '.tag_name' | sed 's/^v//')"
if [[ "$(is_newer "$latest" "$current")" != "true" ]]; then
latest=""
fi
fi
echo "current=${current}" >> "$GITHUB_OUTPUT"
echo "latest=${latest}" >> "$GITHUB_OUTPUT"
echo "Current version: ${current}; latest version: ${latest:-none}"
- name: Report dry run
if: steps.detect.outputs.latest != '' && inputs.dry_run == true
env:
LATEST: ${{ steps.detect.outputs.latest }}
run: |
echo "Dry run: would bump Moeka to ${LATEST}"
- name: Bump cask using Homebrew
if: steps.detect.outputs.latest != '' && inputs.dry_run != true
env:
VERSION: ${{ steps.detect.outputs.latest }}
run: brew bump-cask-pr --write-only --no-audit --no-style --version "$VERSION" "$CASK_TOKEN"
- name: Commit and push if changed
run: |
if git diff --quiet -- Casks/moeka.rb; then
echo "Cask unchanged; nothing to push."
exit 0
fi
new_version="$(sed -nE 's/^[[:space:]]*version[[:space:]]*"([^"]*)"/\1/p' Casks/moeka.rb)"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -- Casks/moeka.rb
git commit -m "moeka: bump to ${new_version}"
# Rebase in case another process (e.g. the source repo's release
# workflow) updated the tap while this job was running.
git pull --rebase origin "${GITHUB_REF_NAME}"
git push origin "HEAD:${GITHUB_REF_NAME}"4.1 每一步在做什么
| 步骤 | 作用 | 关键点 |
|---|---|---|
schedule | 每小时自动触发 | GitHub cron 使用 UTC;也保留 workflow_dispatch 手动触发 |
setup-homebrew | 安装/准备 Homebrew | 使用官方 action;高安全场景建议把 action 固定到 commit SHA |
| 注册 tap | 让 brew 认识当前 checkout | 用 symlink 而不是复制,保证改动落在工作区 |
brew trust | Homebrew 7 信任第三方 tap | 旧版本没有该命令时用 brew help trust 判断并跳过 |
livecheck | 检测最新正式版 | 读取 outdated 和 latest,而不是自己猜 tag |
| API 兜底 | livecheck 不可用时继续工作 | 防止源码模板覆盖 cask 后 livecheck 块丢失 |
bump-cask-pr --write-only | 下载、算 SHA、改写 cask | 不执行 Git 操作,由下一步统一提交 |
| commit/push | 发布到 tap | 只提交 Casks/moeka.rb;push 前 rebase |
4.2 为什么保留 API 兜底
MoekaProject 的 cask 模板还可能由源码仓库的发版流程覆盖。如果模板覆盖时丢掉 livecheck 块,brew livecheck 会失败。workflow 在这种情况下回退到 GitHub 的 releases/latest,仍然只做“检测版本”,SHA-256 继续交给 bump-cask-pr,不会退化成手写校验和。
兜底使用语义化数字比较,避免 0.1.9 > 0.1.10 这类字符串比较错误。
4.3 直推还是 PR
本文默认直接 push main,适合个人/小团队 tap,用户只需 brew update 就能拿到更新。 如果 tap 开启了分支保护,直接 push 会失败,此时有两个选择:
- 在 Settings 中放行 GitHub Actions bot;
- 把最后一步改成“开 PR + auto-merge”,由维护者审核。
官方 autobump 选择的是 PR 模式,适合官方仓库和需要人工判断的高风险更新。
4.4 用户侧如何拿到更新
发布成功后,用户侧只需要让 tap 更新,再显式升级 cask:
brew update
brew upgrade --cask moeka新用户则是:
brew tap MoekaProject/homebrew-moeka
brew install --cask moekabrew update 会拉取 tap 的最新提交;brew upgrade --cask moeka 会按最新 cask 下载对应架构的 DMG。cask 的 auto_updates true 仍然保留应用自身的更新能力, 但它不能替代 tap 的版本元数据更新。
5. 验证:不要只看 workflow 绿灯
5.1 静态检查
ruby -c Casks/moeka.rb
ruby -ryaml -e 'YAML.load_file(".github/workflows/auto-bump-moeka.yml"); puts "yaml ok"'如果要把 workflow 中的所有 run 片段单独做 shell 语法检查,可以抽出后执行:
ruby -ryaml -e '
y = YAML.load_file(".github/workflows/auto-bump-moeka.yml")
y["jobs"]["bump"]["steps"].each_with_index do |step, i|
next unless step["run"]
File.write("/tmp/wf-step-#{i}.sh", step["run"])
end
'
for f in /tmp/wf-step-*.sh; do bash -n "$f" || exit 1; done5.2 本地临时 tap:验证 livecheck 与 bump
先准备一个版本为 0.1.11 的临时副本,避免修改当前工作区。下面的命令假设 当前改动已经提交;在 PR checkout 上直接运行即可:
review_parent="$(mktemp -d)"
review_dir="${review_parent}/homebrew-moeka"
git clone . "${review_dir}"
sed -i '' -E 's/^([[:space:]]*version[[:space:]]*)"[^"]*"/\1"0.1.11"/' \
"${review_dir}/Casks/moeka.rb"
mkdir -p "$(brew --repository)/Library/Taps/moeka-review"
ln -sfn "${review_dir}" \
"$(brew --repository)/Library/Taps/moeka-review/homebrew-moeka"
brew trust moeka-review/moeka
brew livecheck --cask moeka-review/moeka/moeka --json如果旧版本没有 brew trust 子命令,可以跳过这一步;本文 workflow 会用 brew help trust 自动判断。
预期能看到 current: 0.1.11、latest: 0.1.29、outdated: true。
然后验证 Homebrew 官方更新:
brew bump-cask-pr --write-only --no-audit --no-style \
--version 0.1.29 moeka-review/moeka/moeka
git -C "${review_dir}" diff -- Casks/moeka.rb预期 diff 只改 version 和两个 sha256,并保留 livecheck 块。
第一次运行
bump-cask-pr时,Homebrew 可能会安装自己的 Ruby gem, 并下载约 44 MB 的两个 DMG。这是 Homebrew 官方工具的正常行为,不是本方案额外引入的依赖。
5.3 独立复算 SHA-256
不要只相信 workflow 的日志。下载同一版本的资产并独立复算:
for arch in arm64 x86_64; do
curl -fsSL \
"https://github.com/MoekaProject/community/releases/download/v0.1.29/Moeka-macos-${arch}.dmg" \
| shasum -a 256
done本次 Moeka v0.1.29 的实测值:
| 架构 | SHA-256 | 大小 |
|---|---|---|
| arm64 | c579ed1b2909d730ed390f6efcf469095808ff00f92821939fde5963eaacdeae | 21304110 字节 |
| x86_64 | da1f7dfdadea684551280e6c34c656bdaec3d800ea6c378d69bb69c5769c4acc | 23181634 字节 |
5.4 验证 workflow 的 dry-run
在 fork 的 Actions 页面选择 Auto-bump Moeka cask → Run workflow, 把 dry_run 设为 true。预期日志显示当前版本和 latest,但不产生提交。 手动触发同样要求 workflow 文件已经存在于该 fork 的默认分支,并确认 Actions 没有被禁用。
第一次上线时,建议连续观察两三次:
- 没有新版本时是否安静退出;
- 有新版本时是否只产生一个 cask 提交;
- 分支保护和 token 权限是否正确;
- Homebrew 下载失败时是否能看到明确错误。
6. 常见坑与设计取舍
6.1 cron、时区与暂停
- GitHub Actions 的 cron 使用 UTC,不是本地时区;
- 最小间隔是 5 分钟,但没必要这么频繁;
- 仓库 60 天无活动后,scheduled workflow 可能被暂停,需要手动恢复;
schedule只会在默认分支上运行,fork 里的定时任务也可能需要在 Actions 中手动启用。- macOS runner 的分钟数通常比 Linux runner 更贵;公开仓库一般免费, private/自托管仓库要按实际计费策略评估频率。
对 cask 更新来说,每小时或每 6 小时通常足够;如果上游一天只发布一次,每天一次也可以。
6.2 token、分支保护与下游 workflow
permissions: contents: write是 push 回仓库的最低权限;- Settings → Actions → General → Workflow permissions 需要选择 Read and write permissions;
- 分支保护禁止直接 push 时,改为 PR + auto-merge;
- 用
GITHUB_TOKEN产生的 push 不会触发新的 workflow;如果下游发布依赖 tap 提交,需要使用 GitHub App token 或 PAT。
6.3 Homebrew 7 的 tap trust
Homebrew 7 会拒绝加载未受信任的第三方 tap cask:
Error: Refusing to load cask ... from untrusted tap ...在本地或 CI 中先执行 brew trust OWNER/TAP。本文 workflow 已包含 brew help trust 判断,兼容没有 trust 子命令的旧版本。
6.4 官方 autobump 不适用于第三方 tap
Homebrew 官方的 autobump 只在官方仓库中运行。第三方 tap 不能把“官方每 3 小时 自动开 PR”当成现成服务;你需要自己的调度器和 token。
6.5 prerelease 与资产命名
本文的 API 兜底使用 GitHub 的 releases/latest,它只返回正式版。 如果项目要跟踪 nightly / beta / RC,需要改用 releases 列表并显式过滤; 同时要确认 livecheck 策略是否包含预发布版本。
资产文件名也要稳定。例如 Moeka-macos-#{arch}.dmg 一旦改名, cask 的 url 和 bump-cask-pr 都会找不到资产。本文选择在资产缺失时失败, 而不是静默发布错误的 checksum。
6.6 auto_updates true 不是自动发版
auto_updates true 只说明应用自身可能自动更新。cask 的版本、架构和 checksum 仍然是新安装和 brew upgrade --cask 的依据,所以 tap 侧仍要更新 cask。
6.7 --no-audit --no-style 的取舍
本文在定时任务里跳过审计和样式,减少每次运行的 gem 安装与耗时; 这不是“永远不审计”。建议在 PR 或单独的 CI 中运行:
brew style Casks/moeka.rb
brew audit --cask moeka如果上游变更较大,可以去掉 --no-audit --no-style,让 bump-cask-pr 在提交前执行完整检查。
6.8 安全性
- action 固定 tag 方便阅读,但高安全要求场景应固定到 commit SHA;
- 只授予
contents: write,不要给无关的issues/packages权限; - 不要把 PAT 写进代码,使用仓库 secrets;
- 只从上下游官方仓库下载资产;
- 失败时停止并保留日志,不要
|| true吞掉错误。
7. 平台选择:GitHub、Forgejo、Woodpecker
| 平台 | 定时语法 | Homebrew Cask 适配 |
|---|---|---|
| GitHub Actions | on.schedule.cron | 最佳;可直接用 macos-latest |
| Forgejo Actions(Codeberg) | on.schedule.cron,如 @daily | 托管 runner 多为 Linux;建议自托管 macOS runner,或用等价 Ruby 脚本 |
| Woodpecker | when: event: cron,cron 在 UI 配置 | Linux 为主;适合检测/通知,cask 更新需 macOS 环境 |
| Renovate | schedule 配置 | 适合依赖更新,不负责 cask 的 SHA-256 |
Homebrew Cask 面向 macOS。Codeberg 的托管 runner 通常没有 macOS 环境, 因此不能把 GitHub 的
macos-latestworkflow 原样搬过去;可以保留检测逻辑, 把“计算与改写”放到自托管 macOS runner,或用 Ruby/curl/shasum 实现等价逻辑。
8. 优秀项目参考
下面这些项目都来自实际仓库,重点看它们如何组合“定时器 + 幂等 + 发布/更新”, 而不是只抄 cron 表达式。
8.1 GitHub
| 项目 | 值得借鉴 | 链接 |
|---|---|---|
| Homebrew Autobump | 官方 cask 每约 3 小时检查一次,有更新才开 PR;是 PR 模式的黄金标准 | 文档 · 源码 |
| Homebrew/actions | 官方的 setup-homebrew、发布与 CI 基础设施;本文使用其 setup action | Homebrew/actions |
| nushell/nushell | nightly-build.yml 每天 00:15 UTC 构建 nightly;如果上游 commit 未变化就跳过 | nightly-build.yml |
| NixOS/nixpkgs-update | 大规模自动包更新:r-ryantm 机器人持续开 PR;基础设施用 6/12 小时和每日 timer | 更新器 · 基础设施配置 |
| renovatebot/renovate | 成熟的 schedule / PR / automerge 模型;适合依赖和版本文件更新 | Renovate · schedule 选项 |
这些项目的共同点
- 检测到变化才工作:Nushell 会比较最新 nightly 的 hash,Homebrew autobump 会先 livecheck;
- 幂等:没有变化时退出,不制造空提交;
- 可观测:日志和 PR/commit 能说明为什么更新;
- 权限最小化:只给发布/写入需要的 token;
- 历史可回滚:通过 Git 提交或 PR 保留变更记录。
8.2 Codeberg
| 项目 | 值得借鉴 | 链接 |
|---|---|---|
forgejo/forgejo mirror.yml | 使用 schedule: - cron: '@daily' 每天同步镜像;最直接的 Forgejo Actions 定时示例 | mirror.yml |
forgejo/forgejo publish-release.yml | tag 触发的正式发布:复制、签名、升级实验环境;把“定时检测”和“发布动作”分开 | publish-release.yml |
| Codeberg-Infrastructure/renovate-config | Woodpecker 的 event: cron + Renovate;配置 schedule:automergeDaily、周末窗口和 automerge | 配置仓库 · renovate.yml |
| Codeberg/Documentation | 实际项目直接 extends Codeberg 的 Renovate 配置,是“组织级统一更新策略”的落地例子 | renovate.json |
Codeberg 方案的关键差别
- Forgejo Actions 的 schedule 语法与 GitHub 类似,但认证、runner 和权限模型不同;
- Woodpecker 的 cron 是
event: cron,具体 cron 名称/时间在 Woodpecker 中管理,不是全部写在仓库文件里; - Codeberg 的 Renovate 配置使用 automerge 和“每日/周末”策略,适合依赖更新, 但不负责 Homebrew cask 的双架构 SHA-256;
- 若要在 Codeberg 上更新 cask,优先准备自托管 macOS runner,或把 Homebrew 的计算步骤放到 GitHub 的 macOS runner。
9. 复用到其他项目
以本文为模板,替换下面几个位置即可:
| 位置 | Moeka 示例 | 替换为 |
|---|---|---|
| cask token | moekaproject/moeka/moeka | OWNER/TAP/CASK |
| cask 文件 | Casks/moeka.rb | 你的 Casks/<name>.rb |
| 上游 | MoekaProject/community | 你的 Release 仓库 |
| livecheck URL | https://github.com/MoekaProject/community | 你的仓库/发布页 |
| commit message | moeka: bump to ... | 你的 cask 名称 |
| 资产 URL | Moeka-macos-#{arch}.dmg | 你的实际资产命名 |
如果上游不是 GitHub,livecheck 策略可能不同,先运行:
brew livecheck --cask OWNER/TAP/CASK --verbose确认 Homebrew 能识别版本后,再把 brew livecheck --json 的输出接到 brew bump-cask-pr --version。
10. 最小落地清单
- 在 cask 中补上可用的
livecheck块; - 用
brew livecheck --cask ... --json验证outdated结果; - 用
brew bump-cask-pr --write-only --version X在临时 tap 验证 SHA-256 与格式; - 加入 scheduled workflow,先跑
dry_run=true; - 配置
contents: write权限; - 决定直推 main 还是 PR + auto-merge;
- 观察第一次真实更新和无更新两种情况;
- 固定 action 版本/commit SHA,收紧 token 权限;
- 在上游资产改名、平台切换或 pre-release 策略变化时复查 livecheck。
11. 结论
“自动生成 SHA-256 并更新 cask”最成熟的做法不是再写一个下载脚本,而是把职责拆开:
- cask 负责声明:版本、架构、URL、livecheck;
- Homebrew 负责计算:
bump-cask-pr --write-only下载并生成两个 SHA-256; - 调度器负责节奏:GitHub Actions / Forgejo Actions / Woodpecker 的 schedule;
- Git 负责发布:commit/push 或 PR + auto-merge;
- 兜底负责可用性:livecheck 失败时用 API 检测,仍然不手写 checksum。
对第三方 tap 来说,这套组合比“等待仓库所有者手动发版”更可靠,也比自己维护 一套多架构下载逻辑更容易审计。MoekaProject 的实例已经验证了 0.1.11 -> 0.1.29 的检测、双架构 SHA-256 计算和 cask 改写; 把它复制到下一个 tap 时,只需要替换 token、路径和上游地址。
参考链接汇总
- Homebrew 官方文档:Autobump、Brew Livecheck、Cask Cookbook
- Homebrew Actions:github.com/Homebrew/actions
- GitHub schedule 文档:Events that trigger workflows
- Forgejo Actions:forgejo.org/docs/latest/user/actions
- Woodpecker cron:woodpecker-ci.org/docs/usage/cron
- Moeka 上游:MoekaProject/community
- Moeka Tap:MoekaProject/homebrew-moeka