158 lines
6.5 KiB
Markdown
158 lines
6.5 KiB
Markdown
# 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`
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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. running `xcodegen` post-sign breaks the
|
|
signature). The pipeline runs `xcodegen` before `archive` and never edits the
|
|
bundle after the export step.
|
|
- Notarizing the `.app` but shipping a `.zip` made before the staple. The
|
|
pipeline staples the `.app` first, then re-zips it. Order matters — Gatekeeper
|
|
on first launch checks the stapled ticket on the `.app`, not the zip.
|
|
- Using `Apple Development` cert (default in `project.yml`) for distribution —
|
|
the pipeline overrides with `Developer ID Application` at archive time.
|
|
- Skipping `--options=runtime` (no hardened runtime → notary rejects). Pipeline
|
|
passes it via `OTHER_CODE_SIGN_FLAGS`.
|
|
- Universal-binary signing race where `lipo` is run after sign. The archive
|
|
step builds universal in one pass via `ARCHS="arm64 x86_64"` so the codesign
|
|
covers both slices atomically.
|