diff --git a/.github/workflows/sync-homebrew-cask.yml b/.github/workflows/sync-homebrew-cask.yml index 73b98f875..78b687c81 100644 --- a/.github/workflows/sync-homebrew-cask.yml +++ b/.github/workflows/sync-homebrew-cask.yml @@ -34,7 +34,9 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets.GITHUB_TOKEN }} + # The protected default branch only permits organization-admin bypass. + # Use a dedicated repository-scoped admin token; never weaken branch rules. + token: ${{ secrets.HOMEBREW_CASK_ADMIN_TOKEN || secrets.GITHUB_TOKEN }} - uses: actions/setup-node@v4 with: @@ -114,6 +116,7 @@ jobs: if: steps.publication.outputs.ready == 'true' env: VERSION: ${{ steps.meta.outputs.version }} + CASK_ADMIN_TOKEN_AVAILABLE: ${{ secrets.HOMEBREW_CASK_ADMIN_TOKEN != '' }} run: | set -euo pipefail git config user.name "github-actions[bot]" @@ -123,6 +126,10 @@ jobs: echo "cask 已是 $VERSION,无需提交" exit 0 fi + if [ "$CASK_ADMIN_TOKEN_AVAILABLE" != true ]; then + echo "::error::Protected beta requires HOMEBREW_CASK_ADMIN_TOKEN from an organization administrator. No branch protection was changed; this update has not been pushed." + exit 1 + fi # [skip ci]: cask text changes do not need another cross-platform ci.yml run. git commit -m "[cask] openless $VERSION (auto from release) [skip ci]" git push diff --git a/docs/homebrew-openless-updates.md b/docs/homebrew-openless-updates.md index 448eb52b1..9f9e4f75d 100644 --- a/docs/homebrew-openless-updates.md +++ b/docs/homebrew-openless-updates.md @@ -6,6 +6,8 @@ 脚本从 ARM/Intel DMG 的真实文件名读取完整版本,检查两个架构一致且与 tag 对应,再计算 SHA-256 并更新默认分支的 `Casks/openless.rb`。tag 不带 `+build`,DMG 文件名保留日期构建号;1.x 保持 Monterey,2.x 使用与 Host 对应的 Sonoma。 +权限前提:当前 `Protect #beta` 规则仅允许 OrganizationAdmin bypass,默认 `GITHUB_TOKEN` 不能直推。无人值守同步须配置仓库 Secret `HOMEBREW_CASK_ADMIN_TOKEN`,由组织管理员提供、只授权此仓库 Contents read/write;不修改分支保护,不在聊天或日志暴露值。未配置且确有更新时工作流明确失败并提示,不能把准备好文件当作已同步;cask 已被管理员更新时则幂等通过。此 Secret 是外部配置前提,不由代码自证已经存在。 + 手动恢复同步(不重建包): ```sh diff --git a/openless-all/app/scripts/release-publication-contract.test.mjs b/openless-all/app/scripts/release-publication-contract.test.mjs index 03db5a6f6..2e48a669e 100644 --- a/openless-all/app/scripts/release-publication-contract.test.mjs +++ b/openless-all/app/scripts/release-publication-contract.test.mjs @@ -19,6 +19,11 @@ const sync = workflow('sync-homebrew-cask.yml'); assert.deepEqual(sync.on.release.types, ['published']); assert.ok(sync.on.workflow_dispatch.inputs.tag.required); const steps = sync.jobs['update-homebrew-cask'].steps; +const checkout = steps.find((step) => step.uses === 'actions/checkout@v4'); +assert.match(checkout.with.token, /HOMEBREW_CASK_ADMIN_TOKEN/); +const commit = steps.find((step) => step.name === 'Commit & push cask bump'); +assert.match(commit.env.CASK_ADMIN_TOKEN_AVAILABLE, /HOMEBREW_CASK_ADMIN_TOKEN/); +assert.ok(commit.run.indexOf('CASK_ADMIN_TOKEN_AVAILABLE') < commit.run.indexOf('git push')); const gate = steps.find((step) => step.id === 'publication'); assert.ok(gate); assert.match(gate.run, /--json isDraft,isPrerelease/);