super-productivity/docs/update-mac-certificates.md
2025-11-09 16:35:09 +01:00

327 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Update macOS certificates for electron-builder
Mac access required! The instructions below refresh every asset used by the GitHub Actions/macOS runners for Mac App Store (MAS) and direct-download (DMG) builds.
## Certificates
### 1. Clean up old material
⚠️ Note: Deleting old certificates also removes their private keys. If you dont have the private key backed up, generate a new CSR before creating new certificates (so maybe we should not delete the certificates too early?).
- In <https://developer.apple.com/account/resources/certificates/list>, revoke the expiring certificates so they cannot be downloaded again by accident.
- Remove the matching identities from your local keychain (`Keychain Access → My Certificates` - `open -a "Keychain Access"` ) so you do not export the wrong private key later.
### 2. Create a fresh CSR (once)
1. Open Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…
2. Enter the Apple ID email tied to the team, select “Saved to disk”, and pick a location for `mac-dev-team.csr`.
3. Repeat only if you need a CSR for a different Apple ID/team.
### 3. Generate the required certificates
Create the following certificates in the developer portal, using the CSR from step 2:
- **Apple Development** used for local development/debug builds.
- **Apple Distribution** (Apple renamed “Mac App Distribution”) used to sign the MAS app.
- **Mac Installer Distribution** used to sign the MAS `.pkg` that is uploaded via Transporter.
- **Developer ID Application** used to sign the notarized DMG build.
- **Developer ID Installer** used if you ship a signed installer `.pkg` for DMG distribution (still required by electron-builder when `dist:mac:dl` runs).
Download each resulting `.cer` file and note the exact label Apple shows so you can cross-check in CI logs later.
### 4. Install and export as PKCS#12
1. Double-click every downloaded `.cer` so it lands inside the login keychain under “My Certificates”. You should now see each certificate with a disclosure triangle that reveals the paired private key—if the triangle is missing, delete the cert and regenerate it so the private key attaches properly.
2. Multi-select the identities listed above (adjust if you only target MAS or only Developer ID), right-click → **Export Items…**, save as `all-certs.p12`, and choose a strong password (this password becomes the `MAC_CERTS_PASSWORD`). Confirm the repeated macOS password prompts.
- Alternatively, run:
```bash
security export -k ~/Library/Keychains/login.keychain-db \
-t identities -f pkcs12 -P "$MAC_CERTS_PASSWORD" \
-o all-certs.p12
```
3. Verify you can re-import `all-certs.p12` onto another Mac before proceeding.
### 5. Prepare CI secrets
1. Base64 the exported file: `base64 -i all-certs.p12 -o all-certs.b64`.
2. Update the GitHub Actions secret `MAC_CERTS` with the contents of `all-certs.b64` and the secret `MAC_CERTS_PASSWORD` with the password chosen above.
3. Map those secrets to electron-builders expectations inside your workflow, for example:
```yaml
CSC_LINK: ${{ secrets.MAC_CERTS }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: true
```
Use `CSC_INSTALLER_LINK`/`CSC_INSTALLER_KEY_PASSWORD` if you decide to store installer identities separately.
4. If you prefer keeping separate secrets, repeat the export step per certificate and upload them with names such as `MAC_DISTRIBUTION_CERT`, but the current workflow expects a single bundle. Whatever approach you choose, ensure the CI job imports the PKCS#12 into an unlocked keychain before invoking electron-builder.
## Provisioning profiles
> Important: create/refresh profiles **after** the new certificates exist, otherwise downloading the profile will still pull the revoked certs.
1. Go to <https://developer.apple.com/account/resources/profiles/list>.
2. Create two new profiles:
- **Type “Mac App Store”** → select the `Apple Distribution` certificate → choose the MAS App ID → download as `mas.provisionprofile`.
- **Type “Developer ID Application”** → select the `Developer ID Application` certificate → choose the same App ID → download as `dl.provisionprofile` (optional for most Developer ID apps, but we keep it to satisfy older tooling and entitlements checks).
3. Move the files into `tools/mac-profiles/mas.provisionprofile` and `tools/mac-profiles/dl.provisionprofile` (keep the exact filenames so the build scripts pick them up). If you skip the Developer ID profile, remove `tools/mac-profiles/dl.provisionprofile` and clear the `DL_PROVISION_PROFILE` secret so CI doesnt look for it.
4. Base64-encode them for CI:
```bash
base64 -i tools/mac-profiles/dl.provisionprofile -o dmg-profile.b64
base64 -i tools/mac-profiles/mas.provisionprofile -o mas-profile.b64
```
5. Update the GitHub secrets `DL_PROVISION_PROFILE` (dmg) and `MAS_PROVISION_PROFILE` (store) with the encoded strings and ensure the workflows pass them to electron-builder (e.g., `build.mac.provisioningProfile`). Remember to keep hardened runtime enabled (`build.mac.hardenedRuntime=true`) and entitlements aligned for notarization.
See also:
- <https://www.electron.build/code-signing.html>
- <https://www.electronjs.org/docs/tutorial/mac-app-store-submission-guide>
## Build the DMG locally
1. Create or refresh an app-specific password at <https://appleid.apple.com/account/manage> (use the same Apple ID as notarization).
2. Confirm your scripts use `xcrun notarytool` (Apple blocked `altool` uploads as of 20231101). electron-builder defaults to `notarytool` when it detects Xcode 14+, so avoid overriding that behavior.
3. Run:
```bash
APPLEID="you@example.com" \
APPLEIDPASS="app-specific-password" \
rm -Rf app-builds && npm run build && npm run dist:mac:dl
```
4. The script signs, notarizes (via `notarytool`), and staples the DMG using the new certificates and provisioning profiles. Validate with `spctl --assess -vv --type install path/to/app`.
---
# macOS App Store Certificates Guide
_(Local + CI workflow with electron-builder — 2025 edition)_
---
## 1) Overview
To build and publish your Electron app to the **Mac App Store (MAS)**, you need these **certificates** and a **provisioning profile**:
| Item | Purpose | Used in |
| -------------------------------------- | ---------------------------------------- | ---------- |
| **Apple Development** | Sign local debug/test builds | Local only |
| **Apple Distribution** | Sign the App Store `.app` | Local + CI |
| **Mac Installer Distribution** | Sign the `.pkg` uploaded via Transporter | Local + CI |
| **Mac App Store provisioning profile** | Ties the App ID to Apple Distribution | Local + CI |
> You **do not** need Developer ID certificates or notarization for MAS-only distribution.
---
## 2) Generate a new CSR (Certificate Signing Request)
1. On your Mac, open **Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority…**
2. Fill in:
- **User Email Address:** your Apple ID for the developer team
- **Common Name:** e.g. `Super Productivity MAS Signing Key`
- **CA Email Address:** _leave blank_
- **Request is:** _Saved to disk_
3. Save as `mac-mas.csr`.
> This also creates a **private key** in your login keychain.
---
## 3) Create the MAS certificates in the Apple Developer Portal
1. Go to [https://developer.apple.com/account/resources/certificates/list](https://developer.apple.com/account/resources/certificates/list) → ** Add**
2. Create each certificate using **`mac-mas.csr`**:
- **Apple Development** (for local testing)
- **Apple Distribution** (replaces “Mac App Distribution”)
- **Mac Installer Distribution** (required for `.pkg` upload)
3. Download each resulting **`.cer`** file.
---
## 4) Install and export the identities (for local and CI)
1. **Install:** Double-click each `.cer` to add it under **Keychain Access → My Certificates**.
- Each entry must show a **disclosure triangle** with a **private key**.
- If the private key is missing, regenerate the CSR and certificate.
2. **Export:** Select the relevant identities → **right-click → Export …**
- Format: **Personal Information Exchange (.p12)**
- Name: `mas-certs.p12`
- Set a strong password (→ `MAC_CERTS_PASSWORD`).
3. **Verify on a second Mac (optional but recommended):**
```bash
security import mas-certs.p12 -k login.keychain-db -P "$MAC_CERTS_PASSWORD"
security find-identity -v -p codesigning
```
You should see **Apple Distribution** and **Mac Installer Distribution**.
---
## 5) Create / refresh the Mac App Store provisioning profile
1. Go to [https://developer.apple.com/account/resources/profiles/list](https://developer.apple.com/account/resources/profiles/list) → ** Add Profile**
2. Profile type: **Mac App Store**
3. Select:
- **Apple Distribution** certificate
- Your **App ID**
4. Download as `mas.provisionprofile`.
5. Place it in your repo, e.g.:
```
tools/mac-profiles/mas.provisionprofile
```
---
## 6) Configure electron-builder
In `electron-builder.yml` or `package.json > build`:
```yaml
mac:
category: public.app-category.productivity
hardenedRuntime: true
provisioningProfile: tools/mac-profiles/mas.provisionprofile
entitlements: build/entitlements.mas.plist
entitlementsInherit: build/entitlements.mas.inherit.plist
target:
- mas
```
> Ensure your MAS entitlements files are correct and compatible with App Store requirements.
---
## 7) Local build (MAS)
With the identities installed in your keychain:
```bash
npm run build && npm run dist:mac:mas
```
**Result:** `dist/mac/YourApp.pkg` — signed and ready for App Store upload.
---
## 8) Prepare CI secrets
Base64-encode the cert bundle and profile:
```bash
base64 -i mas-certs.p12 -o mas-certs.b64
base64 -i tools/mac-profiles/mas.provisionprofile -o mas-profile.b64
```
Create **GitHub Actions** secrets (Repo → _Settings → Secrets and variables → Actions_):
| Secret | Value |
| ------------------------- | --------------------------------------------------- |
| `MAC_CERTS` | contents of `mas-certs.b64` |
| `MAC_CERTS_PASSWORD` | the `.p12` export password |
| `MAS_PROVISION_PROFILE` | contents of `mas-profile.b64` |
| `APPLE_APP_SPECIFIC_PASS` | Apple ID app-specific password (for upload tooling) |
---
## 9) GitHub Actions workflow (example)
```yaml
name: Build macOS MAS
on:
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
build-mas:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Decode certificates
run: echo "${{ secrets.MAC_CERTS }}" | base64 --decode > mas-certs.p12
- name: Create and unlock keychain
run: |
security create-keychain -p "" build.keychain
security import mas-certs.p12 -k build.keychain -P "${{ secrets.MAC_CERTS_PASSWORD }}" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
security list-keychains -s build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
security find-identity -v -p codesigning build.keychain
- name: Write provisioning profile
run: echo "${{ secrets.MAS_PROVISION_PROFILE }}" | base64 --decode > tools/mac-profiles/mas.provisionprofile
- name: Install dependencies
run: npm ci
- name: Build & sign for MAS
env:
CSC_LINK: ${{ secrets.MAC_CERTS }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: true
APPLEID: you@example.com
APPLEIDPASS: ${{ secrets.APPLE_APP_SPECIFIC_PASS }}
run: npm run build && npm run dist:mac:mas
```
> The `set-key-partition-list` step grants non-interactive access to the private keys for `codesign` during CI.
---
## 10) Upload to App Store Connect
1. Find the generated `.pkg` in `dist/`.
2. Upload with **Transporter** (Apples tool):
- Open Transporter, sign in with your Apple ID
- Drag the `.pkg` and click **Deliver**
3. Optional verification:
```bash
pkgutil --check-signature path/to/YourApp.pkg
```
Expect:
```
Signed by "Apple Mac Installer Distribution: <Your Team>"
Status: signed Apple Software
```
---
## 11) Maintenance & rotation
- Certificates typically expire after **1 year** (some teams may have 3-year certs).
- Before expiry, repeat this guide:
- Revoke/replace **Apple Distribution** and **Mac Installer Distribution**
- Recreate the **Mac App Store** provisioning profile
- Export a **new** `mas-certs.p12`, update **CI secrets**, and (if needed) reimport locally
- Keep your **App-Specific Password** valid for uploads.
---
### Quick Reference (What you actually need for MAS)
- **Certificates:** Apple Development (local), Apple Distribution, Mac Installer Distribution
- **Profile:** Mac App Store provisioning profile
- **No** Developer ID / notarization needed for MAS-only
- **Build target:** `mas`
- **Upload:** `.pkg` via Transporter
---