ci(release): sign macOS and Windows binaries#5
Conversation
Darwin: codesign with Developer ID Application, notarize archives via xcrun notarytool after GoReleaser builds but before upload. Keychain is created per-run from APPLE_DISTRIBUTION_PKCS12 and discarded with the runner. Windows: Azure Trusted Signing via Microsoft's `sign` dotnet tool under the shared HPT "Elevate" profile. Linux: intentionally unsigned — distros don't consume Authenticode-style sigs; the sha256 in checksums.txt is the integrity anchor. Release workflow moves to macos-14 so codesign and notarytool are available; linux/windows binaries still cross-compile via CGO_ENABLED=0. goreleaser runs with --skip=publish so archives exist on disk for the notarize step; a follow-up gh release upload attaches the signed, notarized artifacts to the release release-please already created.
📝 WalkthroughWalkthroughAdds platform-specific binary signing and macOS archive notarization to the release pipeline, integrates signing as a GoReleaser post-build hook, and updates the GitHub Actions release workflow to run on macOS with steps for certificate import, Windows signing tooling, notarization, and manual artifact upload. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor GH_Actions as "GitHub Actions (release)"
participant GoReleaser as "GoReleaser"
participant SignHook as ".github/scripts/sign-binary.sh"
participant macOS_Signer as "codesign (macOS)"
participant Win_Signer as "sign (Azure Trusted Signing)"
participant Archiver as "GoReleaser archiver"
participant Notarizer as ".github/scripts/notarize-archive.sh"
participant Apple_Notary as "xcrun notarytool"
participant GH_Release as "gh release (upload)"
GH_Actions->>GoReleaser: run goreleaser (--skip=publish)
GoReleaser->>SignHook: invoke post-build hook ({{ .Path }}, {{ .Os }})
SignHook-->>macOS_Signer: codesign (darwin) / verify
SignHook-->>Win_Signer: sign CLI (windows) / verify
SignHook-->>SignHook: no-op (linux)
SignHook-->>GoReleaser: return signed artifact
GoReleaser->>Archiver: create archives in dist/
GH_Actions->>Notarizer: loop dist/*darwin*.tar.gz -> run notarize-archive.sh
Notarizer->>Apple_Notary: xcrun notarytool submit --wait
Apple_Notary-->>Notarizer: notarization result
GH_Actions->>GH_Release: gh release upload dist/* --clobber
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/scripts/notarize-archive.sh:
- Around line 10-16: Validate the archive input before calling xcrun: check that
the positional parameter assigned to the variable archive is non-empty and that
the file exists/readable, and if not emit a clear error to stderr (including the
provided archive value or lack thereof) and exit with a non-zero status; update
the top of the script near the archive="$1" binding (and before the xcrun
notarytool submit invocation) to perform these checks so the call to xcrun
notarytool submit --apple-id "$APPLE_ID" --password
"$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait only runs when
archive is valid.
In @.github/scripts/sign-binary.sh:
- Line 14: The script assigns arch with "path=\"$1\" os=\"$2\" arch=\"$3\"" but
never uses arch; either remove the arch assignment (change to "path=\"$1\"
os=\"$2\"" and update .goreleaser.yml to stop passing {{ .Arch }}) or
intentionally use it (e.g., include arch in logging/debug output or add a clear
comment explaining it's reserved for future use) — edit sign-binary.sh to
implement one of these fixes and adjust any callers (like .goreleaser.yml) if
you remove the parameter.
In @.github/workflows/release.yml:
- Line 56: The unquoted command substitution in the security command can cause
word-splitting for keychain paths with spaces; update the invocation of security
in the workflow so the substitution is quoted (i.e., change the arguments to use
"$(security list-keychains -d user | tr -d '"')" or an equivalent that preserves
spaces) when calling security list-keychains -d user -s "$KEYCHAIN" $(security
list-keychains -d user | tr -d '"'); ensure you preserve the existing tr -d '"'
processing but wrap the substitution in double quotes to prevent splitting.
- Around line 79-88: Remove APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and
APPLE_TEAM_ID from the env block passed to the GoReleaser step since
sign-binary.sh (invoked by the GoReleaser job) doesn't use them for codesign;
instead, set those three Apple-related secrets only in the step that runs
notarize-archive.sh (or the notarization job) and update the inline comment
(currently "Apple — consumed by sign-binary.sh") to accurately state they are
used by notarize-archive.sh/notarization, leaving codesign to rely on the
keychain identity.
- Around line 112-118: The artifacts array may be empty (with nullglob on)
causing gh release upload to fail; modify the script around the artifacts
declaration and the gh release upload call to first check the array length
(e.g., if [ ${`#artifacts`[@]} -gt 0 ]; then ... ) and only invoke gh release
upload "$GITHUB_REF_NAME" "${artifacts[@]}" --clobber when there are artifacts,
otherwise skip or log a message; reference the artifacts array and the gh
release upload invocation when making this change.
- Around line 67-68: The workflow step "Install sign CLI" currently installs a
prerelease pinned version via the command `dotnet tool install --global sign
--prerelease --version "0.9.1-beta.26179.1"`, which is risky for production;
either change this step to install a stable, non-prerelease `sign` version
(remove `--prerelease` and pin a released version) or add an inline comment and
a documented justification in the repo explaining exactly why this prerelease is
required (including link to the issue/PR or feature that necessitates it) and
add a TODO to revert to stable once a release is available so reviewers can
locate and validate the rationale.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Lite
Run ID: 8f3217d5-9626-41bd-9cff-6e18f3543b05
📒 Files selected for processing (4)
.github/scripts/notarize-archive.sh.github/scripts/sign-binary.sh.github/workflows/release.yml.goreleaser.yml
- Quote `security list-keychains` subshell to survive spaces in paths.
- Drop APPLE_* from GoReleaser step env; codesign resolves identity from
the imported keychain, not env vars. They stay on the notarize step.
- Drop unused `arch` parameter from sign-binary.sh; prune {{ .Arch }}
from the hook template.
- Guard `gh release upload` against empty artifact glob with explicit
error rather than a confusing gh error.
- Document why `sign` is pinned to a beta (no stable tag ships).
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)
25-27:⚠️ Potential issue | 🟡 MinorAlign Go version with project configuration.
The workflow uses
go-version: "1.25", but the project'sgo.modandDockerfilespecify Go 1.23. Ensure the release workflow version matches the project's configured Go version.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 25 - 27, Update the release workflow to use the project's configured Go version: change the actions/setup-go step (uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff) so the go-version input matches Go 1.23 instead of "1.25" (update the go-version: "1.25" value to "1.23"), ensuring consistency with go.mod and the Dockerfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 25-27: Update the release workflow to use the project's configured
Go version: change the actions/setup-go step (uses:
actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff) so the go-version
input matches Go 1.23 instead of "1.25" (update the go-version: "1.25" value to
"1.23"), ensuring consistency with go.mod and the Dockerfile.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Lite
Run ID: a77b53b1-6942-4ace-ab70-6013f7abcc44
📒 Files selected for processing (3)
.github/scripts/sign-binary.sh.github/workflows/release.yml.goreleaser.yml
Summary
macos-14socodesignandxcrun notarytoolare available; linux/windows binaries still cross-compile viaCGO_ENABLED=0.--skip=publish; notarization runs againstdist/*darwin*.tar.gz; a follow-upgh release uploadattaches signed artifacts to the release release-please already created.Secrets consumed
APPLE_DISTRIBUTION_PKCS12,APPLE_DISTRIBUTION_PKCS12_PASSPHRASE— keychain importAPPLE_ID,APPLE_APP_SPECIFIC_PASSWORD,APPLE_TEAM_ID— notarytoolAZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET—signCLISame secrets already used by viewas-app/elevate-app.
Test plan
spctl -a -vvaftertar xzf.exehas a valid Authenticode sig viasigntool verify /paSummary by CodeRabbit