🎉 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.
This commit is contained in:
373
build_release_signed.sh
Executable file
373
build_release_signed.sh
Executable file
@@ -0,0 +1,373 @@
|
||||
#!/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!"
|
||||
Reference in New Issue
Block a user