Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions packaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <client id>`
`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
Expand Down
Loading