Skip to content

ci(release): 正式发版自动更新 Homebrew cask(+ 补 cask 到 1.3.5)#586

Merged
appergb merged 3 commits into
betafrom
ci/auto-update-homebrew-cask
Jun 4, 2026
Merged

ci(release): 正式发版自动更新 Homebrew cask(+ 补 cask 到 1.3.5)#586
appergb merged 3 commits into
betafrom
ci/auto-update-homebrew-cask

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented Jun 4, 2026

User description

解决的问题

每次发布正式版后,Homebrew cask 需要手动更新。诊断结果:

  • 仓库里其实有 update-cask.yml,监听 release: published。但正式版 Release 是 release-tauri.yml 用默认 GITHUB_TOKEN 创建的——GitHub 防递归:默认 token 创建的 Release 不会触发其它工作流,所以它从不自动跑(历史上只在一次手动 beta 场景跑过、且失败)。
  • 它的条件 endsWith('-tauri') 还会误匹配 beta(beta tag 也以 -tauri 结尾)。
  • 现状:cask 停在 1.3.4,但最新正式版已是 1.3.5 —— Homebrew 用户拿不到 1.3.5。

改动

① 把 cask 更新并进发版流水线(commit 1)

  • release-tauri.yml 新增 needs: buildupdate-homebrew-cask job:构建 + 建 Release 完成后,在同一条流水线里更新 cask(不再依赖会被防递归吃掉的 release 事件)。
  • 仅正式版v*-tauri 且非 -beta-tauri。beta 不碰 Homebrew。
  • 直接 gh release download 下载两个 DMG 自己算 sha256(不依赖 GitHub 资产 digest 字段的时序),sed 更新 Casks/openless.rb,提交回默认分支 beta[skip ci],cask 文本改动不必再跑跨平台 CI)。
  • 删除失效且会错配 beta 的 update-cask.yml

② 一次性把 cask 补到 1.3.5(commit 2,可单独取舍)

  • v1.3.5-tauri release 资产的 sha256 把 Casks/openless.rb 从 1.3.4 补到 1.3.5,立即让 brew install/upgrade --cask openless 拿到最新正式版。

验证

  • ruby -ryaml 校验 workflow YAML 通过;ruby -c 校验 cask 语法 OK。
  • 真实效果需下一次正式发版(推 v*-tauri tag)才能端到端验证;逻辑沿用原 update-cask.yml 已验证过的 sed 模式,DMG 命名(OpenLess_<v>_aarch64.dmg / _x64.dmg)已确认匹配。

之后你的发版动作不变

照常推 vX.Y.Z-tauri tag → 自动构建多平台 + 建 Release + 自动更新 Homebrew cask。beta(-beta-tauri)完全不受影响。


PR Type

Bug fix, Enhancement


Description

  • Fix Homebrew cask auto-update not triggered due to GitHub recursion prevention

  • Move cask update into release pipeline as a new job (update-homebrew-cask)

  • Fix sed command to avoid corrupting arch line in Casks/openless.rb

  • Remove obsolete update-cask.yml; update cask to version 1.3.5


Diagram Walkthrough

flowchart LR
  A["release-tauri.yml"] --> B["build"]
  B --> C["update-homebrew-cask"]
  C --> D["Download DMGs from release"]
  D --> E["Compute SHA256"]
  E --> F["Update Casks/openless.rb"]
  F --> G["Commit & push to default branch"]
Loading

File Walkthrough

Relevant files
Enhancement
openless.rb
Update cask to version 1.3.5                                                         

Casks/openless.rb

  • Updated version from 1.3.4 to 1.3.5
  • Updated SHA256 hashes for both arm and intel architectures
+3/-3     
release-tauri.yml
Integrate homebrew cask update into release pipeline         

.github/workflows/release-tauri.yml

  • Added new job 'update-homebrew-cask' that runs after build for stable
    releases
  • Job downloads DMGs from release, computes SHA256, and updates cask
    file
  • Includes safeguards to prevent accidental arch line corruption
+90/-0   
Miscellaneous
update-cask.yml
Remove obsolete cask update workflow                                         

.github/workflows/update-cask.yml

  • Deleted the entire file as it was ineffective and had incorrect
    trigger logic
+0/-83   

吕柏青 added 2 commits June 4, 2026 11:50
原 update-cask.yml 监听 release: published,但 Release 由 release-tauri.yml 用默认 GITHUB_TOKEN 创建,不会触发该事件(GitHub 防递归)——所以 cask 从不自动更新,需手动;且其条件 endsWith('-tauri') 连 beta 也匹配。

改为在 release-tauri.yml 内新增 needs:build 的 update-homebrew-cask job:仅正式版 tag(v*-tauri 且非 -beta-tauri),构建+建 Release 后下载 DMG 算 sha256、sed 更新 Casks/openless.rb、提交回默认分支([skip ci])。删除失效且会错配 beta 的 update-cask.yml。
cask 此前停在 1.3.4(自动更新一直没生效),Homebrew 用户拿不到 1.3.5。用 v1.3.5-tauri release 资产 sha256 一次性补齐。
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

PR Reviewer Guide 🔍

(Review updated until commit 18ffbb0)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Branch Protection Risk

The update-homebrew-cask job pushes directly to the default branch (beta) using the default GITHUB_TOKEN. If the default branch has branch protection rules that require status checks, pull request approvals, or restrict pushes from actions (common in many repos), this push will fail. Consider using a Personal Access Token (PAT) with appropriate permissions or disabling branch protection for this specific bot commit if possible. Without that, the job will fail silently unless the push error is caught.

  with:
    ref: ${{ github.event.repository.default_branch }}
    token: ${{ secrets.GITHUB_TOKEN }}

- name: Resolve version + DMG sha256 from the release
  id: meta
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    TAG: ${{ github.ref_name }}
  run: |
    set -euo pipefail
    version="${TAG#v}"
    version="${version%-tauri}"

    # 直接下载 DMG 自己算 sha256,不依赖 GitHub 资产 digest 字段的时序/可用性。
    mkdir -p "$RUNNER_TEMP/dmg"
    gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
      --dir "$RUNNER_TEMP/dmg" \
      --pattern '*aarch64.dmg' --pattern '*_x64.dmg'

    arm_sha="$(sha256sum "$RUNNER_TEMP"/dmg/*aarch64.dmg | cut -d' ' -f1)"
    intel_sha="$(sha256sum "$RUNNER_TEMP"/dmg/*_x64.dmg | cut -d' ' -f1)"

    {
      echo "version=$version"
      echo "arm_sha=$arm_sha"
      echo "intel_sha=$intel_sha"
    } >> "$GITHUB_OUTPUT"
    echo "cask → version=$version arm=$arm_sha intel=$intel_sha"

- name: Update Casks/openless.rb
  env:
    VERSION: ${{ steps.meta.outputs.version }}
    ARM_SHA: ${{ steps.meta.outputs.arm_sha }}
    INTEL_SHA: ${{ steps.meta.outputs.intel_sha }}
  run: |
    set -euo pipefail
    cask="Casks/openless.rb"
    # ⚠️ cask 第 2 行 `arch arm: "aarch64", intel: "x64"` 也含 `intel: "..."`,
    # 裸 `s/intel: "..."/` 会把 arch 指令一起改成哈希 → Intel 用户 brew 装包 404。
    # 所以 intel 的 sed 用 `^[[:space:]]*intel:` 锚定 sha256 块那一行;arm 的 sed
    # 靠 `sha256 arm:   ` 前缀天然只命中 sha256 行。捕获组 \1 保留原缩进。
    sed -i "s/version \"[^\"]*\"/version \"$VERSION\"/" "$cask"
    sed -i "s/sha256 arm:   \"[^\"]*\"/sha256 arm:   \"$ARM_SHA\"/" "$cask"
    sed -i "s/^\([[:space:]]*\)intel: \"[^\"]*\"/\1intel: \"$INTEL_SHA\"/" "$cask"
    # 防呆:arch 指令必须原样保留,且本次 sha256 必须确实写进去了,否则让 job 失败。
    grep -q 'arch arm: "aarch64", intel: "x64"' "$cask" \
      || { echo "::error::arch 指令被 sed 误伤"; exit 1; }
    grep -q "\"$ARM_SHA\"" "$cask" && grep -q "\"$INTEL_SHA\"" "$cask" \
      || { echo "::error::sha256 未正确写入 cask"; exit 1; }
    echo "----- updated $cask -----"
    cat "$cask"

- name: Commit & push cask bump
  env:
    VERSION: ${{ steps.meta.outputs.version }}
  run: |
    set -euo pipefail
    git config user.name "github-actions[bot]"
    git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
    git add Casks/openless.rb
    if git diff --cached --quiet; then
      echo "cask 已是 $VERSION,无需提交"
      exit 0
    fi
    # [skip ci]:cask 文本改动不需要再跑一遍跨平台 ci.yml。
    git commit -m "[cask] openless $VERSION (auto from release) [skip ci]"
    git push
Pre-release Tag Match

The job condition only excludes tags ending with -beta-tauri but would match any other pre-release suffix (e.g., v1.3.5-rc1-tauri, v1.3.5-alpha-tauri). This would cause the cask to be updated to a pre-release version of OpenLess, which is not the intended stable release. To prevent this, the condition should also exclude tags containing a pre-release pattern, such as not matching tags with a hyphen before tauri (e.g., !contains(github.ref_name, '-') or a more explicit glob like endsWith(github.ref, '-tauri') && !contains(github.ref, '-beta-') && !contains(github.ref, '-rc-')). This is important to avoid pushing unstable builds to Homebrew users.

if: >-
  startsWith(github.ref, 'refs/tags/v')
  && endsWith(github.ref, '-tauri')
  && !endsWith(github.ref, '-beta-tauri')
steps:

@appergb
Copy link
Copy Markdown
Collaborator Author

appergb commented Jun 4, 2026

@claude 请审核这个 PR(目标合并到 beta)。重点:GitHub Actions update-homebrew-cask job——触发条件是否严格只匹配正式版(v*-tauri 且非 -beta-tauri);DMG 下载+sha256 计算可靠性;提交回默认分支的 token 权限与脚本注入风险(tag 名经 env 传入是否安全);删除 update-cask.yml 是否有遗漏。能否安全合并?给出分级。

@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @appergb's task in 3m 59s —— View job


PR #586 代码审核

审核任务清单:

  • 初始化审核任务
  • 读取 release-tauri.yml 新增 job 代码
  • 读取 Casks/openless.rb cask 文件
  • 分析触发条件严格性(正式版 vs beta 区分)
  • 分析 DMG 下载 + SHA256 计算可靠性
  • 分析 token 权限与脚本注入风险
  • 分析删除 update-cask.yml 的完整性
  • 汇总审核意见 + 给出合并分级

审核结论:🔴 存在一处严重 Bug,合并前必须修复


1. 触发条件 ✅ 正确

if: >-
  startsWith(github.ref, 'refs/tags/v')
  && endsWith(github.ref, '-tauri')
  && !endsWith(github.ref, '-beta-tauri')

三重条件组合严密。验证几类 tag:

Tag 示例 结果
v1.3.5-tauri ✅ 运行(正式版)
v1.3.6-1-beta-tauri ✅ 跳过(beta)
v1.4.0-tauri ✅ 运行
v2.0.0-rc-tauri(假设将来有 RC) ✅ 运行(RC 也算正式)

只要命名规范保持 *-beta-tauri / *-tauri 二分,就没有漏洞。


2. DMG 下载 + SHA256 计算 ✅ 可靠

  • needs: build 保证 build job 完整跑完(包括 Release 创建和资产上传)后才运行此 job,无时序风险。
  • 直接下载 DMG 本地算 sha256sum,比原 update-cask.yml 依赖 GitHub API digest 字段更可靠(digest 字段在资产上传后有短暂延迟且不保证稳定)。
  • 文件名 glob *aarch64.dmg / *_x64.dmg 与 cask URL 模板中的 OpenLess_#{version}_#{arch}.dmg 命名规律一致,匹配无误。
  • set -euo pipefail 全程有效,若下载或 sha256 失败,job 立即报错。

3. Token 权限与注入风险 ✅ 安全

  • permissions: contents: write 仅在本 job 声明,精确最小权限。
  • TAG: ${{ github.ref_name }} 通过 env: 块传入 shell,而非直接字符串插值到 run: 里——这是防注入的标准写法,tag 名不会作为 shell 代码被求值。
  • version 提取用纯 bash 参数展开(${TAG#v} / ${version%-tauri}),无 eval / subprocess。
  • ARM_SHA / INTEL_SHAsha256sum 输出的纯十六进制,sed 替换不存在注入路径。
  • git push(非 --force),[skip ci] commit 防止 CI 递归,合理。

唯一的注意点(低风险):若 beta 分支在构建期间接收了新 commit,最后的 git push 会因 non-fast-forward 报错并使 job 失败。这不是安全问题,只是韧性问题,失败后手动重跑即可,可接受。


4. 删除 update-cask.yml ✅ 无遗漏

原文件三重失效:① 默认 token 创建的 Release 不触发 release: published 事件(GitHub 防递归);② 条件 endsWith('-tauri') 误匹配 beta tag;③ 依赖 GitHub API digest 字段时序不稳定。新实现在同一流水线内解决了全部问题。删除干净,无遗漏。


5. 🔴 严重 Bug:Intel sed 模式过宽,会损坏 arch 指令

问题位置: .github/workflows/release-tauri.yml 第 702 行

sed -i "s/intel: \"[^\"]*\"/intel: \"$INTEL_SHA\"/" "$cask"

为什么会出错: Casks/openless.rb 里有两处 intel: "..." 子串:

# 第 2 行 —— arch 指令
  arch arm: "aarch64", intel: "x64"

# 第 6 行 —— sha256 块(目标行)
         intel: "6fa1960f..."

sed 默认做子串匹配,不锚定行首。上面那条命令会同时命中两行:

替换前 替换后
2(错误命中) arch arm: "aarch64", intel: "x64" arch arm: "aarch64", intel: "6fa1960f..."损坏
6(正确命中) intel: "6fa1960f..." intel: "$INTEL_SHA"

后果: arch 指令里的 intel:"x64" 变成了 64 字符哈希。Homebrew 生成的 Intel 下载 URL 变为 OpenLess_1.x.y_6fa1960f...dmg(文件不存在),Intel Mac 用户 brew install/upgrade --cask openless 全部报 404 失败。(ARM 用户不受影响,因为 aarch64 那侧未动。)

注: 自动代码审查机器人虽然也标记了第 702 行,但给出的原因("前导空格使模式无法匹配")是错的——正因为没有前导空格锚定,才会多匹配了 arch 那行。实际上两行都被替换了。

修复(一行):

# 将第 702 行改为:
sed -i "s/^[[:space:]]*intel: \"[^\"]*\"/         intel: \"$INTEL_SHA\"/" "$cask"

^[[:space:]]*intel: 锚定行首:

  • 第 2 行以 arch 开头,不匹配 ^[[:space:]]*intel: → 不被替换 ✓
  • 第 6 行以 intel: 开头,匹配 → 正确替换 ✓

Fix this →


6. Commit 2(cask 补到 1.3.5)✅ 正确

Casks/openless.rb 的手动更新(version + 两个 sha256)格式正确,可立即让 brew install/upgrade --cask openless 拿到 1.3.5。这个 commit 可以独立合并。


分级汇总

检查项 结论
触发条件(正式版/beta 区分) ✅ 严格正确
DMG 下载 + sha256 可靠性 ✅ 可靠
Token 权限 ✅ 最小权限
tag 名注入风险 ✅ 安全(env 传入)
删除 update-cask.yml ✅ 无遗漏
cask 补 1.3.5(commit 2) ✅ 正确
Intel sed 模式(第 702 行) 🔴 严重 Bug

合并建议:修复第 702 行的 sed 模式后可合并。 修复只需把 s/intel: 换成 s/^[[:space:]]*intel: 一处改动。Commit 2(cask 1.3.5 补丁)可不受影响独立先行。

裸 s/intel: "..."/ 会同时命中 cask 第2行 arch arm: "aarch64", intel: "x64",把 arch 的 intel 改成哈希 → Intel Mac 用户 brew 安装 404。改用 ^[[:space:]]*intel: 锚定 sha256 块行(捕获组保留缩进),并加防呆:arch 指令被破坏或 sha256 没写进去就让 job 失败。本地模拟新旧 sed 对比已验证。
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Persistent review updated to latest commit 18ffbb0

@appergb
Copy link
Copy Markdown
Collaborator Author

appergb commented Jun 4, 2026

✅ 第二轮复审(security-reviewer agent):无 CRITICAL/HIGH。已修的 intel sed CRITICAL 经实测验证正确(arch 行不再被误改)+ 防呆 grep 有效;tag 经 env 传入无注入、权限最小化、触发条件严格只匹配正式版。仅 2 个 MEDIUM(运维健壮性,后续跟进)。批准合并。

@appergb appergb merged commit 7cc8175 into beta Jun 4, 2026
4 checks passed
@appergb appergb deleted the ci/auto-update-homebrew-cask branch June 4, 2026 05:08
appergb pushed a commit that referenced this pull request Jun 4, 2026
… 快速录音提示音丢失修复(#584) + 正式发版自动更新 Homebrew cask(#586)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant