🚀 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.
373 lines
12 KiB
Bash
Executable File
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!" |