#!/usr/bin/env bash # Local Developer ID packaging adapter; publication remains a project CI operation. set -euo pipefail APP="${1:?usage: $0 }" DMG_OUT="${2:?usage: $0 }" : "${SIGN_IDENTITY:?Set the selected Developer ID identity}" ENTITLEMENTS="${ENTITLEMENTS:-}" if [[ "${SKIP_NOTARIZE:-0}" != 1 ]]; then : "${NOTARY_PROFILE:?Set the notarytool keychain profile}" fi [[ -d "$APP" && ! -L "$APP" ]] || { echo 'Expected a regular built app bundle.' >&2; exit 1; } HELPER="$(cd "$(dirname "$0")" && pwd)/signing_checks.py" python3 "$HELPER" preflight "$APP" if [[ -n "${NESTED_SIGN_SCRIPT:-}" ]]; then [[ -x "$NESTED_SIGN_SCRIPT" ]] || { echo 'Project component signing script is not executable.' >&2; exit 1; } "$NESTED_SIGN_SCRIPT" "$APP" fi SIGN_ARGS=(--force --timestamp --options runtime --sign "$SIGN_IDENTITY") if [[ -n "$ENTITLEMENTS" ]]; then [[ -f "$ENTITLEMENTS" ]] || { echo 'Main app entitlements file is missing.' >&2; exit 1; } plutil -lint "$ENTITLEMENTS" SIGN_ARGS+=(--entitlements "$ENTITLEMENTS") fi # Nested code must have already been signed by the project's component-aware script. codesign "${SIGN_ARGS[@]}" "$APP" codesign --verify --deep --strict "$APP" python3 "$HELPER" identity "$APP" APP_IDENTIFIER="$(python3 "$HELPER" bundle-id "$APP")" mkdir -p "$(dirname "$DMG_OUT")" [[ ! -e "$DMG_OUT" && ! -L "$DMG_OUT" ]] || { echo 'Output already exists; choose a new output path.' >&2; exit 1; } hdiutil create -volname "$(basename "${APP%.app}")" -srcfolder "$APP" -format UDZO "$DMG_OUT" codesign --timestamp --sign "$SIGN_IDENTITY" --identifier "$APP_IDENTIFIER.dmg" "$DMG_OUT" codesign --verify --strict "$DMG_OUT" python3 "$HELPER" identity "$DMG_OUT" if [[ "${SKIP_NOTARIZE:-0}" == 1 ]]; then shasum -a 256 "$DMG_OUT" > "$DMG_OUT.sha256" echo "Signed package created; notarization was not requested: $DMG_OUT" exit 0 fi SIGN_TMP="$(mktemp -d)" SIGN_MOUNT="" cleanup() { if [[ -n "$SIGN_MOUNT" ]]; then hdiutil detach "$SIGN_MOUNT" >/dev/null 2>&1 || echo 'Mount cleanup failed; detach manually.' >&2; fi rm -rf "$SIGN_TMP" } trap cleanup EXIT xcrun notarytool submit "$DMG_OUT" --keychain-profile "$NOTARY_PROFILE" --wait --output-format json > "$SIGN_TMP/notary.json" python3 "$HELPER" accepted "$SIGN_TMP/notary.json" xcrun stapler staple "$DMG_OUT" xcrun stapler validate "$DMG_OUT" spctl --assess --type open --context context:primary-signature -vv "$DMG_OUT" SIGN_MOUNT="$SIGN_TMP/volume" hdiutil attach "$DMG_OUT" -readonly -nobrowse -mountpoint "$SIGN_MOUNT" >/dev/null shopt -s nullglob SIGNED_APPS=("$SIGN_MOUNT"/*.app) [[ "${#SIGNED_APPS[@]}" == 1 ]] || { echo 'Expected exactly one app inside the distribution DMG.' >&2; exit 1; } codesign --verify --deep --strict "${SIGNED_APPS[0]}" python3 "$HELPER" identity "${SIGNED_APPS[0]}" spctl --assess --type execute -vv "${SIGNED_APPS[0]}" hdiutil detach "$SIGN_MOUNT" >/dev/null SIGN_MOUNT="" shasum -a 256 "$DMG_OUT" > "$DMG_OUT.sha256" echo "Verified signed, notarized and stapled package: $DMG_OUT"