Require notarization for GUI releases
Shipping an unnotarized build left the user with an app Gatekeeper
rejects, and the skill previously suggested quarantine workarounds that
hid the problem. Notarization is now a hard gate, with the account-level
agreement failure documented so it is diagnosed instead of bypassed.
💘 Generated with Crush
Assisted-by: Crush:kimi-k3
This commit is contained in:
@@ -19,8 +19,14 @@ The reference implementations to copy from are `~/Git/kapture` and
|
|||||||
`gitea.tzgyn.dev`. SSH: `git@gitea.tzgyn.dev:TZGyn/<repo>.git`.
|
`gitea.tzgyn.dev`. SSH: `git@gitea.tzgyn.dev:TZGyn/<repo>.git`.
|
||||||
- **Always print the upgrade script** (the `brew` commands below) in the final
|
- **Always print the upgrade script** (the `brew` commands below) in the final
|
||||||
answer after any release or version bump. The user wants it every time.
|
answer after any release or version bump. The user wants it every time.
|
||||||
|
- **Every GUI release must be notarized and stapled.** An unnotarized build is
|
||||||
|
not a release: never publish one, and never paper over Gatekeeper with
|
||||||
|
`xattr -dr com.apple.quarantine` or `brew install --cask --no-quarantine`.
|
||||||
|
If notarization fails, fix the cause (see
|
||||||
|
[Troubleshooting](#notarization-troubleshooting)) before publishing.
|
||||||
- Never commit build artifacts. `.gitignore` covers `.build/`, `*.app/`,
|
- Never commit build artifacts. `.gitignore` covers `.build/`, `*.app/`,
|
||||||
`dist/`, `.swiftpm/`, `xcuserdata/`, `DerivedData/`, `.DS_Store`.
|
`dist/`, `.swiftpm/`, `xcuserdata/`, `DerivedData/`, `.DS_Store`.
|
||||||
|
(`go build ./...` drops a binary next to the source — ignore it explicitly.)
|
||||||
- Never commit secrets (`.p8`, API keys) or push to a remote unless asked.
|
- Never commit secrets (`.p8`, API keys) or push to a remote unless asked.
|
||||||
- Commit messages: concise, why-focused, first line < 72 chars; add a body only
|
- Commit messages: concise, why-focused, first line < 72 chars; add a body only
|
||||||
when it helps a future reader.
|
when it helps a future reader.
|
||||||
@@ -124,7 +130,9 @@ Two scripts per repo: `build-app.sh <version>` and `release.sh <version>`.
|
|||||||
4. **Bump the cask** (`version` + `sha256`), push the tap, pull Homebrew's
|
4. **Bump the cask** (`version` + `sha256`), push the tap, pull Homebrew's
|
||||||
clone, `brew audit`. Verify by downloading the asset back and comparing the
|
clone, `brew audit`. Verify by downloading the asset back and comparing the
|
||||||
sha256, and `spctl -a -vv <App>.app` → `accepted` /
|
sha256, and `spctl -a -vv <App>.app` → `accepted` /
|
||||||
`source=Notarized Developer ID`.
|
`source=Notarized Developer ID`. If `spctl` does not say
|
||||||
|
`source=Notarized Developer ID`, stop and fix notarization first — do not
|
||||||
|
publish the release or the cask.
|
||||||
|
|
||||||
5. **Print the upgrade script**:
|
5. **Print the upgrade script**:
|
||||||
|
|
||||||
@@ -162,11 +170,33 @@ xcrun stapler staple <App>.app && xcrun stapler validate <App>.app
|
|||||||
spctl -a -vv <App>.app # want: accepted, source=Notarized Developer ID
|
spctl -a -vv <App>.app # want: accepted, source=Notarized Developer ID
|
||||||
```
|
```
|
||||||
|
|
||||||
Wire `release.sh` so it notarizes when `NOTARY_*` env vars are set and otherwise
|
`release.sh` **must** notarize. Treat any skip or failure as a failed release:
|
||||||
skips gracefully (build + zip + sha only). An unnotarized app is still signed but
|
do not publish the tag, the release, or the cask, and do not suggest
|
||||||
Gatekeeper warns on other machines; the user can bypass with
|
quarantine workarounds (`xattr -dr com.apple.quarantine`,
|
||||||
`xattr -dr com.apple.quarantine <App>.app` or install with
|
`brew install --cask --no-quarantine`) — those hand the user a build macOS
|
||||||
`brew install --cask --no-quarantine <name>`.
|
rejects. Only publish once `spctl -a -vv <App>.app` reports `accepted` with
|
||||||
|
`source=Notarized Developer ID`.
|
||||||
|
|
||||||
|
### Notarization troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Cause / fix |
|
||||||
|
| --- | --- |
|
||||||
|
| `HTTP status code: 403. A required agreement is missing or has expired.` | The **Apple Developer Program License Agreement** for the team is unsigned or expired. The Account Holder must accept it in App Store Connect → **Business → Agreements** *and* at `developer.apple.com/account` → **Agreements**. Account-wide: every call fails, including read-only `notarytool history`, until it is accepted; allow a few minutes to propagate. |
|
||||||
|
| The same 403 *after* accepting | The wrong agreement was accepted (App Store Connect "Paid Apps" instead of the Program License Agreement) or it was accepted under a different team's account. |
|
||||||
|
| `401 Authentication credentials are incorrect` | Wrong key id / issuer, or the key belongs to another team. |
|
||||||
|
|
||||||
|
Confirm the team before blaming the credentials — do not "fix" this by
|
||||||
|
switching teams or re-signing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
security find-identity -v -p codesigning # the Developer ID team must match the key's team
|
||||||
|
spctl -a -vv /Applications/<App>.app # an already-notarized app proves the team/key pair works
|
||||||
|
xcrun notarytool history --key ... --key-id ... --issuer ... # 403 here = account agreement, not the app
|
||||||
|
```
|
||||||
|
|
||||||
|
`notarytool history` failing the same way as the submit is the tell: the
|
||||||
|
problem is the account, not the bundle, so there is nothing to change in the
|
||||||
|
app or the scripts.
|
||||||
|
|
||||||
## 5. Mobile
|
## 5. Mobile
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user