Skip to Main Content
Homebrew Cask 定时自动发版:livecheck + bump-cask-pr 的成熟方案Back to Top

Homebrew Cask 定时自动发版:livecheck + bump-cask-pr 的成熟方案

20 minutes

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
2Homebrew7.0.10-108-g8d79935
3上游仓库MoekaProject/community
4TapMoekaProject/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 需要补齐的链路是:

  1. 检测:知道上游出现了一个新的正式版本;
  2. 计算:下载 arm64 / x86_64 资产,分别计算 SHA-256;
  3. 改写:更新 cask 的 version 与两个 sha256;
  4. 发布:提交到 tap,让 brew update 能拉到;
  5. 复用:没有新版本时安静退出,不产生空提交。

这五步里,只有“调度”和“提交”需要自己写;中间的版本、下载、校验和都可以交给 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/TAP

brew 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"
end

3.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 trustHomebrew 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 会失败,此时有两个选择:

  1. 在 Settings 中放行 GitHub Actions bot;
  2. 把最后一步改成“开 PR + auto-merge”,由维护者审核。

官方 autobump 选择的是 PR 模式,适合官方仓库和需要人工判断的高风险更新。

4.4 用户侧如何拿到更新

发布成功后,用户侧只需要让 tap 更新,再显式升级 cask:

brew update
brew upgrade --cask moeka

新用户则是:

brew tap MoekaProject/homebrew-moeka
brew install --cask moeka

brew 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; done

5.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大小
arm64c579ed1b2909d730ed390f6efcf469095808ff00f92821939fde5963eaacdeae21304110 字节
x86_64da1f7dfdadea684551280e6c34c656bdaec3d800ea6c378d69bb69c5769c4acc23181634 字节

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 Actionson.schedule.cron最佳;可直接用 macos-latest
Forgejo Actions(Codeberg)on.schedule.cron,如 @daily托管 runner 多为 Linux;建议自托管 macOS runner,或用等价 Ruby 脚本
Woodpeckerwhen: event: cron,cron 在 UI 配置Linux 为主;适合检测/通知,cask 更新需 macOS 环境
Renovateschedule 配置适合依赖更新,不负责 cask 的 SHA-256

Homebrew Cask 面向 macOS。Codeberg 的托管 runner 通常没有 macOS 环境, 因此不能把 GitHub 的 macos-latest workflow 原样搬过去;可以保留检测逻辑, 把“计算与改写”放到自托管 macOS runner,或用 Ruby/curl/shasum 实现等价逻辑。

8. 优秀项目参考

下面这些项目都来自实际仓库,重点看它们如何组合“定时器 + 幂等 + 发布/更新”, 而不是只抄 cron 表达式。

8.1 GitHub

项目值得借鉴链接
Homebrew Autobump官方 cask 每约 3 小时检查一次,有更新才开 PR;是 PR 模式的黄金标准文档 · 源码
Homebrew/actions官方的 setup-homebrew、发布与 CI 基础设施;本文使用其 setup actionHomebrew/actions
nushell/nushellnightly-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.ymltag 触发的正式发布:复制、签名、升级实验环境;把“定时检测”和“发布动作”分开publish-release.yml
Codeberg-Infrastructure/renovate-configWoodpecker 的 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 tokenmoekaproject/moeka/moekaOWNER/TAP/CASK
cask 文件Casks/moeka.rb你的 Casks/<name>.rb
上游MoekaProject/community你的 Release 仓库
livecheck URLhttps://github.com/MoekaProject/community你的仓库/发布页
commit messagemoeka: bump to ...你的 cask 名称
资产 URLMoeka-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”最成熟的做法不是再写一个下载脚本,而是把职责拆开:

  1. cask 负责声明:版本、架构、URL、livecheck;
  2. Homebrew 负责计算:bump-cask-pr --write-only 下载并生成两个 SHA-256;
  3. 调度器负责节奏:GitHub Actions / Forgejo Actions / Woodpecker 的 schedule;
  4. Git 负责发布:commit/push 或 PR + auto-merge;
  5. 兜底负责可用性:livecheck 失败时用 API 检测,仍然不手写 checksum。

对第三方 tap 来说,这套组合比“等待仓库所有者手动发版”更可靠,也比自己维护 一套多架构下载逻辑更容易审计。MoekaProject 的实例已经验证了 0.1.11 -> 0.1.29 的检测、双架构 SHA-256 计算和 cask 改写; 把它复制到下一个 tap 时,只需要替换 token、路径和上游地址。

参考链接汇总