#!/bin/bash # Archipelago Production macOS Build Script # Creates a production-ready .app bundle and .dmg installer set -e PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_NAME="Archipelago" APP_VERSION="${ARCHIPELAGO_VERSION:-0.1.0}" BUILD_DIR="$PROJECT_ROOT/build/macos" APP_BUNDLE="$BUILD_DIR/$APP_NAME.app" DMG_NAME="Archipelago-${APP_VERSION}-macOS.dmg" echo "๐Ÿ—๏ธ Archipelago macOS Production Build" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo " Version: $APP_VERSION" echo " Target: macOS App Bundle + DMG Installer" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "" # Clean previous build echo "๐Ÿงน Cleaning previous build..." rm -rf "$BUILD_DIR" mkdir -p "$BUILD_DIR" # Step 1: Build Rust Backend (Release Mode) echo "" echo "โš™๏ธ Step 1/6: Building Rust backend (release mode)..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" cd "$PROJECT_ROOT/core" cargo build --release --workspace if [ ! -f "target/release/archipelago" ]; then echo "โŒ Backend build failed - archipelago binary not found" exit 1 fi # Get binary size BACKEND_SIZE=$(du -h target/release/archipelago | cut -f1) echo "โœ… Backend built successfully ($BACKEND_SIZE)" # Step 2: Build Vue.js Frontend (Production Mode) echo "" echo "๐ŸŽจ Step 2/6: Building Vue.js frontend (production mode)..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" cd "$PROJECT_ROOT/neode-ui" # Check if node_modules exists if [ ! -d "node_modules" ]; then echo "๐Ÿ“ฆ Installing frontend dependencies..." npm install fi # Build production frontend npm run build if [ ! -d "dist" ]; then echo "โŒ Frontend build failed - dist directory not found" exit 1 fi # Get build size FRONTEND_SIZE=$(du -sh dist | cut -f1) echo "โœ… Frontend built successfully ($FRONTEND_SIZE)" # Step 3: Create macOS App Bundle Structure echo "" echo "๐Ÿ“ฆ Step 3/6: Creating macOS app bundle..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" # Create standard macOS .app directory structure mkdir -p "$APP_BUNDLE/Contents/MacOS" mkdir -p "$APP_BUNDLE/Contents/Resources" mkdir -p "$APP_BUNDLE/Contents/Frameworks" # Copy backend binary echo " โ€ข Copying backend binary..." cp "$PROJECT_ROOT/core/target/release/archipelago" "$APP_BUNDLE/Contents/MacOS/" chmod +x "$APP_BUNDLE/Contents/MacOS/archipelago" # Copy frontend build echo " โ€ข Copying frontend assets..." cp -R "$PROJECT_ROOT/neode-ui/dist" "$APP_BUNDLE/Contents/Resources/frontend" # Copy Docker UI assets echo " โ€ข Copying Docker UI assets..." mkdir -p "$APP_BUNDLE/Contents/Resources/docker-ui" cp -R "$PROJECT_ROOT/docker/bitcoin-ui" "$APP_BUNDLE/Contents/Resources/docker-ui/" cp -R "$PROJECT_ROOT/docker/lnd-ui" "$APP_BUNDLE/Contents/Resources/docker-ui/" # Copy configuration templates echo " โ€ข Copying configuration..." cp "$PROJECT_ROOT/core/.env.example" "$APP_BUNDLE/Contents/Resources/env.template" cp "$PROJECT_ROOT/core/.env.production" "$APP_BUNDLE/Contents/Resources/env.production" # Copy docker-compose.yml for production echo " โ€ข Copying Docker configuration..." cp "$PROJECT_ROOT/docker-compose.yml" "$APP_BUNDLE/Contents/Resources/" cp "$PROJECT_ROOT/manage-docker.sh" "$APP_BUNDLE/Contents/MacOS/" chmod +x "$APP_BUNDLE/Contents/MacOS/manage-docker.sh" # Create launch script echo " โ€ข Creating launcher script..." cat > "$APP_BUNDLE/Contents/MacOS/launch.sh" << 'LAUNCH_EOF' #!/bin/bash # Archipelago macOS Launcher # Get the directory containing this script BUNDLE_DIR="$(cd "$(dirname "$0")/.." && pwd)" RESOURCES_DIR="$BUNDLE_DIR/Resources" MACOS_DIR="$BUNDLE_DIR/MacOS" # Set up data directory in user's home DATA_DIR="$HOME/Library/Application Support/Archipelago" mkdir -p "$DATA_DIR/data" mkdir -p "$DATA_DIR/logs" # Export environment variables export ARCHIPELAGO_DATA_DIR="$DATA_DIR/data" export ARCHIPELAGO_FRONTEND_DIR="$RESOURCES_DIR/frontend" export ARCHIPELAGO_DOCKER_UI_DIR="$RESOURCES_DIR/docker-ui" export ARCHIPELAGO_LOG_DIR="$DATA_DIR/logs" export RUST_LOG="${RUST_LOG:-info}" # Launch backend cd "$DATA_DIR" exec "$MACOS_DIR/archipelago" > "$DATA_DIR/logs/archipelago.log" 2>&1 LAUNCH_EOF chmod +x "$APP_BUNDLE/Contents/MacOS/launch.sh" # Step 4: Create Info.plist echo "" echo "๐Ÿ“„ Step 4/6: Creating app metadata..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" cat > "$APP_BUNDLE/Contents/Info.plist" << PLIST_EOF CFBundleName Archipelago CFBundleDisplayName Archipelago CFBundleIdentifier com.archipelago.app CFBundleVersion $APP_VERSION CFBundleShortVersionString $APP_VERSION CFBundlePackageType APPL CFBundleSignature ARCH CFBundleExecutable launch.sh CFBundleIconFile AppIcon NSHighResolutionCapable LSMinimumSystemVersion 10.15 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright Copyright ยฉ 2026 Archipelago. All rights reserved. LSBackgroundOnly NSRequiresAquaSystemAppearance NSSupportsAutomaticGraphicsSwitching PLIST_EOF echo "โœ… Info.plist created" # Create PkgInfo echo -n "APPLARCH" > "$APP_BUNDLE/Contents/PkgInfo" # Step 5: Create App Icon (placeholder - user should provide real icon) echo "" echo "๐ŸŽจ Step 5/6: Creating app icon..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" # Check if sips command is available (macOS built-in) if command -v sips >/dev/null 2>&1; then # Try to find a logo to convert LOGO_SOURCE="" if [ -f "$PROJECT_ROOT/neode-ui/public/assets/img/logo.png" ]; then LOGO_SOURCE="$PROJECT_ROOT/neode-ui/public/assets/img/logo.png" elif [ -f "$PROJECT_ROOT/neode-ui/public/assets/img/app-icons/bitcoin-core.png" ]; then LOGO_SOURCE="$PROJECT_ROOT/neode-ui/public/assets/img/app-icons/bitcoin-core.png" fi if [ -n "$LOGO_SOURCE" ]; then # Create iconset ICONSET_DIR="$BUILD_DIR/AppIcon.iconset" mkdir -p "$ICONSET_DIR" # Generate icon sizes for size in 16 32 128 256 512; do sips -z $size $size "$LOGO_SOURCE" --out "$ICONSET_DIR/icon_${size}x${size}.png" >/dev/null 2>&1 sips -z $((size*2)) $((size*2)) "$LOGO_SOURCE" --out "$ICONSET_DIR/icon_${size}x${size}@2x.png" >/dev/null 2>&1 done # Convert to icns iconutil -c icns "$ICONSET_DIR" -o "$APP_BUNDLE/Contents/Resources/AppIcon.icns" 2>/dev/null || { echo "โš ๏ธ Icon conversion failed - app will use default icon" } rm -rf "$ICONSET_DIR" echo "โœ… App icon created from $LOGO_SOURCE" else echo "โš ๏ธ No logo found - app will use default icon" echo " Add logo.png to neode-ui/public/assets/img/ and rebuild" fi else echo "โš ๏ธ sips not available - skipping icon creation" fi # Step 6: Create DMG Installer echo "" echo "๐Ÿ’ฟ Step 6/6: Creating DMG installer..." echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" DMG_TEMP_DIR="$BUILD_DIR/dmg" mkdir -p "$DMG_TEMP_DIR" # Copy app to DMG staging cp -R "$APP_BUNDLE" "$DMG_TEMP_DIR/" # Create Applications symlink ln -s /Applications "$DMG_TEMP_DIR/Applications" # Create DMG hdiutil create -volname "Archipelago $APP_VERSION" \ -srcfolder "$DMG_TEMP_DIR" \ -ov -format UDZO \ "$BUILD_DIR/$DMG_NAME" 2>/dev/null || { echo "โš ๏ธ DMG creation failed - using app bundle only" } # Cleanup rm -rf "$DMG_TEMP_DIR" # Summary echo "" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "โœ… Production build complete!" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" echo "" echo "๐Ÿ“ฆ Build artifacts:" echo " โ€ข App Bundle: $APP_BUNDLE" if [ -f "$BUILD_DIR/$DMG_NAME" ]; then DMG_SIZE=$(du -h "$BUILD_DIR/$DMG_NAME" | cut -f1) echo " โ€ข DMG Installer: $BUILD_DIR/$DMG_NAME ($DMG_SIZE)" fi echo "" echo "๐Ÿ“‹ Build summary:" echo " โ€ข Backend: $BACKEND_SIZE (Rust)" echo " โ€ข Frontend: $FRONTEND_SIZE (Vue.js)" BUNDLE_SIZE=$(du -sh "$APP_BUNDLE" | cut -f1) echo " โ€ข Total Bundle: $BUNDLE_SIZE" echo "" echo "๐Ÿš€ Next steps:" echo " 1. Test the app:" echo " open \"$APP_BUNDLE\"" echo "" echo " 2. Install system-wide:" echo " cp -R \"$APP_BUNDLE\" /Applications/" echo "" echo " 3. Distribute via DMG:" if [ -f "$BUILD_DIR/$DMG_NAME" ]; then echo " โ€ข Share: $BUILD_DIR/$DMG_NAME" else echo " โ€ข (DMG creation skipped - use app bundle directly)" fi echo "" echo " 4. Code signing (optional but recommended):" echo " codesign --deep --force --verify --verbose \\ --sign \"Developer ID Application: Your Name\" \\ \"$APP_BUNDLE\"" echo "" echo "๐Ÿ’ก For notarization (macOS 10.14.5+):" echo " โ€ข Requires Apple Developer account" echo " โ€ข Use: xcrun notarytool submit $DMG_NAME ..." echo ""