AikidoSec-safe-chain/docs/homebrew.md
Ethan Setnik e976d100f3 Add Homebrew tap for safe-chain (closes #372)
Adds a publish-homebrew job to the release workflow that renders
Formula/safe-chain.rb from a template (substituting the released
version + per-platform SHA256s parsed from the install script asset)
and pushes it to AikidoSec/homebrew-tap on every non-prerelease.

Users can then install via:

  brew install AikidoSec/tap/safe-chain
  safe-chain setup

The formula downloads the existing prebuilt single-file binaries
from the GitHub release (the same ones the install script uses),
so there is no extra build work in this pipeline.

One-time maintainer setup (creating the AikidoSec/homebrew-tap repo
and adding HOMEBREW_TAP_TOKEN as a secret on safe-chain) is documented
in docs/homebrew.md.

Tested locally on macOS arm64 with Homebrew 5.1.11:
  - brew style: 0 offenses
  - brew install --build-from-source: success
  - brew test: 2 assertions pass (--version + help)
  - brew audit --new: 0 offenses

This PR addresses item 1 of #372 (Homebrew only). The integrity-check
piece in item 2 has already shipped — install-safe-chain.sh already
calls verify_checksum() against the baked-in SHA256s. winget and
Chocolatey are not in scope here; see docs/homebrew.md for notes on
why they belong in separate PRs.
2026-05-15 11:36:57 -04:00

114 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Homebrew Tap
End-user install instructions live in the top-level [`README.md`](../README.md#macos--linux-homebrew). This page is for maintainers: how the tap is wired up and what one-time setup is required.
## Architecture
```
AikidoSec/safe-chain AikidoSec/homebrew-tap
┌──────────────────────────┐ ┌──────────────────────────┐
│ tag push (vX.Y.Z) │ │ Formula/ │
│ → create-binaries │ │ safe-chain.rb │
│ → publish-binaries │ │ ^ │
│ (draft release + │ │ │ │
│ install-*.sh with │ │ │ │
│ baked SHA256s) │ │ │ git push │
│ │ │ │ on each release │
│ release.published │ ────────► │ │ │
│ → publish-homebrew │ │ │
│ → publish-npm │ │ │
└──────────────────────────┘ └──────────────────────────┘
```
The `publish-homebrew` job in [`.github/workflows/build-and-release.yml`](../.github/workflows/build-and-release.yml) fires on `release.published` (i.e. when a maintainer publishes the draft release that `publish-binaries` created on the tag push). It:
1. Downloads the release's `install-safe-chain.sh` asset (already has per-platform SHA256s baked in by the earlier `publish-binaries` step — see lines 7686 of the workflow).
2. Parses the four SHA256s it needs (macOS x64/arm64, Linux x64/arm64) out of the install script.
3. Renders `Formula/safe-chain.rb` from a quoted heredoc template, substituting the new version and SHAs.
4. Pushes the rendered formula to the `main` branch of `AikidoSec/homebrew-tap`.
Prereleases are skipped (`if: ... && !github.event.release.prerelease`) so beta tags like `0.0.1-sha256-in-installer-beta` don't update the stable tap.
## One-time setup
This needs to happen exactly once. Until it's done, the `publish-homebrew` job will fail on the next release with a 404 on the tap-repo checkout (which is harmless — it doesn't block npm publish — but the job will appear red).
### 1. Create the tap repository
Create a new public repository under the AikidoSec organisation called **`homebrew-tap`** (the `homebrew-` prefix is required by `brew tap`; users will run `brew tap AikidoSec/tap` which expands to `AikidoSec/homebrew-tap`).
```
Owner: AikidoSec
Repository: homebrew-tap
Visibility: Public
Initialize: Yes (add a README — anything will do, the workflow only touches Formula/safe-chain.rb)
Default branch: main
```
No further repo configuration is required. The `Formula/` directory will be created by the workflow on the first publish.
### 2. Create the token used by the workflow
The workflow needs to push to `AikidoSec/homebrew-tap` from `AikidoSec/safe-chain`. The default `GITHUB_TOKEN` is scoped to the safe-chain repo only and cannot push cross-repo, so we need a token that grants write access to the tap repo.
**Recommended:** a fine-grained personal access token from a maintenance bot account (or a deploy-token-style PAT), scoped to `AikidoSec/homebrew-tap` only.
- Resource owner: `AikidoSec`
- Repository access: **Only select repositories**`AikidoSec/homebrew-tap`
- Repository permissions: **Contents: Read and write**
- Expiration: 1 year (set a calendar reminder; expired tokens silently break releases)
Alternative: a GitHub App installation with `contents:write` on the tap repo and a step that exchanges the App's private key for an installation token. More secure for an org-owned automation but more setup. The PAT path is fine for a low-blast-radius tap.
### 3. Add the token as a secret on `AikidoSec/safe-chain`
Settings → Secrets and variables → Actions → New repository secret:
- Name: `HOMEBREW_TAP_TOKEN`
- Value: the PAT (or App installation token) from step 2
That's it. The next release published from `main` will populate `Formula/safe-chain.rb` in the tap.
### 4. (Optional) Bootstrap with the current release
If you want users to be able to `brew install AikidoSec/tap/safe-chain` immediately rather than waiting for the next release, manually trigger the workflow against the current tag:
```sh
gh workflow run "Create Release" --repo AikidoSec/safe-chain --ref <current-tag>
```
Or simply cut a patch release; the next normal release will populate the tap.
## How users install
```sh
brew install AikidoSec/tap/safe-chain
safe-chain setup
```
`brew install` downloads the prebuilt platform-specific binary from the GitHub release (verified against the SHA256 in the formula) and places it on PATH as `safe-chain`. The user then runs `safe-chain setup` (or `safe-chain setup-ci`) to install the shell aliases that wrap `npm`, `yarn`, `pnpm`, `pip`, etc. This second step is the same as the one in the `curl | sh` install path; Homebrew doesn't (and shouldn't) modify user shell rc files at install time.
## How users upgrade
```sh
brew upgrade safe-chain
```
The formula's `livecheck` block uses GitHub's `releases/latest` strategy, so `brew outdated` and Homebrew's auto-bump tooling pick up new versions automatically once the tap repo has been updated by the release workflow.
## Testing the formula locally
```sh
brew tap AikidoSec/tap
brew install --build-from-source AikidoSec/tap/safe-chain
brew test AikidoSec/tap/safe-chain
brew style $(brew --repository)/Library/Taps/aikidosec/homebrew-tap/Formula/safe-chain.rb
brew audit --new --formula AikidoSec/tap/safe-chain
```
The PR that wired this up (#TBD) validated all four of these against the 1.5.3 release on macOS arm64 (Homebrew 5.1.11).
## Future work
- **Submission to `homebrew-core`:** A custom tap is the lowest-friction path. If we ever want `brew install safe-chain` (no tap prefix) to work, the formula needs to be submitted to Homebrew's central `homebrew-core` repo. That requires the project to meet [Homebrew's notability criteria](https://docs.brew.sh/Acceptable-Formulae) and the formula to build from source (no prebuilt binaries) — which would mean either compiling the Node + pkg bundling in-formula or rewriting the wrapper in a compiled language. Not in scope right now.
- **winget and Chocolatey:** Also requested in [#372](https://github.com/AikidoSec/safe-chain/issues/372). Each has its own manifest format and release-time automation; they should be separate PRs.