#!/bin/bash set -e # Arguments passed from the main script VERSION="$1" ARCHITECTURE="$2" WORK_DIR="$3" # The top-level build directory (e.g., ./build) APP_STAGING_DIR="$4" # Directory containing the prepared app files (e.g., ./build/electron-app) PACKAGE_NAME="$5" MAINTAINER="$6" DESCRIPTION="$7" echo "--- Starting Debian Package Build ---" echo "Version: $VERSION" echo "Architecture: $ARCHITECTURE" echo "Work Directory: $WORK_DIR" echo "App Staging Directory: $APP_STAGING_DIR" echo "Package Name: $PACKAGE_NAME" PACKAGE_ROOT="$WORK_DIR/package" INSTALL_DIR="$PACKAGE_ROOT/usr" # Clean previous package structure if it exists rm -rf "$PACKAGE_ROOT" # Create Debian package structure echo "Creating package structure in $PACKAGE_ROOT..." mkdir -p "$PACKAGE_ROOT/DEBIAN" mkdir -p "$INSTALL_DIR/lib/$PACKAGE_NAME" mkdir -p "$INSTALL_DIR/share/applications" mkdir -p "$INSTALL_DIR/share/icons" mkdir -p "$INSTALL_DIR/bin" # --- Icon Installation --- echo "🎨 Installing icons..." # Map icon sizes to their corresponding extracted files (relative to WORK_DIR) declare -A icon_files=( ["16"]="claude_13_16x16x32.png" ["24"]="claude_11_24x24x32.png" ["32"]="claude_10_32x32x32.png" ["48"]="claude_8_48x48x32.png" ["64"]="claude_7_64x64x32.png" ["256"]="claude_6_256x256x32.png" ) for size in 16 24 32 48 64 256; do icon_dir="$INSTALL_DIR/share/icons/hicolor/${size}x${size}/apps" mkdir -p "$icon_dir" icon_source_path="$WORK_DIR/${icon_files[$size]}" if [ -f "$icon_source_path" ]; then echo "Installing ${size}x${size} icon from $icon_source_path..." install -Dm 644 "$icon_source_path" "$icon_dir/claude-desktop.png" else echo "Warning: Missing ${size}x${size} icon at $icon_source_path" fi done echo "✓ Icons installed" # --- Copy Application Files --- echo "📦 Copying application files from $APP_STAGING_DIR..." cp "$APP_STAGING_DIR/app.asar" "$INSTALL_DIR/lib/$PACKAGE_NAME/" cp -r "$APP_STAGING_DIR/app.asar.unpacked" "$INSTALL_DIR/lib/$PACKAGE_NAME/" # Copy local electron if it was packaged (check if node_modules exists in staging) if [ -d "$APP_STAGING_DIR/node_modules" ]; then echo "Copying packaged electron..." cp -r "$APP_STAGING_DIR/node_modules" "$INSTALL_DIR/lib/$PACKAGE_NAME/" fi echo "✓ Application files copied" # --- Create Desktop Entry --- echo "📝 Creating desktop entry..." cat > "$INSTALL_DIR/share/applications/claude-desktop.desktop" << EOF [Desktop Entry] Name=Claude Exec=/usr/bin/claude-desktop %u Icon=claude-desktop Type=Application Terminal=false Categories=Office;Utility; MimeType=x-scheme-handler/claude; StartupWMClass=Claude EOF echo "✓ Desktop entry created" # --- Create Launcher Script --- echo "🚀 Creating launcher script..." cat > "$INSTALL_DIR/bin/claude-desktop" << EOF #!/bin/bash LOG_FILE="\$HOME/claude-desktop-launcher.log" echo "--- Claude Desktop Launcher Start ---" >> "\$LOG_FILE" echo "Timestamp: \$(date)" >> "\$LOG_FILE" echo "Arguments: \$@" >> "\$LOG_FILE" export ELECTRON_FORCE_IS_PACKAGED=true # Detect if Wayland is likely running IS_WAYLAND=false if [ ! -z "\$WAYLAND_DISPLAY" ]; then IS_WAYLAND=true echo "Wayland detected" >> "\$LOG_FILE" fi # Check for display issues and set compatibility mode if needed if [ "\$IS_WAYLAND" = true ]; then echo "Setting Wayland compatibility mode..." >> "\$LOG_FILE" # Use native Wayland backend with GlobalShortcuts Portal support export ELECTRON_OZONE_PLATFORM_HINT=wayland # Keep GPU acceleration enabled for better performance echo "Wayland compatibility mode enabled (using native Wayland backend)" >> "\$LOG_FILE" elif [ -z "\$DISPLAY" ] && [ -z "\$WAYLAND_DISPLAY" ]; then echo "No display detected (TTY session) - cannot start graphical application" >> "\$LOG_FILE" # No graphical environment detected; display error message in TTY session echo "Error: Claude Desktop requires a graphical desktop environment." >&2 echo "Please run from within an X11 or Wayland session, not from a TTY." >&2 exit 1 fi # Determine Electron executable path ELECTRON_EXEC="electron" # Default to global LOCAL_ELECTRON_PATH="/usr/lib/$PACKAGE_NAME/node_modules/electron/dist/electron" # Correct path to executable if [ -f "\$LOCAL_ELECTRON_PATH" ]; then ELECTRON_EXEC="\$LOCAL_ELECTRON_PATH" echo "Using local Electron: \$ELECTRON_EXEC" >> "\$LOG_FILE" else # Check if global electron exists before declaring it as the choice if command -v electron &> /dev/null; then echo "Using global Electron: \$ELECTRON_EXEC" >> "\$LOG_FILE" else echo "Error: Electron executable not found (checked local \$LOCAL_ELECTRON_PATH and global path)." >> "\$LOG_FILE" # Log the correct path checked # Optionally, display an error to the user via zenity or kdialog if available if command -v zenity &> /dev/null; then zenity --error --text="Claude Desktop cannot start because the Electron framework is missing. Please ensure Electron is installed globally or reinstall Claude Desktop." elif command -v kdialog &> /dev/null; then kdialog --error "Claude Desktop cannot start because the Electron framework is missing. Please ensure Electron is installed globally or reinstall Claude Desktop." fi exit 1 fi fi # Base command arguments array, starting with app path APP_PATH="/usr/lib/$PACKAGE_NAME/app.asar" ELECTRON_ARGS=("\$APP_PATH") # Add compatibility flags if [ "\$IS_WAYLAND" = true ]; then echo "Adding compatibility flags for Wayland session" >> "\$LOG_FILE" ELECTRON_ARGS+=("--no-sandbox") # Enable Wayland features for Electron 37+ ELECTRON_ARGS+=("--enable-features=UseOzonePlatform,WaylandWindowDecorations,GlobalShortcutsPortal") ELECTRON_ARGS+=("--ozone-platform=wayland") ELECTRON_ARGS+=("--enable-wayland-ime") ELECTRON_ARGS+=("--wayland-text-input-version=3") echo "Enabled native Wayland support with GlobalShortcuts Portal" >> "\$LOG_FILE" fi # Change to the application directory APP_DIR="/usr/lib/$PACKAGE_NAME" echo "Changing directory to \$APP_DIR" >> "\$LOG_FILE" cd "\$APP_DIR" || { echo "Failed to cd to \$APP_DIR" >> "\$LOG_FILE"; exit 1; } # Execute Electron with app path, flags, and script arguments # Redirect stdout and stderr to the log file FINAL_CMD="\"\$ELECTRON_EXEC\" \"\${ELECTRON_ARGS[@]}\" \"\$@\"" echo "Executing: \$FINAL_CMD" >> "\$LOG_FILE" "\$ELECTRON_EXEC" "\${ELECTRON_ARGS[@]}" "\$@" >> "\$LOG_FILE" 2>&1 EXIT_CODE=\$? echo "Electron exited with code: \$EXIT_CODE" >> "\$LOG_FILE" echo "--- Claude Desktop Launcher End ---" >> "\$LOG_FILE" exit \$EXIT_CODE EOF chmod +x "$INSTALL_DIR/bin/claude-desktop" echo "✓ Launcher script created" # --- Create Control File --- echo "📄 Creating control file..." # Determine dependencies based on whether electron was packaged DEPENDS="nodejs, npm, p7zip-full" # Base dependencies # Electron is now always packaged locally, so it's not listed as an external dependency. echo "Electron is packaged locally; not adding to external Depends list." cat > "$PACKAGE_ROOT/DEBIAN/control" << EOF Package: $PACKAGE_NAME Version: $VERSION Architecture: $ARCHITECTURE Maintainer: $MAINTAINER Depends: $DEPENDS Description: $DESCRIPTION Claude is an AI assistant from Anthropic. This package provides the desktop interface for Claude. . Supported on Debian-based Linux distributions (Debian, Ubuntu, Linux Mint, MX Linux, etc.) Requires: nodejs (>= 12.0.0), npm EOF echo "✓ Control file created" # --- Create Postinst Script --- echo "⚙️ Creating postinst script..." cat > "$PACKAGE_ROOT/DEBIAN/postinst" << EOF #!/bin/sh set -e # Update desktop database for MIME types echo "Updating desktop database..." update-desktop-database /usr/share/applications &> /dev/null || true # Set correct permissions for chrome-sandbox if electron is installed globally or locally packaged echo "Setting chrome-sandbox permissions..." SANDBOX_PATH="" # Electron is always packaged locally now, so only check the local path. LOCAL_SANDBOX_PATH="/usr/lib/$PACKAGE_NAME/node_modules/electron/dist/chrome-sandbox" # Correct path to sandbox if [ -f "\$LOCAL_SANDBOX_PATH" ]; then SANDBOX_PATH="\$LOCAL_SANDBOX_PATH" fi if [ -n "\$SANDBOX_PATH" ] && [ -f "\$SANDBOX_PATH" ]; then echo "Found chrome-sandbox at: \$SANDBOX_PATH" chown root:root "\$SANDBOX_PATH" || echo "Warning: Failed to chown chrome-sandbox" chmod 4755 "\$SANDBOX_PATH" || echo "Warning: Failed to chmod chrome-sandbox" echo "Permissions set for \$SANDBOX_PATH" else echo "Warning: chrome-sandbox binary not found in local package at \$LOCAL_SANDBOX_PATH. Sandbox may not function correctly." # Log the correct path checked fi exit 0 EOF chmod +x "$PACKAGE_ROOT/DEBIAN/postinst" echo "✓ Postinst script created" # --- Build .deb Package --- echo "📦 Building .deb package..." DEB_FILE="$WORK_DIR/${PACKAGE_NAME}_${VERSION}_${ARCHITECTURE}.deb" # Fix DEBIAN directory permissions (must be 755 for dpkg-deb) echo "Setting DEBIAN directory permissions..." chmod 755 "$PACKAGE_ROOT/DEBIAN" # Fix script permissions in DEBIAN directory echo "Setting script permissions..." chmod 755 "$PACKAGE_ROOT/DEBIAN/postinst" if ! dpkg-deb --build "$PACKAGE_ROOT" "$DEB_FILE"; then echo "❌ Failed to build .deb package" exit 1 fi echo "✓ .deb package built successfully: $DEB_FILE" echo "--- Debian Package Build Finished ---" exit 0