#!/bin/bash # ShotScreen - Professional Build & Sign Script # This script creates a properly signed and notarized build to prevent antivirus false positives set -e # Exit on any error echo "🔨 Starting professional build process for ShotScreen..." # Configuration APP_NAME="ShotScreen" BUNDLE_ID="com.shotscreen.app" DEVELOPER_ID="Developer ID Application: Nick Roodenrijs (HX9B36NN8V)" KEYCHAIN_PROFILE="ShotScreen-Profile" # Create this with 'xcrun notarytool store-credentials' # Build paths - FIXED: Use SwiftPM standard paths like build_app.sh DIST_DIR="./dist" APP_DIR="$DIST_DIR/$APP_NAME.app" CONTENTS_DIR="$APP_DIR/Contents" MACOS_DIR="$CONTENTS_DIR/MacOS" RESOURCES_DIR="$CONTENTS_DIR/Resources" FRAMEWORKS_DIR="$CONTENTS_DIR/Frameworks" # Create dist directory rm -rf "$DIST_DIR" mkdir -p "$DIST_DIR" # Read version from Info.plist for DMG naming if [ -f "Info.plist" ]; then APP_VERSION=$(plutil -extract CFBundleShortVersionString raw Info.plist 2>/dev/null || echo "1.0") else APP_VERSION="1.4" fi DMG_PATH="./dist/$APP_NAME-$APP_VERSION.dmg" echo "📦 Building release version..." # FIXED: Use standard SwiftPM release build like build_app.sh does for debug swift build --configuration release # FIXED: Get the path to the SwiftPM built binary (like build_app.sh) BINARY_PATH=".build/release/ShotScreen" # Verify binary exists if [ ! -f "$BINARY_PATH" ]; then echo "❌ Error: Built binary not found at $BINARY_PATH" echo "Available files in .build/release/:" ls -la .build/release/ || echo "No .build/release/ directory found" exit 1 fi echo "✅ Found built binary at: $BINARY_PATH" echo "🏗️ Creating app bundle..." # Create app bundle structure (like build_app.sh) mkdir -p "$MACOS_DIR" mkdir -p "$RESOURCES_DIR" mkdir -p "$FRAMEWORKS_DIR" # Copy binary to app bundle and rename it to ShotScreen echo "Copying binary to app bundle and renaming to ShotScreen..." cp "$BINARY_PATH" "$MACOS_DIR/ShotScreen" # Verify the copy was successful if [ -f "$MACOS_DIR/ShotScreen" ]; then echo "✅ Binary successfully copied to app bundle" ls -la "$MACOS_DIR/ShotScreen" else echo "❌ Error: Failed to copy binary to app bundle" exit 1 fi # Copy Info.plist echo "Copying Info.plist..." cp Info.plist "$CONTENTS_DIR/" # Copy app icon echo "Copying app icon..." if [ -f "./AppIcon.icns" ]; then cp "./AppIcon.icns" "$RESOURCES_DIR/" echo "✅ Copied AppIcon.icns to app bundle Resources" else echo "⚠️ Warning: AppIcon.icns not found" fi # Copy resource images if [ -d "ShotScreen/Sources/images" ]; then cp -r "ShotScreen/Sources/images" "$RESOURCES_DIR/" 2>/dev/null || true echo "✅ Copied resource images" fi # Copy frameworks (using same logic as build_app.sh) echo "📦 Copying frameworks..." # Find and copy Sparkle framework SPARKLE_FRAMEWORK_PATH="" for framework_path in .build/release/*.framework .build/*/release/*.framework .build/release/PackageFrameworks/*.framework; do if [[ -d "$framework_path" && "$(basename "$framework_path")" == "Sparkle.framework" ]]; then SPARKLE_FRAMEWORK_PATH="$framework_path" break fi done if [[ -n "$SPARKLE_FRAMEWORK_PATH" && -d "$SPARKLE_FRAMEWORK_PATH" ]]; then echo "✅ Found Sparkle framework at: $SPARKLE_FRAMEWORK_PATH" cp -R "$SPARKLE_FRAMEWORK_PATH" "$FRAMEWORKS_DIR/" echo "✅ Copied Sparkle framework to app bundle" else echo "❌ Warning: Sparkle framework not found in SwiftPM build output" echo "🔍 Searching in .build directory:" find .build -name "*.framework" -type d 2>/dev/null || echo "No frameworks found" # Try alternative locations for Sparkle if [ -d ".build/arm64-apple-macosx/release/Sparkle.framework" ]; then cp -R ".build/arm64-apple-macosx/release/Sparkle.framework" "$FRAMEWORKS_DIR/" echo "✅ Copied Sparkle framework from arm64 location" else echo "❌ Sparkle framework not found!" exit 1 fi fi # Configure @rpath for framework loading (like build_app.sh) echo "🔧 Configuring @rpath for framework loading..." install_name_tool -add_rpath "@loader_path/../Frameworks" "$MACOS_DIR/ShotScreen" if [ $? -eq 0 ]; then echo "✅ Added @rpath pointing to ../Frameworks directory" else echo "❌ Failed to add @rpath configuration" fi # Create PkgInfo file echo "APPLSCR" > "$CONTENTS_DIR/PkgInfo" # Make the app bundle executable chmod +x "$MACOS_DIR/ShotScreen" echo "✍️ Code signing with hardened runtime..." # Sign frameworks first echo "🔐 Signing frameworks..." for framework in "$FRAMEWORKS_DIR"/*.framework; do if [ -d "$framework" ]; then echo " Signing framework: $(basename "$framework")" codesign --sign "$DEVELOPER_ID" \ --force \ --timestamp \ --options runtime \ "$framework" echo " ✅ Signed framework: $(basename "$framework")" fi done # Sign with hardened runtime to prevent malware-like behavior detection echo "🔐 Signing main app bundle..." codesign --sign "$DEVELOPER_ID" \ --deep \ --force \ --options runtime \ --entitlements ShotScreen/entitlements.plist \ --timestamp \ "$APP_DIR" echo "🔍 Verifying signature..." codesign --verify --deep --strict "$APP_DIR" echo "✅ Code signature is valid" # Note: spctl assessment may fail for unnotarized apps, but that's okay for local testing if spctl --assess --type exec "$APP_DIR" 2>/dev/null; then echo "✅ Gatekeeper assessment passed" else echo "⚠️ Gatekeeper assessment failed (app needs notarization for distribution)" fi echo "📦 Creating professional DMG with custom background and layout..." # FIXED: Use unique volume name to avoid conflicts with existing ShotScreen volume DMG_VOLUME_NAME="$APP_NAME-$APP_VERSION" # First unmount any existing volumes to prevent conflicts hdiutil detach "/Volumes/$DMG_VOLUME_NAME" 2>/dev/null || true # Create a temporary folder for DMG contents TEMP_DMG_DIR="./temp_dmg" rm -rf "$TEMP_DMG_DIR" mkdir -p "$TEMP_DMG_DIR" # Copy app to temp folder cp -R "$APP_DIR" "$TEMP_DMG_DIR/" # Create symlink to Applications folder echo "🔗 Creating Applications symlink..." ln -s /Applications "$TEMP_DMG_DIR/Applications" # Copy background image to temp folder echo "🎨 Adding custom background image..." if [ -f "./ShotScreen/Sources/images/BannerFinder.png" ]; then mkdir -p "$TEMP_DMG_DIR/.background" cp "./ShotScreen/Sources/images/BannerFinder.png" "$TEMP_DMG_DIR/.background/background.png" echo "✅ Background image added (BannerFinder.png)" else echo "⚠️ Warning: BannerFinder.png not found in ShotScreen/Sources/images/" fi # Create initial DMG (read-write for customization) TEMP_DMG_PATH="./temp_dmg_rw.dmg" rm -f "$TEMP_DMG_PATH" hdiutil create -volname "$DMG_VOLUME_NAME" \ -srcfolder "$TEMP_DMG_DIR" \ -ov \ -format UDRW \ -size 100m \ "$TEMP_DMG_PATH" if [ $? -ne 0 ]; then echo "❌ DMG creation failed!" echo "🔍 Checking if volume is still mounted..." mount | grep -i shotscreen || echo "No ShotScreen volumes mounted" # Clean up temp folder rm -rf "$TEMP_DMG_DIR" exit 1 fi # Mount the DMG for customization echo "🔧 Mounting DMG for customization..." hdiutil attach "$TEMP_DMG_PATH" -mountpoint "/Volumes/$DMG_VOLUME_NAME" -nobrowse # Wait for mount to complete sleep 2 # Customize DMG appearance with AppleScript echo "✨ Customizing DMG appearance with background and layout..." cat > dmg_style.applescript << 'EOF' tell application "Finder" tell disk "DMG_VOLUME_NAME_PLACEHOLDER" open -- Wait for window to open delay 3 set current view of container window to icon view set toolbar visible of container window to false set statusbar visible of container window to false -- Smaller window size (500x350 instead of 500x300) set the bounds of container window to {300, 100, 800, 450} set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 96 -- Large icons set text size of theViewOptions to 12 -- Set background picture set background picture of theViewOptions to file ".background:background.png" -- Wait for settings to apply delay 2 -- Position icons with more space set position of item "ShotScreen.app" of container window to {150, 200} set position of item "Applications" of container window to {350, 200} -- Force update and wait update without registering applications delay 3 -- Close and reopen to ensure settings stick close delay 1 open delay 2 end tell end tell EOF # Replace placeholder with actual volume name sed -i '' "s/DMG_VOLUME_NAME_PLACEHOLDER/$DMG_VOLUME_NAME/g" dmg_style.applescript # Apply the styling echo "🎨 Applying AppleScript styling..." if osascript dmg_style.applescript; then echo "✅ AppleScript styling applied successfully" else echo "⚠️ AppleScript styling failed, trying alternative method..." # Alternative: Set view manually via Finder automation cat > dmg_style_alt.applescript << 'EOF' tell application "Finder" tell disk "DMG_VOLUME_NAME_PLACEHOLDER" open delay 5 -- Simple settings that usually work set current view of container window to icon view delay 1 set theViewOptions to the icon view options of container window set icon size of theViewOptions to 96 delay 1 close delay 1 open delay 2 end tell end tell EOF # Replace placeholder and run alternative sed -i '' "s/DMG_VOLUME_NAME_PLACEHOLDER/$DMG_VOLUME_NAME/g" dmg_style_alt.applescript osascript dmg_style_alt.applescript 2>/dev/null || echo "⚠️ Alternative styling also failed" rm -f dmg_style_alt.applescript fi # Clean up AppleScript rm -f dmg_style.applescript # Force create .DS_Store with proper settings echo "📁 Setting Finder view options..." if [ -d "/Volumes/$DMG_VOLUME_NAME" ]; then # Ensure background image is accessible if [ -f "/Volumes/$DMG_VOLUME_NAME/.background/background.png" ]; then echo "✅ Background image found in DMG" else echo "⚠️ Background image not found in DMG" fi # Force Finder to acknowledge the DMG osascript -e "tell application \"Finder\" to open disk \"$DMG_VOLUME_NAME\"" 2>/dev/null || true sleep 3 # Let Finder settle and write .DS_Store sleep 5 fi # Unmount the DMG echo "📤 Unmounting DMG..." hdiutil detach "/Volumes/$DMG_VOLUME_NAME" # Convert to compressed read-only DMG echo "🗜️ Converting to final compressed DMG..." hdiutil convert "$TEMP_DMG_PATH" \ -format UDZO \ -o "$DMG_PATH" # Clean up temporary files rm -f "$TEMP_DMG_PATH" rm -rf "$TEMP_DMG_DIR" echo "✅ Professional DMG created successfully with custom background and layout!" echo "✍️ Signing DMG..." codesign --sign "$DEVELOPER_ID" \ --timestamp \ "$DMG_PATH" echo "📤 Submitting for notarization..." if xcrun notarytool submit "$DMG_PATH" --keychain-profile "$KEYCHAIN_PROFILE" --wait; then echo "✅ Notarization successful!" echo "🔍 Stapling notarization ticket to DMG..." xcrun stapler staple "$DMG_PATH" echo "✅ DMG is now notarized and ready for distribution!" else echo "⚠️ Notarization failed or timed out" echo "💡 You can check status with: xcrun notarytool history --keychain-profile $KEYCHAIN_PROFILE" echo "💡 Manual stapling: xcrun stapler staple $DMG_PATH" fi echo "" echo "✅ Build complete!" echo "📦 DMG created at: $DMG_PATH" echo "✅ Code signed with Developer ID: Nick Roodenrijs (HX9B36NN8V)" echo "🎯 DMG ready for hufterproof release script!"