Files
amethyst/scripts/bump-homebrew-cask.sh
Vitor PamplonaandClaude Opus 5.5 cbb5dbe1d4 docs(release): treat Homebrew as autobumped; cask script verifies instead of submitting
The amethyst-nostr cask and the amy formula are on Homebrew's autobump
list, so BrewTestBot opens their version PRs and `brew bump-cask-pr`
refuses to open a competing one (it errored on v1.17.0).

scripts/bump-homebrew-cask.sh keeps re-verifying the live DMG's sha256
and stapled notarization (BrewTestBot checks neither against ours), then
finds BrewTestBot's PR and fails if its sha256 differs. No token is
needed. It falls back to `brew bump-cask-pr` only if the cask leaves
autobump.

RELEASE_OPS.md, BUILDING.md and the sync workflows now describe that
flow, drop the per-release HOMEBREW_GITHUB_API_TOKEN step, and record
that winget is live (initial PR merged 2026-09-23).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:10:16 -04:00

145 lines
6.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Verify the amethyst-nostr release DMG and check that Homebrew has picked it up.
#
# amethyst-nostr is on homebrew-cask's AUTOBUMP list: BrewTestBot opens the
# version-bump PR itself, every ~3 hours, and `brew bump-cask-pr` refuses to
# open a competing one. So for an autobumped cask this script opens nothing. It
# re-verifies the live DMG (sha256 + stapled notarization — BrewTestBot computes
# its own sha256 and never checks notarization), finds BrewTestBot's PR for the
# version, and checks that the sha256 in it matches ours. No token is needed.
#
# Only if Homebrew ever takes the cask off autobump does this fall back to
# opening the PR with `brew bump-cask-pr`, which forks homebrew-cask into the
# token owner's account and so needs a CLASSIC PAT with the `repo` scope. That
# scope grants write to every repository the account can reach, so it stays in
# a maintainer's shell rather than a CI secret. See BUILDING.md § Homebrew cask.
#
# Everything error-prone (version, sha256, notarization check) is already done
# in CI by .github/workflows/bump-homebrew.yml, which opens a PR syncing
# desktopApp/packaging/homebrew/amethyst-nostr.rb. Merge that PR first; this
# script reads the merged values so the two can never disagree.
#
# Usage:
# scripts/bump-homebrew-cask.sh # uses the cask file as-is
# scripts/bump-homebrew-cask.sh v1.13.2 # asserts the cask matches this tag
# DRY_RUN=1 scripts/bump-homebrew-cask.sh # print what would happen, do nothing
# # (the autobump path never writes anything)
#
# Requires: macOS and curl; `gh` to look up BrewTestBot's PR. The fallback path
# (cask not autobumped) also needs brew and HOMEBREW_GITHUB_API_TOKEN (classic
# PAT, `repo` scope):
# https://github.com/settings/tokens/new?scopes=repo&description=Homebrew%20cask%20bump
set -euo pipefail
REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
CASK="$REPO_ROOT/desktopApp/packaging/homebrew/amethyst-nostr.rb"
EXPECTED_TAG="${1:-}"
DRY_RUN="${DRY_RUN:-}"
die() { echo "error: $*" >&2; exit 1; }
[[ "$(uname -s)" == "Darwin" ]] || die "casks are macOS-only; run this on a Mac"
[[ -f "$CASK" ]] || die "cask not found at $CASK"
VERSION=$(grep -E '^ version "' "$CASK" | sed -E 's/.*"(.*)".*/\1/')
SHA=$(grep -E '^ sha256 "' "$CASK" | sed -E 's/.*"(.*)".*/\1/')
[[ -n "$VERSION" && -n "$SHA" ]] || die "could not parse version/sha256 out of $CASK"
if [[ -n "$EXPECTED_TAG" && "v$VERSION" != "$EXPECTED_TAG" ]]; then
die "cask is at v$VERSION but you asked for $EXPECTED_TAG.
Merge the 'chore: sync amethyst-nostr cask to $EXPECTED_TAG' PR first, then pull."
fi
URL="https://github.com/vitorpamplona/amethyst/releases/download/v${VERSION}/amethyst-desktop-${VERSION}-macos-arm64.dmg"
echo "cask : amethyst-nostr"
echo "version : $VERSION"
echo "sha256 : $SHA"
echo "url : $URL"
echo
# Re-verify against the live asset. CI already checked this, but the whole point
# of a manual gate is that a human confirms what is about to be advertised to
# every macOS user.
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
echo "==> downloading and verifying the release DMG"
curl -fsSL -o "$TMP/amethyst.dmg" "$URL" || die "could not download $URL"
ACTUAL_SHA=$(shasum -a 256 "$TMP/amethyst.dmg" | awk '{print $1}')
[[ "$ACTUAL_SHA" == "$SHA" ]] || die "sha256 mismatch!
cask says : $SHA
actual : $ACTUAL_SHA"
echo " sha256 matches"
xcrun stapler validate "$TMP/amethyst.dmg" >/dev/null 2>&1 \
|| die "the DMG has NO stapled notarization ticket.
Users would hit a Gatekeeper block. Do not submit this to homebrew-cask.
Check the notarizeReleaseDmg step in .github/workflows/create-release.yml."
echo " notarization ticket stapled"
echo
# Autobumped casks: BrewTestBot owns the PR. Find it and check its sha256.
AUTOBUMP=$(curl -fsSL "https://formulae.brew.sh/api/cask/amethyst-nostr.json" 2>/dev/null \
| python3 -c 'import sys, json; print(str(json.load(sys.stdin).get("autobump")).lower())' 2>/dev/null || echo unknown)
if [[ "$AUTOBUMP" == "true" ]]; then
echo "==> amethyst-nostr is autobumped; looking for BrewTestBot's PR"
command -v gh >/dev/null || die "gh not found — needed to look up the homebrew-cask PR"
PR=$(gh pr list --repo Homebrew/homebrew-cask --state all --search "amethyst-nostr $VERSION in:title" \
--json number,state,title --jq ".[] | select(.title == \"amethyst-nostr $VERSION\") | \"\(.number) \(.state)\"" | head -1)
if [[ -z "$PR" ]]; then
echo " no PR yet. BrewTestBot runs about every 3 hours; check again later:"
echo " https://github.com/Homebrew/homebrew-cask/pulls?q=amethyst-nostr+$VERSION"
exit 0
fi
read -r PR_NUMBER PR_STATE <<<"$PR"
echo " #$PR_NUMBER ($PR_STATE) https://github.com/Homebrew/homebrew-cask/pull/$PR_NUMBER"
PR_SHA=$(gh pr diff "$PR_NUMBER" --repo Homebrew/homebrew-cask | grep -E '^\+ *sha256 "' | sed -E 's/.*"(.*)".*/\1/' | head -1)
if [[ -z "$PR_SHA" ]]; then
echo " could not read a sha256 from the PR diff; compare by hand against $SHA"
elif [[ "$PR_SHA" == "$SHA" ]]; then
echo " sha256 in the PR matches"
else
die "BrewTestBot's PR advertises a different sha256!
PR #$PR_NUMBER : $PR_SHA
release DMG : $SHA
Comment on the PR before it merges."
fi
exit 0
fi
echo "==> amethyst-nostr is not autobumped (autobump=$AUTOBUMP); opening the PR ourselves"
if [[ -n "$DRY_RUN" ]]; then
echo "DRY_RUN set — would now run:"
echo " brew bump-cask-pr amethyst-nostr --version $VERSION --sha256 $SHA --url $URL"
exit 0
fi
command -v brew >/dev/null || die "brew not found"
[[ -n "${HOMEBREW_GITHUB_API_TOKEN:-}" ]] || die "HOMEBREW_GITHUB_API_TOKEN is not set.
Create a CLASSIC PAT with the 'repo' scope:
https://github.com/settings/tokens/new?scopes=repo&description=Homebrew%20cask%20bump
then: export HOMEBREW_GITHUB_API_TOKEN=ghp_...
Prefer a dedicated bot account — 'repo' reaches every repo that account can see."
if ! brew info --cask amethyst-nostr >/dev/null 2>&1; then
cat >&2 <<EOF
error: cask 'amethyst-nostr' does not exist in homebrew-cask yet.
\`brew bump-cask-pr\` can only bump an EXISTING cask. The first submission is a
manual, human-reviewed new-cask PR. See BUILDING.md § Homebrew cask
(one-time initial PR) for that flow; come back to this script for every
release after it merges.
EOF
exit 1
fi
echo "==> opening the homebrew-cask PR"
brew bump-cask-pr amethyst-nostr --version "$VERSION" --sha256 "$SHA" --url "$URL" --no-browse
echo
echo "Done. Watch the PR at https://github.com/Homebrew/homebrew-cask/pulls"