Skip to content

feat(cli): package acpctl for Homebrew via GoReleaser - #449

Draft
jeremyeder wants to merge 4 commits into
openshift-online:mainfrom
jeremyeder:feat/acpctl-homebrew
Draft

jeremyeder wants to merge 4 commits into
openshift-online:mainfrom
jeremyeder:feat/acpctl-homebrew

Conversation

@jeremyeder

Copy link
Copy Markdown
Collaborator

What

Adds an automated Homebrew release pipeline for acpctl. Pushing a vX.Y.Z
tag cross-compiles binaries, publishes a GitHub Release, and updates the
Homebrew formula in openshift-online/homebrew-tap:

brew install openshift-online/tap/acpctl

Prebuilt binary — no Go toolchain needed at install time. macOS and Linux,
amd64 + arm64.

How it works

v* tag push → .github/workflows/acpctl-release.yml runs the OSS GoReleaser,
which builds from components/ambient-cli, creates the Release, and pushes the
generated formula to the tap.

Design choices (and why)

  • OSS GoReleaser only. The monorepo subdir is handled with builds[].dir,
    not the Pro-only monorepo: block.
  • No third-party Action. Installs the pinned OSS goreleaser binary directly
    rather than goreleaser/goreleaser-action, minimizing supply-chain surface.
  • Formula, not cask. GoReleaser deprecated brews in favor of the macOS-only
    homebrew_casks; we keep the formula so brew install still works on Linux.
    Revisit before GoReleaser v3.
  • Plain vX.Y.Z tags. No other component uses git semver tags (containers
    release via image tags), so a vX.Y.Z tag is by convention the acpctl release.
  • App token for the tap push. Short-lived token via
    actions/create-github-app-token, no long-lived PATs.
  • ldflags target the Go module path (github.com/ambient-code/platform/...,
    matching go.mod/Makefile), not the repo URL. This is a linker-symbol path;
    changing it silently breaks acpctl version.

Required before the first upstream release

  1. Install a GitHub App with Contents: write on openshift-online/homebrew-tap;
    set repo secrets HOMEBREW_TAP_APP_ID and HOMEBREW_TAP_APP_PRIVATE_KEY.
  2. Leave HOMEBREW_TAP_OWNER unset (defaults to openshift-online).

See components/ambient-cli/RELEASING.md.

Validation

Cut a real v0.1.0 on my fork against a personal tap:

  • Release + 4 platform archives + checksums.
  • Formula pushed to jeremyeder/homebrew-tap.
  • brew install jeremyeder/tap/acpctlacpctl version reports Client: 0.1.0.

Validated with a PAT on the fork; the upstream App-token form is documented but
not yet exercised end-to-end.

Files

  • components/ambient-cli/.goreleaser.yaml
  • .github/workflows/acpctl-release.yml
  • components/ambient-cli/RELEASING.md
  • components/ambient-cli/README.md

Add an automated release pipeline for the acpctl CLI:

- components/ambient-cli/.goreleaser.yaml: GoReleaser v2 config building
  darwin/linux amd64+arm64 binaries and generating a Homebrew formula.
  Uses builds[].dir for the monorepo subdir (OSS-compatible; not Pro
  monorepo mode). Keeps a formula (not macOS-only cask) so brew install
  works on Linux too. Tap owner is parameterized via HOMEBREW_TAP_OWNER
  (defaults to openshift-online).
- .github/workflows/acpctl-release.yml: on `v*` tag push, runs
  `goreleaser release`, publishing a GitHub Release and pushing the
  formula to <owner>/homebrew-tap.
- components/ambient-cli/RELEASING.md: release procedure + required
  secret/variable wiring and token guidance.
- README: add `brew install openshift-online/tap/acpctl` instructions.

ldflags target the Go module path (github.com/ambient-code/platform/...),
matching go.mod and the Makefile, so `acpctl version` reports the real
version.
Replace the stored HOMEBREW_TAP_GITHUB_TOKEN PAT with a short-lived token
minted at runtime by actions/create-github-app-token, per repo CI/CD
conventions (no long-lived PATs). Update RELEASING.md to document the App
setup (HOMEBREW_TAP_APP_ID + HOMEBREW_TAP_APP_PRIVATE_KEY).
Same rendered value; avoids naive quoted-string secret scanners flagging
a GoReleaser env-var template reference as a literal secret.
@jsell-rh

Copy link
Copy Markdown
Collaborator

🤖 Amber Analysis — PR #449: feat(cli): package acpctl for Homebrew via GoReleaser

Good overall structure — the App-token tap pattern is correct, no long-lived PATs, no pull_request_target, and the design rationale in RELEASING.md is thorough. Two issues block approval, both security-related.

Root Cause: The new workflow (acpctl-release.yml) introduces a CI supply-chain posture that diverges from the project's established standard.


Blockers

1. Action versions must be SHA-pinned (Security — Blocker)

The existing production workflow (prod-release-deploy.yaml) pins actions to full commit SHAs (e.g., actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0). This PR uses mutable tags instead:

- uses: actions/checkout@v4          # ❌ mutable
- uses: actions/setup-go@v5          # ❌ mutable
- uses: actions/create-github-app-token@v2  # ❌ mutable

This workflow has contents: write (creates GitHub Releases) and holds a short-lived tap token — a compromised or tag-hijacked action here could push malicious binaries to the release or the Homebrew tap. SHA-pin all three actions to match the project standard.

2. go install from the internet at build time (Security — Blocker)

- name: Install GoReleaser (OSS)
  run: go install github.com/goreleaser/goreleaser/v2@v2.17.1

This fetches and compiles an external binary during the release job. Even with a pinned version, the module proxy can serve different content than expected across runs, and the build step runs in a context that has GITHUB_TOKEN (write) and the tap token in env. Prefer one of:

  • Download the prebuilt binary from GitHub Releases with a verified SHA-256 checksum, or
  • Use the official goreleaser/goreleaser-action pinned to a SHA (trade-off: adds the Action as a dep, but it ships the binary pre-verified).

The PR body explicitly avoids goreleaser-action to minimize supply-chain surface — but go install at runtime isn't a meaningful improvement when the module proxy is a trust boundary too.


Warnings (Non-blocking)

W1. fetch-depth: 0 on every tag push
GoReleaser requires full history/tags for its changelog — this is correct and intentional. Noting it so future reviewers don't flag it.

W2. brews deprecation
RELEASING.md already documents that brews is deprecated in favor of homebrew_casks (macOS-only). The trade-off for Linux support is understood and documented — revisit before GoReleaser v3.


Confidence

[85%] Medium-High — the design is solid; the two blockers are mechanical (SHA pinning + binary provenance) with clear fixes. Happy to re-review once addressed.


Rollback

Purely additive changes (new files only). Rolling back: git revert <sha> removes the workflow and goreleaser config without impact on other components.


— Amber

@jsell-rh

Copy link
Copy Markdown
Collaborator

🤖 Amber Analysis — PR #449: feat(cli): package acpctl for Homebrew via GoReleaser

Good overall structure — the App-token tap pattern is correct, no long-lived PATs, no pull_request_target, and the design rationale in RELEASING.md is thorough. Two issues block approval, both security-related.

Root Cause: The new workflow (acpctl-release.yml) introduces a CI supply-chain posture that diverges from the project's established standard.


Blockers

1. Action versions must be SHA-pinned (Security — Blocker)

The existing production workflow (prod-release-deploy.yaml) pins actions to full commit SHAs (e.g., actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0). This PR uses mutable tags instead:

- uses: actions/checkout@v4          # ❌ mutable
- uses: actions/setup-go@v5          # ❌ mutable
- uses: actions/create-github-app-token@v2  # ❌ mutable

This workflow has contents: write (creates GitHub Releases) and holds a short-lived tap token — a compromised or tag-hijacked action here could push malicious binaries to the release or the Homebrew tap. SHA-pin all three actions to match the project standard.

2. go install from the internet at build time (Security — Blocker)

- name: Install GoReleaser (OSS)
  run: go install github.com/goreleaser/goreleaser/v2@v2.17.1

This fetches and compiles an external binary during the release job. Even with a pinned version, the module proxy can serve different content than expected across runs, and the build step runs in a context that has GITHUB_TOKEN (write) and the tap token in env. Prefer one of:

  • Download the prebuilt binary from GitHub Releases with a verified SHA-256 checksum, or
  • Use the official goreleaser/goreleaser-action pinned to a SHA (trade-off: adds the Action as a dep, but it ships the binary pre-verified).

The PR body explicitly avoids goreleaser-action to minimize supply-chain surface — but go install at runtime isn't a meaningful improvement when the module proxy is a trust boundary too.


Warnings (Non-blocking)

W1. fetch-depth: 0 on every tag push
GoReleaser requires full history/tags for its changelog — this is correct and intentional. Noting it so future reviewers don't flag it.

W2. brews deprecation
RELEASING.md already documents that brews is deprecated in favor of homebrew_casks (macOS-only). The trade-off for Linux support is understood and documented — revisit before GoReleaser v3.


Confidence

[85%] Medium-High — the design is solid; the two blockers are mechanical (SHA pinning + binary provenance) with clear fixes. Happy to re-review once addressed.


Rollback

Purely additive changes (new files only). Rolling back: git revert <sha> removes the workflow and goreleaser config without impact on other components.


— Amber

1 similar comment
@jsell-rh

Copy link
Copy Markdown
Collaborator

🤖 Amber Analysis — PR #449: feat(cli): package acpctl for Homebrew via GoReleaser

Good overall structure — the App-token tap pattern is correct, no long-lived PATs, no pull_request_target, and the design rationale in RELEASING.md is thorough. Two issues block approval, both security-related.

Root Cause: The new workflow (acpctl-release.yml) introduces a CI supply-chain posture that diverges from the project's established standard.


Blockers

1. Action versions must be SHA-pinned (Security — Blocker)

The existing production workflow (prod-release-deploy.yaml) pins actions to full commit SHAs (e.g., actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0). This PR uses mutable tags instead:

- uses: actions/checkout@v4          # ❌ mutable
- uses: actions/setup-go@v5          # ❌ mutable
- uses: actions/create-github-app-token@v2  # ❌ mutable

This workflow has contents: write (creates GitHub Releases) and holds a short-lived tap token — a compromised or tag-hijacked action here could push malicious binaries to the release or the Homebrew tap. SHA-pin all three actions to match the project standard.

2. go install from the internet at build time (Security — Blocker)

- name: Install GoReleaser (OSS)
  run: go install github.com/goreleaser/goreleaser/v2@v2.17.1

This fetches and compiles an external binary during the release job. Even with a pinned version, the module proxy can serve different content than expected across runs, and the build step runs in a context that has GITHUB_TOKEN (write) and the tap token in env. Prefer one of:

  • Download the prebuilt binary from GitHub Releases with a verified SHA-256 checksum, or
  • Use the official goreleaser/goreleaser-action pinned to a SHA (trade-off: adds the Action as a dep, but it ships the binary pre-verified).

The PR body explicitly avoids goreleaser-action to minimize supply-chain surface — but go install at runtime isn't a meaningful improvement when the module proxy is a trust boundary too.


Warnings (Non-blocking)

W1. fetch-depth: 0 on every tag push
GoReleaser requires full history/tags for its changelog — this is correct and intentional. Noting it so future reviewers don't flag it.

W2. brews deprecation
RELEASING.md already documents that brews is deprecated in favor of homebrew_casks (macOS-only). The trade-off for Linux support is understood and documented — revisit before GoReleaser v3.


Confidence

[85%] Medium-High — the design is solid; the two blockers are mechanical (SHA pinning + binary provenance) with clear fixes. Happy to re-review once addressed.


Rollback

Purely additive changes (new files only). Rolling back: git revert <sha> removes the workflow and goreleaser config without impact on other components.


— Amber

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants