diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b741fc..1ea7a6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -144,3 +144,44 @@ jobs: dist/Capsule-*.zip dist/appcast.xml generate_release_notes: true + + # ── Keep the Homebrew tap current ───────────────────────────────────────── + - name: Determine Homebrew tap availability + id: tap + env: + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + run: | + # A PAT (Contents: read+write) scoped to capsule-native/homebrew-tap — the job's own + # GITHUB_TOKEN can only write to *this* repo. Absent on forks → skip cleanly, never fail. + if [ -n "$TAP_TOKEN" ]; then + echo "enabled=true" >> "$GITHUB_OUTPUT" + else + echo "enabled=false" >> "$GITHUB_OUTPUT" + echo "::warning::HOMEBREW_TAP_TOKEN absent — skipping the Homebrew cask bump." + fi + + - name: Bump the Homebrew cask + if: steps.signing.outputs.enabled == 'true' && steps.tap.outputs.enabled == 'true' && startsWith(github.ref, 'refs/tags/') + env: + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + TAP_REPO: capsule-native/homebrew-tap + run: | + # Version comes from the app, not the tag (the tag guard already proved they agree). + version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' App/Info.plist)" + dmg="dist/Capsule-${version}.dmg" + [ -f "$dmg" ] || { echo "::error::expected $dmg after 'make release'"; exit 1; } + + work="$(mktemp -d)" + git clone --depth 1 "https://x-access-token:${TAP_TOKEN}@github.com/${TAP_REPO}.git" "$work" + Scripts/release/update-homebrew-cask.sh "$version" "$dmg" > "$work/Casks/capsule.rb" + + cd "$work" + if git diff --quiet -- Casks/capsule.rb; then + echo "Homebrew cask already at ${version} — nothing to bump." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -aqm "Update capsule cask to ${version}" + git push origin HEAD:main + echo "Bumped the Homebrew cask → ${version}." diff --git a/Scripts/release/README.md b/Scripts/release/README.md index dfe773d..5407e4b 100644 --- a/Scripts/release/README.md +++ b/Scripts/release/README.md @@ -18,6 +18,13 @@ unsandboxed to drive the `container` CLI). **** — the app's `SUFeedURL`. It runs on a schedule (~15 min) and on demand (Actions ▸ Sync appcast ▸ Run workflow) for an instant publish right after a release. +4. The release job also **bumps the Homebrew cask**: it recomputes the DMG's SHA-256, renders + `Casks/capsule.rb` via [`update-homebrew-cask.sh`](update-homebrew-cask.sh), and pushes it to + [`capsule-native/homebrew-tap`](https://github.com/capsule-native/homebrew-tap) so that + `brew install --cask capsule-native/tap/capsule` serves the new version. Gated on the + `HOMEBREW_TAP_TOKEN` secret (a fine-grained PAT with **Contents: read+write** on the tap); + absent on forks, it's skipped with a warning. `auto_updates true` means *existing* installs are + updated in place by Sparkle regardless — the bump keeps **fresh** installs current. > The code repo stays **public** so Sparkle can download the update zip from the release assets > without authentication. diff --git a/Scripts/release/update-homebrew-cask.sh b/Scripts/release/update-homebrew-cask.sh new file mode 100755 index 0000000..5b63b1b --- /dev/null +++ b/Scripts/release/update-homebrew-cask.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# +# update-homebrew-cask.sh +# Capsule +# +# Copyright © 2026 Capsule. All rights reserved. +# +# Renders the Homebrew cask for a released build to stdout, pinning the SHA-256 of the release +# DMG. Pure and network-free: the CI release job redirects this into a checkout of +# capsule-native/homebrew-tap and pushes the result (see .github/workflows/release.yml). Keeping +# the template here makes the main repo the single source of truth for the cask. +# +# Usage: update-homebrew-cask.sh +# e.g. update-homebrew-cask.sh 0.1.0 dist/Capsule-0.1.0.dmg + +set -euo pipefail + +version="${1:?usage: update-homebrew-cask.sh }" +dmg="${2:?usage: update-homebrew-cask.sh }" + +[ -f "$dmg" ] || { echo "update-homebrew-cask.sh: missing DMG: $dmg" >&2; exit 1; } + +sha256="$(shasum -a 256 "$dmg" | awk '{print $1}')" + +# The `#{version}` tokens below are Ruby interpolation for Homebrew and must survive verbatim; +# only ${version}/${sha256} are expanded by the shell here. +cat <