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
44 changes: 8 additions & 36 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,59 +225,31 @@ jobs:
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}

- name: Install quill for macOS signing
run: |
go install github.com/anchore/quill/cmd/quill@v0.7.1
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Prepare signing credentials
env:
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
run: |
umask 077
echo "$MACOS_SIGN_P12" | base64 -d > "$RUNNER_TEMP/codesign.p12"
echo "$MACOS_NOTARY_KEY" | base64 -d > "$RUNNER_TEMP/notary.p8"

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
distribution: goreleaser
version: 'v2.14.1'
# Keep in sync with .mise.toml goreleaser version pin.
version: 'v2.15.4'
install-only: true

- name: Run GoReleaser
env:
CHANGELOG_FILE: ${{ steps.changelog.outputs.file }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ steps.sdk-token.outputs.token }}
QUILL_SIGN_P12: ${{ runner.temp }}/codesign.p12
QUILL_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
run: |
RELEASE_CHANGELOG=""
if [ -n "$CHANGELOG_FILE" ] && [ -f "$CHANGELOG_FILE" ]; then
RELEASE_CHANGELOG=$(cat "$CHANGELOG_FILE")
fi
export RELEASE_CHANGELOG
goreleaser release --clean --skip=notarize

- name: Notarize macOS binaries
env:
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
run: |
for bin in dist/basecamp_darwin_*/basecamp; do
echo "Notarizing $bin..."
quill notarize "$bin" \
--notary-issuer "$MACOS_NOTARY_ISSUER_ID" \
--notary-key-id "$MACOS_NOTARY_KEY_ID" \
--notary-key "$RUNNER_TEMP/notary.p8" \
--wait
done

- name: Clean up signing credentials
if: always()
run: rm -f "$RUNNER_TEMP/codesign.p12" "$RUNNER_TEMP/notary.p8"
goreleaser release --clean

- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
Expand Down
22 changes: 13 additions & 9 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ builds:
- -X github.com/basecamp/basecamp-cli/internal/version.Version={{.Version}}
- -X github.com/basecamp/basecamp-cli/internal/version.Commit={{.Commit}}
- -X github.com/basecamp/basecamp-cli/internal/version.Date={{.Date}}
hooks:
post:
- cmd: scripts/sign-darwin.sh "{{ .Os }}" "{{ .Path }}"
output: true

archives:
- id: default
Expand Down Expand Up @@ -98,13 +94,21 @@ signs:
artifacts: checksum
output: true

# Signing handled by scripts/sign-darwin.sh via build hook using anchore/quill
# CLI directly. The goreleaser/quill fork lacks the TeamIdentifier fix from
# anchore/quill v0.7.0 (https://github.com/anchore/quill/issues/147).
# Notarization runs as a separate workflow step after GoReleaser publishes.
# Sign and notarize macOS binaries (cross-platform via embedded quill)
notarize:
macos:
- enabled: 'false'
- enabled: '{{ and (ne .Env.MACOS_SIGN_P12 "") (ne .Env.MACOS_SIGN_PASSWORD "") (ne .Env.MACOS_NOTARY_KEY "") (ne .Env.MACOS_NOTARY_KEY_ID "") (ne .Env.MACOS_NOTARY_ISSUER_ID "") }}'
ids:
- basecamp
sign:
certificate: "{{.Env.MACOS_SIGN_P12}}"
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
notarize:
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
key: "{{.Env.MACOS_NOTARY_KEY}}"
wait: true
timeout: 20m

changelog:
# Use GitHub's auto-generated release notes (categories configured in .github/release.yml)
Expand Down
2 changes: 2 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[tools]
go = "1.26"
# Keep in sync with .github/workflows/release.yml GoReleaser CLI version pin.
goreleaser = "2.15.4"
28 changes: 1 addition & 27 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ make release VERSION=0.2.0 DRY_RUN=1
- Generates AI changelog from commit history
- Builds binaries for all platforms (darwin, linux, windows, freebsd, openbsd × amd64/arm64)
- Builds `.deb`, `.rpm`, `.apk` Linux packages (amd64 + arm64)
- Signs macOS binaries via build hook (`anchore/quill` CLI), notarizes post-publish (see [tradeoff](#macos-signing-tradeoffs))
- Signs and notarizes macOS binaries via GoReleaser's built-in notarize (embedded quill)
- Signs checksums with cosign (keyless via Sigstore OIDC)
- Generates SBOM for supply chain transparency
- Updates Homebrew cask (`basecamp-cli`) in `basecamp/homebrew-tap`
Expand Down Expand Up @@ -94,29 +94,3 @@ make update-nix-hash
| deb/rpm/apk packages | GitHub Release assets | GoReleaser (nfpm) |
| Nix flake | `flake.nix` in repo | Self-serve (`nix profile install github:basecamp/basecamp-cli`) |
| go install | `go install github.com/basecamp/basecamp-cli/cmd/basecamp@latest` | Go module proxy |

## macOS signing tradeoffs

**Signing** uses `anchore/quill` CLI directly (not GoReleaser's embedded fork)
because the `goreleaser/quill` fork does not populate the `TeamIdentifier` field
in the CodeDirectory ([anchore/quill#147](https://github.com/anchore/quill/issues/147)).
The build hook signs darwin binaries before archiving, so archives, checksums,
and tap manifests all contain correctly signed binaries.

**Notarization** runs after GoReleaser publishes. This means there is a brief
window where the GitHub release and Homebrew/Scoop manifests point at binaries
Apple has not yet accepted. This is accepted release debt, not a bug:
- The binary bytes are final — notarization for bare Mach-O is purely server-side
- GateKeeper checks Apple's servers at runtime, not a stapled ticket
- Bare Mach-O executables cannot be stapled (Apple limitation)
- This matches the previous GoReleaser/quill behavior

The `macos-verify` post-release job runs on a real macOS runner for both amd64
and arm64. TeamIdentifier and hardened runtime are hard assertions that fail
the workflow. The notarization check is best-effort telemetry — ticket
propagation can lag, so it warns rather than gates.

**Reverting**: when `goreleaser/quill` syncs the fix from anchore/quill v0.7.0,
revert to the built-in notarize block: remove the build hook, re-enable
`notarize.macos`, remove the quill install/notarize workflow steps, and delete
`scripts/sign-darwin.sh`.
42 changes: 0 additions & 42 deletions scripts/sign-darwin.sh

This file was deleted.

Loading