6.5 KiB
Release pipeline secrets
.github/workflows/release.yml requires the following GitHub Actions secrets
on momenbasel/PureMac. All must be set before the next tag push or the
notarize/staple steps will fail and ship a broken signature (the cause of #86).
The pipeline uses an App Store Connect API key for notarization (modern,
no rotation, scoped to one team) instead of the legacy
APPLE_ID + app-specific-password flow.
Required secrets (6)
| Secret | Source | Notes |
|---|---|---|
BUILD_CERTIFICATE_BASE64 |
Filtered Developer ID Application .p12 (cert + matching private key only), base64 |
base64 -i cert.p12 | pbcopy |
P12_PASSWORD |
Password set when exporting the .p12 |
Random — only you and CI need it |
KEYCHAIN_PASSWORD |
Random string | Only used to lock the runner's temp keychain — never leaves CI |
APP_STORE_CONNECT_KEY_ID |
The 10-char ID from the .p8 filename (AuthKey_XXXXXXXXXX.p8) |
e.g. 5G7R52L8RK |
APP_STORE_CONNECT_ISSUER_ID |
UUID from https://appstoreconnect.apple.com/access/integrations/api | e.g. 5de3898a-cd31-4061-850f-ae17b389e46a |
APP_STORE_CONNECT_PRIVATE_KEY |
Full contents of the .p8 file (-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----) |
Paste raw, including the BEGIN/END lines |
Optional secret (1)
| Secret | Source | Notes |
|---|---|---|
HOMEBREW_TAP_TOKEN |
Fine-grained PAT with Contents: read+write on momenbasel/homebrew-tap |
Without this the tap formula bump step is skipped (in-repo homebrew/puremac.rb still bumps via the default GITHUB_TOKEN) |
Extracting your Developer ID cert as a filtered .p12
mkdir -p ~/Desktop/PureMac-secrets && cd ~/Desktop/PureMac-secrets
# 1. Export everything from login keychain
P12_PWD=$(openssl rand -base64 24)
echo "$P12_PWD" > P12_PASSWORD.txt
security export -k login.keychain-db -t identities -f pkcs12 -P "$P12_PWD" -o all.p12
# 2. Dump to PEM, isolate the Developer ID cert + matching private key
openssl pkcs12 -in all.p12 -passin "pass:$P12_PWD" -nodes -out all.pem
security find-certificate -c "Developer ID Application: Moamen Basel" -p login.keychain-db > devid.crt
# 3. Use python to split private keys, then match by modulus
python3 - <<'PY'
import re
content = open("all.pem").read()
for i, k in enumerate(re.findall(r"-----BEGIN PRIVATE KEY-----.*?-----END PRIVATE KEY-----", content, re.DOTALL), 1):
open(f"key_{i}.pem", "w").write(k + "\n")
PY
CERT_MOD=$(openssl x509 -in devid.crt -modulus -noout | shasum -a 256 | awk '{print $1}')
for k in key_*.pem; do
if [[ "$(openssl rsa -in "$k" -modulus -noout 2>/dev/null | shasum -a 256 | awk '{print $1}')" == "$CERT_MOD" ]]; then
cp "$k" devid.key && break
fi
done
# 4. Re-pack as a clean p12 with ONLY Developer ID + key
openssl pkcs12 -export \
-in devid.crt -inkey devid.key \
-name "Developer ID Application: Moamen Basel (H3WXHVTP97)" \
-out PureMac-DeveloperID.p12 \
-passout "pass:$P12_PWD" \
-macalg sha256 -keypbe AES-256-CBC -certpbe AES-256-CBC
# 5. Base64 for the GH secret + scrub intermediates
base64 -i PureMac-DeveloperID.p12 -o PureMac-DeveloperID.p12.b64
rm -P all.p12 all.pem devid.key key_*.pem
Storing the ASC API key locally for notarytool
Already set up — confirmed via xcrun notarytool history --keychain-profile AC_NOTARY.
For reference:
xcrun notarytool store-credentials AC_NOTARY \
--key ~/.appstoreconnect/private_keys/AuthKey_5G7R52L8RK.p8 \
--key-id 5G7R52L8RK \
--issuer 5de3898a-cd31-4061-850f-ae17b389e46a
That keychain profile is consumed by scripts/release-local.sh for emergency
hotfixes. The CI workflow uses raw secrets instead (no keychain dependency on
the runner).
Setting them all via gh CLI
# Fill in the 4 you control:
P12_PWD=$(cat ~/Desktop/PureMac-secrets/P12_PASSWORD.txt)
KC_PWD=$(cat ~/Desktop/PureMac-secrets/KEYCHAIN_PASSWORD.txt)
gh secret set BUILD_CERTIFICATE_BASE64 --repo momenbasel/PureMac < ~/Desktop/PureMac-secrets/PureMac-DeveloperID.p12.b64
gh secret set P12_PASSWORD --repo momenbasel/PureMac --body "$P12_PWD"
gh secret set KEYCHAIN_PASSWORD --repo momenbasel/PureMac --body "$KC_PWD"
gh secret set APP_STORE_CONNECT_KEY_ID --repo momenbasel/PureMac --body "5G7R52L8RK"
gh secret set APP_STORE_CONNECT_ISSUER_ID --repo momenbasel/PureMac --body "5de3898a-cd31-4061-850f-ae17b389e46a"
gh secret set APP_STORE_CONNECT_PRIVATE_KEY --repo momenbasel/PureMac < ~/.appstoreconnect/private_keys/AuthKey_5G7R52L8RK.p8
# Optional:
gh secret set HOMEBREW_TAP_TOKEN --repo momenbasel/PureMac --body "<your fine-grained PAT>"
# Verify:
gh secret list --repo momenbasel/PureMac
After upload:
rm -P ~/Desktop/PureMac-secrets/PureMac-DeveloperID.p12* \
~/Desktop/PureMac-secrets/P12_PASSWORD.txt \
~/Desktop/PureMac-secrets/KEYCHAIN_PASSWORD.txt
Triggering a release
Dry run first (build + sign + notarize, no upload, no homebrew bump):
gh workflow run release.yml --repo momenbasel/PureMac -f version=2.2.0 -f dry_run=true
gh run watch --repo momenbasel/PureMac
Real release (after dry run is green):
git tag v2.2.0
git push origin v2.2.0
What ships
| Artifact | Purpose |
|---|---|
PureMac-X.Y.Z.dmg |
Direct download link in release notes (signed + notarized + stapled) |
PureMac-X.Y.Z.zip |
Source for the homebrew cask (notarized + stapled .app inside) |
Both checksums land in build/CHECKSUMS.md and the GH release body.
Troubleshooting #86 (code or signature have been modified)
Root causes that the pipeline guards against, that the previous manual release path did not:
- Files modified after
codesign(e.g. runningxcodegenpost-sign breaks the signature). The pipeline runsxcodegenbeforearchiveand never edits the bundle after the export step. - Notarizing the
.appbut shipping a.zipmade before the staple. The pipeline staples the.appfirst, then re-zips it. Order matters — Gatekeeper on first launch checks the stapled ticket on the.app, not the zip. - Using
Apple Developmentcert (default inproject.yml) for distribution — the pipeline overrides withDeveloper ID Applicationat archive time. - Skipping
--options=runtime(no hardened runtime → notary rejects). Pipeline passes it viaOTHER_CODE_SIGN_FLAGS. - Universal-binary signing race where
lipois run after sign. The archive step builds universal in one pass viaARCHS="arm64 x86_64"so the codesign covers both slices atomically.