Files
shotscreen/build_release_signed.sh
Nick Roodenrijs 0dabed11d2 🎉 ShotScreen v1.0 - Initial Release
🚀 First official release of ShotScreen with complete feature set:

 Core Features:
- Advanced screenshot capture system
- Multi-monitor support
- Professional UI/UX design
- Automated update system with Sparkle
- Apple notarized & code signed

🛠 Technical Excellence:
- Native Swift macOS application
- Professional build & deployment pipeline
- Comprehensive error handling
- Memory optimized performance

📦 Distribution Ready:
- Professional DMG packaging
- Apple notarization complete
- No security warnings for users
- Ready for public distribution

This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
2025-06-28 16:15:15 +02:00

373 lines
12 KiB
Bash
Executable File

#!/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!"