| name | notarize-release |
| description | Sign, notarize, staple, and package Loopaper into a distributable DMG. Use for Phase 8 release builds — produces a Gatekeeper-passing notarized .dmg from a Release build. User-invoked only. |
| disable-model-invocation | true |
Notarize & Release Loopaper
Produces a notarized, stapled .dmg of Loopaper that passes Gatekeeper on a clean Mac. This is the Phase 8 workflow from docs/work-plan.md. Loopaper ships outside the Mac App Store (ADR-0001): App Sandbox OFF, Hardened Runtime ON, Developer ID Application signing.
Preflight — verify before doing anything
Confirm with the user, and check what you can:
- Signing identity — a "Developer ID Application" cert is in the keychain:
security find-identity -v -p codesigning | grep "Developer ID Application"
- Notary credentials — a stored
notarytool profile (preferred) or Apple ID + app-specific password + Team ID. To create a stored profile once:
xcrun notarytool store-credentials "loopaper-notary" \
--apple-id "<apple-id>" --team-id "<TEAMID>" --password "<app-specific-password>"
- Build settings — the Release target has Hardened Runtime ON and App Sandbox OFF. (If sandbox were on,
NSWorkspace.setDesktopImageURL — the Freeze feature — would be blocked.)
- Ask the user for the version/build number being released and confirm it matches the Xcode project.
If any preflight item is unmet, stop and report it — do not proceed.
Steps
Set variables first (ask the user for real values; do not guess the path or identity):
APP="build/Release/Loopaper.app"
IDENTITY="Developer ID Application: <Name> (<TEAMID>)"
PROFILE="loopaper-notary"
DMG="dist/Loopaper.dmg"
1. Build Release (Universal)
Build a Release, Universal (arm64 + x86_64) configuration via Xcode or:
xcodebuild -scheme Loopaper -configuration Release \
-derivedDataPath build ONLY_ACTIVE_ARCH=NO clean build
Locate the produced Loopaper.app and set APP accordingly.
2. Codesign (deep, hardened, with timestamp)
codesign --force --options runtime --timestamp \
--sign "$IDENTITY" --deep "$APP"
codesign --verify --strict --verbose=2 "$APP"
3. Build the DMG
Create dist/, stage the app + an /Applications symlink, then make a compressed DMG:
mkdir -p dist dmg-staging
cp -R "$APP" dmg-staging/
ln -sf /Applications dmg-staging/Applications
hdiutil create -volname "Loopaper" -srcfolder dmg-staging \
-ov -format UDZO "$DMG"
rm -rf dmg-staging
4. Notarize the DMG (wait for result)
xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait
If it reports Invalid, fetch the log and fix before retrying:
xcrun notarytool log <submission-id> --keychain-profile "$PROFILE"
5. Staple
xcrun stapler staple "$DMG"
xcrun stapler validate "$DMG"
6. Final Gatekeeper check
spctl -a -t open --context context:primary-signature -v "$DMG"
codesign --verify --deep --strict --verbose=2 "$APP"
Done
Report the path to the stapled $DMG, the version released, and the Gatekeeper/spctl verdict. Remind the user to verify on a different Mac (downloaded, not copied) — quarantine behavior only triggers on a real download. Do not push tags or publish anything unless the user explicitly asks.