Skip to content

Commit 014d8ce

Browse files
ndemiancclaude
andcommitted
PLAN/docs: mark M5 auto-verify + context UI shipped; add macOS signing runbook
- PLAN.md M5: mark the diagnostics-driven auto-verify loop ✅ DONE and update the cost-accounting item with the context-window donut + breakdown popover. - docs/SIGNING-NOTARIZATION.md: full step-by-step runbook for Developer ID signing + notarization (enroll → Developer ID Application cert → notarytool credential → inside-out hardened-runtime signing + Electron/V8 entitlements → notarize → staple → verify), with troubleshooting + an env-gated repo integration plan. Linked from PLAN.md M8. Captured while Apple enrollment is pending (~2 business days). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3c8d318 commit 014d8ce

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

‎docs/SIGNING-NOTARIZATION.md‎

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Atom++ — Code Signing & Notarization Runbook (macOS)
2+
3+
How to turn the **unsigned / ad-hoc** Atom++ build into a **Developer-ID-signed, notarized, stapled**
4+
`.dmg` that launches cleanly on any Mac with no Gatekeeper warnings.
5+
6+
> **Status (2026-06-28):** Apple Developer enrollment in progress (awaiting Apple support, up to ~2
7+
> business days). This runbook captures the full process so it's ready the moment the account + cert
8+
> land. Until then, `scripts/make-dmg.sh` keeps producing an **ad-hoc-signed** dmg (runs locally;
9+
> needs right-click→Open or `xattr -dr com.apple.quarantine` on other Macs).
10+
11+
---
12+
13+
## 0. Where we are vs. where we're going
14+
15+
| | Today (ad-hoc) | Goal (Developer ID + notarized) |
16+
| --- | --- | --- |
17+
| Signature | `codesign --sign -` (ad-hoc) | `Developer ID Application: … (TEAMID)` |
18+
| Hardened runtime | no | **yes** (`--options runtime`, required to notarize) |
19+
| Entitlements | none | Electron/V8 JIT entitlements |
20+
| Notarized by Apple | no | **yes** (`notarytool`) |
21+
| Stapled ticket | no | **yes** (`stapler`) |
22+
| Other Macs | Gatekeeper blocks (right-click→Open) | **launches cleanly, no warning** |
23+
24+
**The pipeline:** enroll → create *Developer ID Application* cert → create a notarization credential →
25+
sign inside-out with hardened runtime + entitlements → notarize the dmg → staple → verify.
26+
27+
---
28+
29+
## 1. Enroll in the Apple Developer Program *(manual — only you can do this)*
30+
31+
- **Cost:** $99 / year. **Time:** minutes to ~48h for approval.
32+
- Pre-req: your **Apple ID must have two-factor auth** enabled (<https://appleid.apple.com>).
33+
- Go to <https://developer.apple.com/programs/> → **Enroll**.
34+
- **Entity type:**
35+
- **Individual / Sole Proprietor** — fastest; the app's signer shows your *personal legal name*.
36+
- **Organization** — shows your *company name*, supports a team, but needs a **D-U-N-S number**
37+
(free; can take a few days) and legal authority to enroll the entity.
38+
- Pay the **$99** (Apple Developer iPhone app or web) and wait for the approval email.
39+
40+
> Recommendation for an indie/personal release: **Individual** now; convert to Organization later if needed.
41+
42+
---
43+
44+
## 2. Create the **Developer ID Application** certificate *(manual)*
45+
46+
⚠️ **Pick the exact right type** — distribution outside the App Store needs **"Developer ID Application"**:
47+
- NOT "Apple Distribution" (App-Store only).
48+
- NOT "Developer ID Installer" (that's for `.pkg`; we ship a `.dmg`, so we don't need it).
49+
50+
**Via Xcode (easiest — also creates the private key):**
51+
1. Install **Xcode** (Mac App Store, free).
52+
2. Xcode → **Settings → Accounts** → add your Apple ID → **Manage Certificates…**
53+
3. **+** → **Developer ID Application**. The cert + private key land in your **login keychain**.
54+
55+
**Verify (this exact identity string is what the build uses):**
56+
```bash
57+
security find-identity -v -p codesigning
58+
# → "Developer ID Application: Your Name (TEAMID1234)"
59+
```
60+
Record your **Team ID** (`TEAMID1234`) and the full identity string.
61+
62+
> Back up the cert+key: Keychain Access → My Certificates → right-click the Developer ID Application
63+
> cert → **Export** → save the `.p12` somewhere safe. Losing the private key means re-issuing.
64+
65+
---
66+
67+
## 3. Create a notarization credential *(manual)*
68+
69+
`notarytool` must authenticate to Apple. Pick one and store it once in the keychain:
70+
71+
**Option A — App-specific password (simplest):**
72+
1. <https://appleid.apple.com> → **Sign-In and Security → App-Specific Passwords** → generate (name it `atompp-notary`).
73+
2. Store it:
74+
```bash
75+
xcrun notarytool store-credentials "atompp-notary" \
76+
--apple-id "you@example.com" \
77+
--team-id "TEAMID1234" \
78+
--password "xxxx-xxxx-xxxx-xxxx" # the app-specific password (not your Apple ID password)
79+
```
80+
81+
**Option B — App Store Connect API key (best for CI):**
82+
- App Store Connect → **Users and Access → Integrations → App Store Connect API → +** → download the
83+
`.p8`, note **Key ID** + **Issuer ID**.
84+
```bash
85+
xcrun notarytool store-credentials "atompp-notary" \
86+
--key AuthKey_XXXXXX.p8 --key-id "KEYID" --issuer "ISSUER-UUID"
87+
```
88+
89+
Either way the saved profile is referenced later as `--keychain-profile "atompp-notary"`. No secret ever
90+
enters the repo.
91+
92+
---
93+
94+
## 4. Entitlements *(in the repo — Electron/V8 needs these or it crashes under the hardened runtime)*
95+
96+
Create `build/entitlements/atompp.entitlements.plist`:
97+
```xml
98+
<?xml version="1.0" encoding="UTF-8"?>
99+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
100+
<plist version="1.0">
101+
<dict>
102+
<key>com.apple.security.cs.allow-jit</key><true/>
103+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
104+
<key>com.apple.security.cs.disable-library-validation</key><true/>
105+
<key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>
106+
</dict>
107+
</plist>
108+
```
109+
The same set is applied to the **helper apps** (GPU / Renderer / Plugin) — they inherit these so V8's JIT
110+
works in every process. (This mirrors what upstream VS Code / `@electron/osx-sign` apply.)
111+
112+
---
113+
114+
## 5. Sign — inside-out, hardened runtime *(automated by `scripts/sign-notarize.sh`, to be added)*
115+
116+
Electron apps **must be signed from the inside out** (every nested `.dylib`/`.framework` and each
117+
`*.app` helper first, the main bundle last). Plain `--deep` is unreliable for this — prefer
118+
[`@electron/osx-sign`](https://github.com/electron/osx-sign) which knows the correct order, or a manual
119+
loop. Key flags on every binary:
120+
```bash
121+
codesign --force --timestamp --options runtime \
122+
--entitlements build/entitlements/atompp.entitlements.plist \
123+
--sign "Developer ID Application: Your Name (TEAMID1234)" <path>
124+
```
125+
- `--options runtime` → hardened runtime (mandatory for notarization).
126+
- `--timestamp` → secure timestamp (mandatory; needs network).
127+
- Sign helpers/frameworks **before** the outer `Atom++.app`.
128+
129+
**Verify the signature before notarizing:**
130+
```bash
131+
codesign --verify --deep --strict --verbose=2 "VSCode-darwin-arm64/Atom++.app"
132+
codesign -dv --verbose=4 "VSCode-darwin-arm64/Atom++.app" # → "Authority=Developer ID Application…", "flags=…runtime"
133+
```
134+
135+
---
136+
137+
## 6. Notarize the `.dmg`
138+
139+
Build the dmg (`scripts/make-dmg.sh`), then:
140+
```bash
141+
xcrun notarytool submit "Atom++-arm64.dmg" --keychain-profile "atompp-notary" --wait
142+
```
143+
- `--wait` blocks until Apple finishes (usually a few minutes) and prints **Accepted / Invalid**.
144+
- On **Invalid**, get the details:
145+
```bash
146+
xcrun notarytool log <submission-id> --keychain-profile "atompp-notary"
147+
```
148+
149+
---
150+
151+
## 7. Staple the ticket
152+
153+
```bash
154+
xcrun stapler staple "Atom++-arm64.dmg"
155+
```
156+
Stapling embeds the notarization ticket so Gatekeeper validates **offline** (no network on first launch).
157+
You can also staple the `.app` *before* building the dmg, then staple the dmg too.
158+
159+
---
160+
161+
## 8. Verify it will pass Gatekeeper on a clean Mac
162+
163+
```bash
164+
xcrun stapler validate "Atom++-arm64.dmg"
165+
spctl -a -t open --context context:primary-signature -v "Atom++-arm64.dmg"
166+
# → "source=Notarized Developer ID" / "accepted"
167+
```
168+
Best real test: copy the dmg to a **different Mac** (or a fresh user account), open it, drag to
169+
Applications, double-click — it should launch with **no warning**.
170+
171+
---
172+
173+
## 9. Troubleshooting
174+
175+
| Symptom | Cause / fix |
176+
| --- | --- |
177+
| `notarytool` → **Invalid**, log says "not signed with a valid Developer ID" | Re-sign with the *Developer ID Application* identity + `--options runtime`. |
178+
| Log: "The executable does not have the hardened runtime enabled" | Missing `--options runtime` on some nested binary — re-sign inside-out. |
179+
| App crashes on launch ("killed", code 137 / SIGKILL) after signing | Missing JIT entitlements (§4) on the main app or a helper. |
180+
| "The signature does not include a secure timestamp" | Add `--timestamp` (needs network when signing). |
181+
| Gatekeeper still warns after notarizing | Forgot to **staple** (§7), or stapled the app but not the dmg. |
182+
| `errSecInternalComponent` during codesign | Keychain locked / wrong identity — `security unlock-keychain`; check `security find-identity`. |
183+
184+
---
185+
186+
## 10. Repo integration plan (to wire once the cert is installed)
187+
188+
1. `build/entitlements/atompp.entitlements.plist` — the entitlements from §4 (tracked).
189+
2. `scripts/sign-notarize.sh` — Developer-ID signs inside-out (`@electron/osx-sign` or manual loop),
190+
notarizes (§6), staples (§7), verifies (§8). **Gated on env vars** so it's a no-op without a cert:
191+
- `ATOMPP_SIGN_IDENTITY` — e.g. `Developer ID Application: Your Name (TEAMID1234)`
192+
- `ATOMPP_NOTARY_PROFILE` — e.g. `atompp-notary`
193+
3. `scripts/make-dmg.sh` — when `ATOMPP_SIGN_IDENTITY` is set, call `sign-notarize.sh` instead of the
194+
ad-hoc `codesign --sign -`; otherwise behave exactly as today (ad-hoc, unnotarized).
195+
196+
So a signed release becomes:
197+
```bash
198+
export ATOMPP_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID1234)"
199+
export ATOMPP_NOTARY_PROFILE="atompp-notary"
200+
./scripts/build-macos.sh && ./scripts/make-dmg.sh # signs → dmg → notarize → staple → verify
201+
```
202+
203+
---
204+
205+
## Quick reference (once enrolled)
206+
207+
```bash
208+
security find-identity -v -p codesigning # confirm cert
209+
xcrun notarytool store-credentials "atompp-notary" --apple-id … --team-id … --password …
210+
# build + sign + notarize + staple (via the wired scripts):
211+
ATOMPP_SIGN_IDENTITY="Developer ID Application: … (TEAMID)" \
212+
ATOMPP_NOTARY_PROFILE="atompp-notary" ./scripts/make-dmg.sh
213+
xcrun stapler validate Atom++-arm64.dmg && spctl -a -t open --context context:primary-signature -v Atom++-arm64.dmg
214+
```

0 commit comments

Comments
 (0)