Files
momenbasel--puremac/scripts/SECRETS.md
T
wehub-resource-sync 869b84f27c
Build / build (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

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. 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.