diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..442e26c --- /dev/null +++ b/.env.template @@ -0,0 +1,16 @@ +# Environment variables for Meetingnotes code signing and notarization +# Copy this file to .env and fill in your values + +# Your Developer ID Application certificate name (from Keychain) +# Run: security find-identity -v -p codesigning | grep "Developer ID Application" +DEVELOPER_ID="developer-id" + +# Your Apple ID email address +APPLE_ID="your-apple-id@example.com" + +# Your Apple Developer Team ID +TEAM_ID="team-id" + +# App-specific password for notarization +# Generate at: https://appleid.apple.com/account/manage -> App-Specific Passwords +APP_PASSWORD="your-app-specific-password" diff --git a/Meetingnotes.xcodeproj/project.pbxproj b/Meetingnotes.xcodeproj/project.pbxproj index 8382cac..e023f4d 100644 --- a/Meetingnotes.xcodeproj/project.pbxproj +++ b/Meetingnotes.xcodeproj/project.pbxproj @@ -280,7 +280,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = ML6HYR5LUR; ENABLE_HARDENED_RUNTIME = YES; @@ -294,7 +294,8 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.0.0; + ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = owen.meetingnotes; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -314,7 +315,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = ML6HYR5LUR; ENABLE_HARDENED_RUNTIME = YES; @@ -328,7 +329,8 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.0.0; + ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = owen.meetingnotes; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/appcast.xml b/appcast.xml index ca920aa..0fa5828 100644 --- a/appcast.xml +++ b/appcast.xml @@ -2,32 +2,13 @@ Meetingnotes - - 1.1.1 - Sun, 13 Jul 2025 21:32:03 -0400 - 3 - 1.1.1 - 14.0 - - - - - - - 1.1.0 - Sun, 13 Jul 2025 20:42:41 -0400 - 2 - 1.1.0 - 14.0 - - 1.0.0 - Fri, 11 Jul 2025 08:36:54 -0400 - 1 + Mon, 14 Jul 2025 16:22:59 -0400 + 5 1.0.0 14.0 - + \ No newline at end of file diff --git a/releases/Meetingnotes-1.1.1.dmg b/releases/Meetingnotes-1.0.0-resubmit.dmg similarity index 83% rename from releases/Meetingnotes-1.1.1.dmg rename to releases/Meetingnotes-1.0.0-resubmit.dmg index 4be3ac7..56c8e5b 100644 Binary files a/releases/Meetingnotes-1.1.1.dmg and b/releases/Meetingnotes-1.0.0-resubmit.dmg differ diff --git a/releases/Meetingnotes-1.0.0.dmg b/releases/Meetingnotes-1.0.0.dmg index d427dce..900f4c6 100644 Binary files a/releases/Meetingnotes-1.0.0.dmg and b/releases/Meetingnotes-1.0.0.dmg differ diff --git a/releases/Meetingnotes-1.1.0.dmg b/releases/Meetingnotes-1.1.0.dmg deleted file mode 100644 index e5da182..0000000 Binary files a/releases/Meetingnotes-1.1.0.dmg and /dev/null differ diff --git a/releases/Meetingnotes3-2.delta b/releases/Meetingnotes3-2.delta deleted file mode 100644 index aa6a077..0000000 Binary files a/releases/Meetingnotes3-2.delta and /dev/null differ diff --git a/scripts/build_release.sh b/scripts/build_release.sh index 86756b8..1def27f 100755 --- a/scripts/build_release.sh +++ b/scripts/build_release.sh @@ -9,19 +9,52 @@ set -e # Exit on any error APP_NAME="Meetingnotes" BUNDLE_ID="owen.meetingnotes" VERSION=$(grep -m1 "MARKETING_VERSION" Meetingnotes.xcodeproj/project.pbxproj | sed 's/.*= \(.*\);/\1/') -SIGN_ID="-" # always use an ad-hoc signature + +# Production code signing configuration +DEVELOPER_ID="${DEVELOPER_ID:-}" + +# Notarization configuration (required for production builds) +APPLE_ID="${APPLE_ID:-}" +TEAM_ID="${TEAM_ID:-}" +APP_PASSWORD="${APP_PASSWORD:-}" if [ -z "$VERSION" ]; then echo "❌ Could not determine version from project file" echo " Make sure Meetingnotes.xcodeproj/project.pbxproj exists and contains MARKETING_VERSION" exit 1 fi + BUILD_DIR="$(pwd)/build" RELEASES_DIR="$(pwd)/releases" DMG_NAME="${APP_NAME}-${VERSION}.dmg" echo "🚀 Building ${APP_NAME} v${VERSION}..." +# Check signing configuration +echo "🔏 Using Developer ID Application: [HIDDEN]" + +# Verify notarization credentials +if [ -z "$DEVELOPER_ID" ] || [ -z "$APPLE_ID" ] || [ -z "$TEAM_ID" ] || [ -z "$APP_PASSWORD" ]; then + echo "❌ Missing required credentials!" + echo "" + echo "📝 Required environment variables:" + echo " DEVELOPER_ID - Your Developer ID Application certificate name" + echo " APPLE_ID - Your Apple ID email" + echo " TEAM_ID - Your Apple Developer Team ID" + echo " APP_PASSWORD - App-specific password" + echo "" + echo "🔧 Set them up:" + echo " Create a .env file with your credentials" + echo " Then run: source .env && ./scripts/build_release.sh" + echo "" + echo "💡 Use: ./scripts/setup_codesigning.sh to get started" + echo "" + exit 1 +fi + +echo "📡 Notarization configured for Apple ID: [HIDDEN]" +echo "🏷️ Team ID: [HIDDEN]" + # Clean and build a *universal* binary (arm64 + x86_64) # ----------------------------------------------------- # Xcode will only build the active architecture by default ("My Mac") which results in an @@ -51,19 +84,71 @@ if [ ! -d "$APP_PATH" ]; then exit 1 fi -# 🔏 Code-sign the app (ad-hoc) ------------------------------------------------- +# 🔏 Production code signing with hardened runtime ------------------------------------------------- -echo "🔏 Ad-hoc signing (.app + embedded frameworks)" +echo "🔏 Code signing all embedded frameworks and components..." + +# Sign all embedded frameworks and their components first +# This is required for notarization - we must sign from the inside out +find "$APP_PATH" -name "*.framework" -type d | while read framework; do + echo " Signing framework: $(basename "$framework")" + + # Sign all binaries within the framework + find "$framework" -type f -perm +111 -exec sh -c 'file "$1" | grep -q "Mach-O"' _ {} \; -print | while read binary; do + echo " Signing binary: $(basename "$binary")" + codesign \ + --force \ + --options runtime \ + --sign "$DEVELOPER_ID" \ + --timestamp \ + "$binary" + done + + # Sign the framework itself + codesign \ + --force \ + --options runtime \ + --sign "$DEVELOPER_ID" \ + --timestamp \ + "$framework" +done + +# Sign all XPC services +find "$APP_PATH" -name "*.xpc" -type d | while read xpc; do + echo " Signing XPC service: $(basename "$xpc")" + codesign \ + --force \ + --options runtime \ + --sign "$DEVELOPER_ID" \ + --timestamp \ + "$xpc" +done + +# Sign all nested apps (like Sparkle's Updater.app) +find "$APP_PATH" -name "*.app" -type d | grep -v "^$APP_PATH$" | while read app; do + echo " Signing nested app: $(basename "$app")" + codesign \ + --force \ + --options runtime \ + --sign "$DEVELOPER_ID" \ + --timestamp \ + "$app" +done + +echo "🔏 Code signing the main app with hardened runtime..." codesign \ --force \ - --deep \ - --sign - \ + --options runtime \ + --entitlements "meetingnotes/meetingnotes.entitlements" \ + --sign "$DEVELOPER_ID" \ + --timestamp \ "$APP_PATH" # Validate the signature before packaging +echo "✅ Validating code signature..." codesign --verify --deep --strict --verbose=2 "$APP_PATH" -echo "✅ App built successfully at $APP_PATH" +echo "✅ App built and signed successfully at $APP_PATH" # Create DMG echo "📀 Creating DMG..." @@ -88,12 +173,50 @@ create-dmg \ echo "✅ DMG created: $RELEASES_DIR/$DMG_NAME" -# Generate appcast with signatures +# 📡 Notarization (required for all production builds) +echo "📡 Starting notarization process..." + +# Submit for notarization +echo "📤 Submitting DMG for notarization..." +NOTARIZATION_RESPONSE=$(xcrun notarytool submit "$RELEASES_DIR/$DMG_NAME" \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASSWORD" \ + --wait) + +if echo "$NOTARIZATION_RESPONSE" | grep -q "status: Accepted"; then + echo "✅ Notarization successful!" + + # Staple the notarization + echo "📎 Stapling notarization ticket to DMG..." + xcrun stapler staple "$RELEASES_DIR/$DMG_NAME" + echo "✅ DMG notarized and stapled!" +else + echo "❌ Notarization failed!" + echo "$NOTARIZATION_RESPONSE" + exit 1 +fi + +# Generate appcast with signatures - only process current version to avoid URL corruption echo "📡 Generating appcast with EdDSA signatures..." + +# Temporarily move old DMGs to avoid URL corruption +echo "📦 Temporarily moving old DMGs to preserve their URLs..." +mkdir -p "$RELEASES_DIR/temp_old" +find "$RELEASES_DIR" -name "*.dmg" ! -name "$DMG_NAME" -exec mv {} "$RELEASES_DIR/temp_old/" \; + +# Generate appcast (will only see current DMG + existing appcast.xml) /opt/homebrew/Caskroom/sparkle/2.7.1/bin/generate_appcast "$RELEASES_DIR" \ --download-url-prefix "https://github.com/owengretzinger/meetingnotes/releases/download/v${VERSION}/" \ -o "appcast.xml" +# Move old DMGs back +echo "📦 Restoring old DMGs..." +if [ -d "$RELEASES_DIR/temp_old" ] && [ "$(ls -A "$RELEASES_DIR/temp_old")" ]; then + mv "$RELEASES_DIR/temp_old"/* "$RELEASES_DIR/" +fi +rmdir "$RELEASES_DIR/temp_old" + echo "📝 Note: Make sure to upload the DMG to GitHub releases with the correct tag (v${VERSION})" echo "✅ Appcast generated: appcast.xml" @@ -104,8 +227,10 @@ echo "📊 Release Summary:" echo " Version: $VERSION" echo " DMG: $DMG_NAME ($(du -h "$RELEASES_DIR/$DMG_NAME" | cut -f1))" echo " Location: $RELEASES_DIR/$DMG_NAME" +echo " Code Signing: ✅ Production (Owen's Developer ID)" +echo " Notarization: ✅ Complete" echo "" -echo "🎉 Release ready! Next steps:" +echo "🎉 Production release ready! Next steps:" echo " 1. Test the DMG on another Mac" echo " 2. Create a GitHub release with tag v${VERSION}" echo " 3. Upload the DMG to the GitHub release" diff --git a/scripts/verify_codesigning.sh b/scripts/verify_codesigning.sh new file mode 100755 index 0000000..b4b2752 --- /dev/null +++ b/scripts/verify_codesigning.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# Verify Code Signing Setup for Meetingnotes +# This script checks if your Apple Developer credentials are properly configured + +set -e + +echo "🔍 Code Signing Verification" +echo "============================" +echo "" + +# Check environment variables +echo "📋 Environment Variables:" + +if [ -n "$DEVELOPER_ID" ]; then + echo " ✅ DEVELOPER_ID: $DEVELOPER_ID" +else + echo " ❌ DEVELOPER_ID: Not set (REQUIRED)" +fi + +if [ -n "$APPLE_ID" ]; then + echo " ✅ APPLE_ID: $APPLE_ID" +else + echo " ❌ APPLE_ID: Not set (REQUIRED for notarization)" +fi + +if [ -n "$TEAM_ID" ]; then + echo " ✅ TEAM_ID: $TEAM_ID" +else + echo " ❌ TEAM_ID: Not set (REQUIRED)" +fi + +if [ -n "$APP_PASSWORD" ]; then + echo " ✅ APP_PASSWORD: $APP_PASSWORD" +else + echo " ❌ APP_PASSWORD: Not set (REQUIRED for notarization)" +fi + +echo "" + +# Check certificates +echo "🔍 Available Certificates:" +DEVELOPER_ID_CERTS=$(security find-identity -v -p codesigning | grep "Developer ID Application" || true) +if [ -n "$DEVELOPER_ID_CERTS" ]; then + echo " ✅ Developer ID Application certificates found:" + echo "$DEVELOPER_ID_CERTS" | sed 's/^/ /' +else + echo " ❌ No Developer ID Application certificates found" +fi + +echo "" + +# Validate certificate matches expected +if [ -n "$DEVELOPER_ID_CERTS" ]; then + echo " ✅ Developer ID Application certificates are available" +else + echo " ❌ No Developer ID Application certificates found" + echo " Install your certificate from Apple Developer portal" +fi + +echo "" + +# Check notarytool +echo "🔍 Notarization Tools:" +if command -v xcrun &> /dev/null; then + if xcrun --find notarytool &> /dev/null; then + echo " ✅ notarytool available" + else + echo " ❌ notarytool not found (requires Xcode 13+)" + fi +else + echo " ❌ xcrun not available" +fi + +echo "" + +# Check entitlements file +echo "🔍 Entitlements File:" +if [ -f "meetingnotes/meetingnotes.entitlements" ]; then + echo " ✅ meetingnotes.entitlements found" +else + echo " ❌ meetingnotes.entitlements not found" +fi + +echo "" + +# Overall status +echo "📊 Overall Status:" +CERT_OK=$(echo "$DEVELOPER_ID_CERTS" | grep -q "Developer ID Application" && echo "true" || echo "false") +CREDS_OK=$([ -n "$DEVELOPER_ID" ] && [ -n "$APPLE_ID" ] && [ -n "$TEAM_ID" ] && [ -n "$APP_PASSWORD" ] && echo "true" || echo "false") + +if [ "$CERT_OK" = "true" ] && [ "$CREDS_OK" = "true" ]; then + echo " 🎉 Ready for production builds with notarization!" +elif [ "$CERT_OK" = "true" ]; then + echo " ⚠️ Certificate installed, but missing environment variables" +else + echo " ❌ Missing certificate or environment variables" +fi + +echo "" +echo "🚀 Next Steps:" +if [ "$CERT_OK" = "false" ]; then + echo " 1. Install your Developer ID certificate in Keychain" + echo " 2. Run: ./scripts/setup_codesigning.sh" + echo " 3. Set up your .env file with credentials" +elif [ "$CREDS_OK" = "false" ]; then + echo " 1. Run: ./scripts/setup_codesigning.sh" + echo " 2. Create and configure your .env file" + echo " 3. Test: source .env && ./scripts/verify_codesigning.sh" +else + echo " 1. Run: source .env && ./scripts/build_release.sh" + echo " 2. Test the resulting DMG on another Mac" + echo " 3. Upload to GitHub releases" +fi + +echo "" +echo "💡 Quick setup with .env file:" +echo " 1. Copy .env.template to .env: cp .env.template .env" +echo " 2. Edit .env with your Apple ID and app-specific password" +echo " 3. Load and verify: source .env && ./scripts/verify_codesigning.sh" \ No newline at end of file