#!/usr/bin/env bash # notary-dry-run.sh — menubar notarytool dry-run (Issue 1 commercial) # # Default: locate notarytool, report APP/ZIP presence, print submit/staple # recipes using env **names** only. Does NOT codesign, submit, or staple. # # Live submit only when ALL of: # NOTARY_SUBMIT=1 # notarytool on PATH # a zip (or ditto-able .app) exists # Never prints .p8 contents, passwords, or real secret values. # # Related: # ci-notarize.yml.example # NOTARIZE-CHECKLIST.md # platform/windows/wix/CI-SIGN-NOTARY.md # docs/COMMERCIAL-INSTALLER-BACKLOG.md # # Usage: # ./notary-dry-run.sh # ./notary-dry-run.sh ~/Applications/Verae\ Menubar.app # APP=… ZIP=… ./notary-dry-run.sh # NOTARY_SUBMIT=1 ZIP=/tmp/VeraeMenubar.zip ./notary-dry-run.sh # gated submit # # Exit: 0 on dry-run success (including missing notarytool tip). # 1 on bad args / failed gated submit. set -euo pipefail DIR="$(cd "$(dirname "$0")" && pwd)" ROOT="$(cd "$DIR/../../.." && pwd)" APP="${APP:-}" ZIP="${ZIP:-}" PROFILE="${NOTARY_KEYCHAIN_PROFILE:-verae-notary}" # Optional positional: path to .app or .zip if [[ $# -gt 0 ]]; then arg="$1" case "$arg" in *.zip|*.ZIP) ZIP="$arg" ;; *.app|*.APP) APP="$arg" ;; *) if [[ -d "$arg" && "$arg" == *.app ]]; then APP="$arg" elif [[ -f "$arg" && "$arg" == *.zip ]]; then ZIP="$arg" else echo "usage: $0 [path/to/Verae Menubar.app|.zip]" >&2 echo " env: APP= ZIP= NOTARY_SUBMIT=1 NOTARY_KEYCHAIN_PROFILE=" >&2 exit 1 fi ;; esac fi # Defaults when unset if [[ -z "$APP" ]]; then for cand in \ "$DIR/Verae Menubar.app" \ "$HOME/Applications/Verae Menubar.app" \ "/Applications/Verae Menubar.app" do if [[ -d "$cand" ]]; then APP="$cand" break fi done fi if [[ -z "$ZIP" ]]; then for cand in \ "/tmp/VeraeMenubar.zip" \ "$DIR/VeraeMenubar.zip" \ "${TMPDIR:-/tmp}/VeraeMenubar.zip" do if [[ -f "$cand" ]]; then ZIP="$cand" break fi done fi ZIP="${ZIP:-/tmp/VeraeMenubar.zip}" echo "notary-dry-run — does NOT codesign or staple by default; submit only if NOTARY_SUBMIT=1." echo "Repo: $ROOT" echo "Docs: platform/macos/menubar/ci-notarize.yml.example" echo " platform/macos/menubar/NOTARIZE-CHECKLIST.md" echo " platform/windows/wix/CI-SIGN-NOTARY.md" echo " docs/COMMERCIAL-INSTALLER-BACKLOG.md" echo "" # --- notarytool presence --- echo "==== notarytool ====" NOTARYTOOL="" if command -v xcrun >/dev/null 2>&1; then if xcrun --find notarytool >/dev/null 2>&1; then NOTARYTOOL="$(xcrun --find notarytool 2>/dev/null || true)" fi fi if [[ -z "$NOTARYTOOL" ]] && command -v notarytool >/dev/null 2>&1; then NOTARYTOOL="$(command -v notarytool)" fi if [[ -z "$NOTARYTOOL" ]]; then echo " notarytool: NOT FOUND" echo "" echo " Tip: install Xcode Command Line Tools (or full Xcode), then:" echo " xcode-select --install" echo " xcrun --find notarytool" echo " Live CI notary stays gated off — see ci-notarize.yml.example (if: false)" echo " and CI-SIGN-NOTARY.md. Exiting 0 (dry-run scaffold; no notarytool required)." exit 0 fi echo " notarytool: $NOTARYTOOL" # --- artifacts --- echo "" echo "==== Artifacts ====" if [[ -n "$APP" && -d "$APP" ]]; then echo " APP: $APP" if [[ -f "$APP/Contents/Resources/VERSION" ]]; then echo " VERSION=$(tr -d '\r\n' < "$APP/Contents/Resources/VERSION")" fi else echo " APP: (none found — build with ./build-menubar-app.sh)" APP="" fi if [[ -f "$ZIP" ]]; then echo " ZIP: $ZIP ($(wc -c < "$ZIP" | tr -d ' ') bytes)" else echo " ZIP: $ZIP (missing — ditto after codesign; see recipe below)" fi # --- codesign / staple status (informational only) --- echo "" echo "==== Status (informational) ====" if [[ -n "$APP" ]]; then if command -v codesign >/dev/null 2>&1; then if codesign --verify --deep --strict "$APP" >/dev/null 2>&1; then echo " codesign verify: OK" else echo " codesign verify: FAIL / unsigned (expected until Developer ID applied)" fi else echo " codesign: not on PATH" fi if command -v xcrun >/dev/null 2>&1 && xcrun --find stapler >/dev/null 2>&1; then if xcrun stapler validate "$APP" >/dev/null 2>&1; then echo " stapler validate: OK (ticket present)" else echo " stapler validate: no ticket (expected until notary Accepted + staple)" fi fi else echo " (skip — no APP)" fi # --- env secret names + recipe --- echo "" echo "==== Submit recipe (env names only — no secrets) ====" echo " Secret / env names (see CI-SIGN-NOTARY.md + ci-notarize.yml.example):" echo " APPLE_API_KEY_P8_BASE64 # base64 of AuthKey_*.p8 (CI materialize only)" echo " APPLE_API_KEY_ID" echo " APPLE_API_ISSUER_ID" echo " APPLE_TEAM_ID" echo " APPLE_CODESIGN_IDENTITY # Developer ID Application: … (TEAMID)" echo " APPLE_INSTALLER_IDENTITY # optional; product .pkg path" echo " NOTARY_KEYCHAIN_PROFILE # laptop path (default print: $PROFILE)" echo " NOTARY_KEY_PATH # path to AuthKey_*.p8 after materialize" echo " NOTARY_KEY_ID / NOTARY_ISSUER_ID # aliases used in NOTARIZE-CHECKLIST" echo "" echo " # 0) Build unsigned (dev default)" echo " ./build-menubar-app.sh \"\${APP:-\$HOME/Applications/Verae Menubar.app}\"" echo "" echo " # 1) Codesign hardened runtime (operator machine / gated CI — not this dry-run)" echo " APP=\"\${APP:-\$HOME/Applications/Verae Menubar.app}\"" echo " ENT=\"\$APP/Contents/Resources/entitlements.plist\"" echo " codesign --force --deep --options runtime --timestamp \\" echo " --entitlements \"\$ENT\" \\" echo " --sign \"\$APPLE_CODESIGN_IDENTITY\" \\" echo " \"\$APP\"" echo " codesign --verify --deep --strict --verbose=2 \"\$APP\"" echo "" echo " # 2) Zip for notarytool" echo " ZIP=\"\${ZIP:-/tmp/VeraeMenubar.zip}\"" echo " rm -f \"\$ZIP\"" echo " ditto -c -k --keepParent \"\$APP\" \"\$ZIP\"" echo "" echo " # 3a) Submit — keychain profile (laptops)" echo " xcrun notarytool submit \"\$ZIP\" \\" echo " --keychain-profile \"\${NOTARY_KEYCHAIN_PROFILE:-$PROFILE}\" \\" echo " --wait" echo "" echo " # 3b) Submit — App Store Connect API key (CI-friendly)" echo " xcrun notarytool submit \"\$ZIP\" \\" echo " --key \"\$NOTARY_KEY_PATH\" \\" echo " --key-id \"\${APPLE_API_KEY_ID:-\$NOTARY_KEY_ID}\" \\" echo " --issuer \"\${APPLE_API_ISSUER_ID:-\$NOTARY_ISSUER_ID}\" \\" echo " --wait" echo "" echo " # 4) Staple + Gatekeeper" echo " xcrun stapler staple \"\$APP\"" echo " xcrun stapler validate \"\$APP\"" echo " spctl --assess --type execute -vv \"\$APP\"" echo "" echo " CI sketch (unsigned default; notary job if: false):" echo " platform/macos/menubar/ci-notarize.yml.example" echo " Backlog: docs/COMMERCIAL-INSTALLER-BACKLOG.md" echo " This dry-run does not claim notarization shipped." # --- gated submit --- echo "" echo "==== Gated submit ====" if [[ "${NOTARY_SUBMIT:-}" != "1" ]]; then echo " NOTARY_SUBMIT is not 1 — no submit performed (print-only)." echo " To submit for real: NOTARY_SUBMIT=1 ZIP=… [key env] $0" exit 0 fi echo " NOTARY_SUBMIT=1 — attempting notarytool submit --wait" if [[ ! -f "$ZIP" ]]; then if [[ -n "$APP" && -d "$APP" ]]; then echo " ZIP missing; creating via ditto from APP…" rm -f "$ZIP" ditto -c -k --keepParent "$APP" "$ZIP" else echo " ERROR: NOTARY_SUBMIT=1 but no ZIP and no APP to ditto." >&2 exit 1 fi fi # Prefer API key env if NOTARY_KEY_PATH set; else keychain profile set +e if [[ -n "${NOTARY_KEY_PATH:-}" || -n "${APPLE_API_KEY_ID:-}" ]]; then KEY_PATH="${NOTARY_KEY_PATH:-}" KEY_ID="${APPLE_API_KEY_ID:-${NOTARY_KEY_ID:-}}" ISSUER="${APPLE_API_ISSUER_ID:-${NOTARY_ISSUER_ID:-}}" if [[ -z "$KEY_PATH" || -z "$KEY_ID" || -z "$ISSUER" ]]; then echo " ERROR: API-key submit needs NOTARY_KEY_PATH + APPLE_API_KEY_ID + APPLE_API_ISSUER_ID" >&2 exit 1 fi echo " mode: API key (key-id=\$APPLE_API_KEY_ID — value not printed)" xcrun notarytool submit "$ZIP" \ --key "$KEY_PATH" \ --key-id "$KEY_ID" \ --issuer "$ISSUER" \ --wait rc=$? else echo " mode: keychain profile (${NOTARY_KEYCHAIN_PROFILE:-$PROFILE})" xcrun notarytool submit "$ZIP" \ --keychain-profile "${NOTARY_KEYCHAIN_PROFILE:-$PROFILE}" \ --wait rc=$? fi set -e if [[ $rc -ne 0 ]]; then echo " notarytool submit failed (exit $rc)" >&2 exit "$rc" fi if [[ -n "$APP" && -d "$APP" ]]; then echo " stapling $APP" xcrun stapler staple "$APP" xcrun stapler validate "$APP" || true fi echo " submit path finished." exit 0