diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df6bc49..6283bb6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,9 @@ name: Release # Without it the release does NOT build: an ad-hoc signature changes the # app's identity on every release and macOS revokes Full Disk Access after # every upgrade. -# HOMEBREW_TAP_TOKEN - token with write access to RenaCode/homebrew-tap. +# TAP_APP_KEY (secret) + TAP_APP_CLIENT_ID (variable) - GitHub App `renacode-tap` +# (Contents: write, installed on RenaCode/homebrew-tap). The job mints a +# 1-hour token scoped to that one repository; nothing expires. # # Still no Developer ID and no notarization - Gatekeeper does not know the # publisher; the cask removes the quarantine attribute in postflight. @@ -277,23 +279,41 @@ jobs: mac-app/build/*.sha256 generate_release_notes: true - - name: Check tap token + - name: Check tap app if: env.IS_RELEASE == 'true' env: - TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + TAP_APP_CLIENT_ID: ${{ vars.TAP_APP_CLIENT_ID }} + TAP_APP_KEY: ${{ secrets.TAP_APP_KEY }} run: | - if [ -z "$TAP_TOKEN" ]; then - echo "::error::Release published, but HOMEBREW_TAP_TOKEN is missing - the cask in the tap was NOT updated. Contents to insert manually:" + if [ -z "$TAP_APP_CLIENT_ID" ] || [ -z "$TAP_APP_KEY" ]; then + echo "::error::Release published, but TAP_APP_CLIENT_ID/TAP_APP_KEY is missing - the cask in the tap was NOT updated. Contents to insert manually:" cat mac-app/build/cloudmachine.rb exit 1 fi + # Token and the access probe run on EVERY build, not only on release: + # an app that lost its installation (or a missing key) fails the PR, + # not the release after the DMG is already published. + - name: Tap token + id: tap-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.TAP_APP_CLIENT_ID }} + private-key: ${{ secrets.TAP_APP_KEY }} + owner: RenaCode + repositories: homebrew-tap + + - name: Check tap access + env: + TAP_TOKEN: ${{ steps.tap-token.outputs.token }} + run: git ls-remote "https://x-access-token:${TAP_TOKEN}@github.com/RenaCode/homebrew-tap.git" HEAD + - name: Check out tap if: env.IS_RELEASE == 'true' uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: repository: RenaCode/homebrew-tap - token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + token: ${{ steps.tap-token.outputs.token }} path: homebrew-tap - name: Push cask to tap diff --git a/packaging/README.md b/packaging/README.md index 36ce741..83b937a 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -80,15 +80,21 @@ rm -rf "$work" Do this **once**. A new certificate is a new identity: every Mac would have to grant Full Disk Access again. -### 2. The tap repository and `HOMEBREW_TAP_TOKEN` +### 2. The tap repository and the `renacode-tap` app 1. Create the public repository `RenaCode/homebrew-tap` (empty is fine; the workflow creates `Casks/`). -2. Create a fine-grained token limited to that repository with - *Contents: Read and write*, and store it: - `gh secret set HOMEBREW_TAP_TOKEN -R RenaCode/CloudMachine` +2. Create a GitHub App in the RenaCode organization (no webhook, Repository + permissions *Contents: Read and write*), install it on `homebrew-tap` only, + and store its client ID and private key: + `gh variable set TAP_APP_CLIENT_ID -R RenaCode/CloudMachine --body ` + `gh secret set TAP_APP_KEY -R RenaCode/CloudMachine < key.pem` -Without the token the release is still published, but the job fails and + The release job turns the key into a one-hour token for `homebrew-tap` + (`actions/create-github-app-token`), so there is no token to renew. The app + is `renacode-tap` (since 2026-10-23; before that a fine-grained PAT). + +Without the app the release is still published, but the job fails and prints the cask, so a missing tap update cannot go unnoticed. ## What the cask does and deliberately does not do