#!/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 and DESCRIPTION might not be directly used by AppImage tools but passed for consistency echo "--- Starting AppImage Build ---" echo "Version: $VERSION" echo "Architecture: $ARCHITECTURE" echo "Work Directory: $WORK_DIR" echo "App Staging Directory: $APP_STAGING_DIR" echo "Package Name: $PACKAGE_NAME" COMPONENT_ID="io.github.aaddrick.claude-desktop-debian" # Define AppDir structure path APPDIR_PATH="$WORK_DIR/${COMPONENT_ID}.AppDir" rm -rf "$APPDIR_PATH" mkdir -p "$APPDIR_PATH/usr/bin" mkdir -p "$APPDIR_PATH/usr/lib" mkdir -p "$APPDIR_PATH/usr/share/icons/hicolor/256x256/apps" mkdir -p "$APPDIR_PATH/usr/share/applications" echo "📦 Staging application files into AppDir..." # Copy the core application files (asar, unpacked resources, node_modules if present) # Explicitly copy required components to ensure hidden files/dirs like .bin are included if [ -f "$APP_STAGING_DIR/app.asar" ]; then cp -a "$APP_STAGING_DIR/app.asar" "$APPDIR_PATH/usr/lib/" fi if [ -d "$APP_STAGING_DIR/app.asar.unpacked" ]; then cp -a "$APP_STAGING_DIR/app.asar.unpacked" "$APPDIR_PATH/usr/lib/" fi if [ -d "$APP_STAGING_DIR/node_modules" ]; then echo "Copying node_modules from staging to AppDir..." cp -a "$APP_STAGING_DIR/node_modules" "$APPDIR_PATH/usr/lib/" fi # Ensure Electron is bundled within the AppDir for portability # Check if electron was copied into the staging dir's node_modules # The actual executable is usually inside the 'dist' directory BUNDLED_ELECTRON_PATH="$APPDIR_PATH/usr/lib/node_modules/electron/dist/electron" echo "Checking for executable at: $BUNDLED_ELECTRON_PATH" if [ ! -x "$BUNDLED_ELECTRON_PATH" ]; then # Check if it exists and is executable echo "❌ Electron executable not found or not executable in staging area ($BUNDLED_ELECTRON_PATH)." echo " AppImage requires Electron to be bundled. Ensure the main script copies it correctly." exit 1 fi # Ensure the bundled electron is executable (redundant check, but safe) chmod +x "$BUNDLED_ELECTRON_PATH" # --- Create AppRun Script --- echo "🚀 Creating AppRun script..." # Note: We use $VERSION and $PACKAGE_NAME from the build script environment here # They will be embedded into the AppRun script. cat > "$APPDIR_PATH/AppRun" << EOF #!/bin/bash set -e # Find the location of the AppRun script and the AppImage file itself APPDIR=\$(dirname "\$0") # Try to get the absolute path of the AppImage file being run # $APPIMAGE is often set by the AppImage runtime, otherwise try readlink APPIMAGE_PATH="\${APPIMAGE:-}" if [ -z "\$APPIMAGE_PATH" ]; then # Find the AppRun script itself, which should be $0 # Use readlink -f to get the absolute path, handling symlinks # Go up one level from AppRun's dir to get the AppImage path (usually) # This might be fragile if AppRun is not at the root, but it's standard. APPIMAGE_PATH=\$(readlink -f "\$APPDIR/../$(basename "$APPDIR" .AppDir).AppImage" 2>/dev/null || readlink -f "\$0" 2>/dev/null) # As a final fallback, just use $0, hoping it's the AppImage path if [ -z "\$APPIMAGE_PATH" ] || [ ! -f "\$APPIMAGE_PATH" ]; then APPIMAGE_PATH="\$0" fi fi # --- Desktop Integration (Handled by Gear Lever) --- # The bundled .desktop file (claude-desktop-appimage.desktop) inside the AppImage # contains the necessary MimeType=x-scheme-handler/claude; entry. # Gear Lever (or similar tools) will use this file to integrate # the AppImage with the system, including setting up the URI scheme handler, # if the user chooses to integrate. No manual registration is needed here. # --- End Desktop Integration --- # Set up environment variables if needed (e.g., LD_LIBRARY_PATH) # export LD_LIBRARY_PATH="\$APPDIR/usr/lib:\$LD_LIBRARY_PATH" export ELECTRON_FORCE_IS_PACKAGED=true # Detect if Wayland is likely running IS_WAYLAND=false if [ ! -z "\$WAYLAND_DISPLAY" ]; then IS_WAYLAND=true fi # Path to the bundled Electron executable # Use the path relative to AppRun within the 'electron/dist' module directory ELECTRON_EXEC="\$APPDIR/usr/lib/node_modules/electron/dist/electron" APP_PATH="\$APPDIR/usr/lib/app.asar" # Base command arguments array # Add --no-sandbox flag to avoid sandbox issues within AppImage ELECTRON_ARGS=("--no-sandbox" "\$APP_PATH") # Add Wayland flags if Wayland is detected if [ "\$IS_WAYLAND" = true ]; then echo "AppRun: Wayland detected, adding flags." ELECTRON_ARGS+=("--enable-features=UseOzonePlatform,WaylandWindowDecorations,GlobalShortcutsPortal") ELECTRON_ARGS+=("--ozone-platform=wayland") ELECTRON_ARGS+=("--enable-wayland-ime") ELECTRON_ARGS+=("--wayland-text-input-version=3") fi # Change to the application resources directory (where app.asar is) cd "\$APPDIR/usr/lib" || exit 1 # Define log file path in user's home directory LOG_FILE="\$HOME/claude-desktop-launcher.log" # Change to HOME directory before exec'ing Electron to avoid CWD permission issues cd "\$HOME" || exit 1 # Execute Electron with app path, flags, and script arguments passed to AppRun # Redirect stdout and stderr to the log file (append) echo "AppRun: Executing \$ELECTRON_EXEC \${ELECTRON_ARGS[@]} \$@ >> \$LOG_FILE 2>&1" exec "\$ELECTRON_EXEC" "\${ELECTRON_ARGS[@]}" "\$@" >> "\$LOG_FILE" 2>&1 EOF chmod +x "$APPDIR_PATH/AppRun" echo "✓ AppRun script created (with logging to \$HOME/claude-desktop-launcher.log, --no-sandbox, and CWD set to \$HOME)" # --- Create Desktop Entry (Bundled inside AppDir) --- echo "📝 Creating bundled desktop entry..." # This is the desktop file *inside* the AppImage, used by tools like appimaged cat > "$APPDIR_PATH/$COMPONENT_ID.desktop" << EOF [Desktop Entry] Name=Claude Exec=AppRun %u Icon=$COMPONENT_ID Type=Application Terminal=false Categories=Network;Utility; Comment=Claude Desktop for Linux MimeType=x-scheme-handler/claude; StartupWMClass=Claude X-AppImage-Version=$VERSION X-AppImage-Name=Claude Desktop EOF # Also place it in the standard location for tools like appimaged and validation mkdir -p "$APPDIR_PATH/usr/share/applications" cp "$APPDIR_PATH/$COMPONENT_ID.desktop" "$APPDIR_PATH/usr/share/applications/" echo "✓ Bundled desktop entry created and copied to usr/share/applications/" # --- Copy Icons --- echo "🎨 Copying icons..." # Use the 256x256 icon as the main AppImage icon ICON_SOURCE_PATH="$WORK_DIR/claude_6_256x256x32.png" if [ -f "$ICON_SOURCE_PATH" ]; then # Standard location within AppDir cp "$ICON_SOURCE_PATH" "$APPDIR_PATH/usr/share/icons/hicolor/256x256/apps/${COMPONENT_ID}.png" # Top-level icon (used by appimagetool) - Should match the Icon field in the .desktop file cp "$ICON_SOURCE_PATH" "$APPDIR_PATH/${COMPONENT_ID}.png" # Top-level icon without extension (fallback for some tools) cp "$ICON_SOURCE_PATH" "$APPDIR_PATH/${COMPONENT_ID}" # Hidden .DirIcon (fallback for some systems/tools) cp "$ICON_SOURCE_PATH" "$APPDIR_PATH/.DirIcon" echo "✓ Icon copied to standard path, top-level (.png and no ext), and .DirIcon" else echo "Warning: Missing 256x256 icon at $ICON_SOURCE_PATH. AppImage icon might be missing." fi # --- Create AppStream Metadata --- echo "📄 Creating AppStream metadata..." METADATA_DIR="$APPDIR_PATH/usr/share/metainfo" mkdir -p "$METADATA_DIR" # Use the package name for the appdata file name (seems required by appimagetool warning) # Use reverse-DNS for component ID and filename, following common practice APPDATA_FILE="$METADATA_DIR/${COMPONENT_ID}.appdata.xml" # Filename matches component ID # Generate the AppStream XML file # Use MIT license based on LICENSE-MIT file in repo # ID follows reverse DNS convention cat > "$APPDATA_FILE" << EOF $COMPONENT_ID CC0-1.0 MIT aaddrick Claude Desktop Unofficial desktop client for Claude AI

Provides a desktop experience for interacting with Claude AI, wrapping the web interface.

${COMPONENT_ID}.desktop ${COMPONENT_ID} https://github.com/aaddrick/claude-desktop-debian https://github.com/user-attachments/assets/93080028-6f71-48bd-8e59-5149d148cd45 AppRun Network Utility

Version $VERSION.

EOF echo "✓ AppStream metadata created at $APPDATA_FILE" # --- Get appimagetool --- APPIMAGETOOL_PATH="" if command -v appimagetool &> /dev/null; then APPIMAGETOOL_PATH=$(command -v appimagetool) echo "✓ Found appimagetool in PATH: $APPIMAGETOOL_PATH" elif [ -f "$WORK_DIR/appimagetool-x86_64.AppImage" ]; then # Check for specific arch first APPIMAGETOOL_PATH="$WORK_DIR/appimagetool-x86_64.AppImage" echo "✓ Found downloaded x86_64 appimagetool: $APPIMAGETOOL_PATH" elif [ -f "$WORK_DIR/appimagetool-aarch64.AppImage" ]; then # Check for other arch APPIMAGETOOL_PATH="$WORK_DIR/appimagetool-aarch64.AppImage" echo "✓ Found downloaded aarch64 appimagetool: $APPIMAGETOOL_PATH" else echo "🛠️ Downloading appimagetool..." # Determine architecture for download URL TOOL_ARCH="" case "$ARCHITECTURE" in # Use target ARCHITECTURE passed to script "amd64") TOOL_ARCH="x86_64" ;; "arm64") TOOL_ARCH="aarch64" ;; *) echo "❌ Unsupported architecture for appimagetool download: $ARCHITECTURE"; exit 1 ;; esac APPIMAGETOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${TOOL_ARCH}.AppImage" APPIMAGETOOL_PATH="$WORK_DIR/appimagetool-${TOOL_ARCH}.AppImage" if wget -q -O "$APPIMAGETOOL_PATH" "$APPIMAGETOOL_URL"; then chmod +x "$APPIMAGETOOL_PATH" echo "✓ Downloaded appimagetool to $APPIMAGETOOL_PATH" else echo "❌ Failed to download appimagetool from $APPIMAGETOOL_URL" rm -f "$APPIMAGETOOL_PATH" # Clean up partial download exit 1 fi fi # --- Build AppImage --- echo "📦 Building AppImage..." OUTPUT_FILENAME="${PACKAGE_NAME}-${VERSION}-${ARCHITECTURE}.AppImage" OUTPUT_PATH="$WORK_DIR/$OUTPUT_FILENAME" # --- Prepare Update Information (GitHub Actions only) --- # Check if running in GitHub Actions workflow if [ "$GITHUB_ACTIONS" = "true" ]; then echo "🔄 Running in GitHub Actions - embedding update information for automatic updates..." # Check if zsyncmake is available (required for generating .zsync files) if ! command -v zsyncmake &> /dev/null; then echo "⚠️ zsyncmake not found. Installing zsync package for .zsync file generation..." if command -v apt-get &> /dev/null; then sudo apt-get update && sudo apt-get install -y zsync elif command -v dnf &> /dev/null; then sudo dnf install -y zsync elif command -v zypper &> /dev/null; then sudo zypper install -y zsync else echo "⚠️ Cannot install zsync automatically. .zsync files may not be generated." fi fi # Format: gh-releases-zsync|||| # Using 'latest' tag to always point to the most recent release UPDATE_INFO="gh-releases-zsync|aaddrick|claude-desktop-debian|latest|claude-desktop-*-${ARCHITECTURE}.AppImage.zsync" echo "Update info: $UPDATE_INFO" # Execute appimagetool with update information export ARCH="$ARCHITECTURE" echo "Using ARCH=$ARCH" # Debug output if "$APPIMAGETOOL_PATH" --updateinformation "$UPDATE_INFO" "$APPDIR_PATH" "$OUTPUT_PATH"; then echo "✓ AppImage built successfully with embedded update info: $OUTPUT_PATH" # Check if zsync file was generated ZSYNC_FILE="${OUTPUT_PATH}.zsync" if [ -f "$ZSYNC_FILE" ]; then echo "✓ zsync file generated: $ZSYNC_FILE" echo "📤 zsync file will be included in release artifacts" else echo "⚠️ zsync file not generated (zsyncmake may not be installed)" fi else echo "❌ Failed to build AppImage using $APPIMAGETOOL_PATH" exit 1 fi else echo "🏠 Running locally - building AppImage without update information" echo " (Update info and zsync files are only generated in GitHub Actions for releases)" # Execute appimagetool without update information export ARCH="$ARCHITECTURE" echo "Using ARCH=$ARCH" # Debug output if "$APPIMAGETOOL_PATH" "$APPDIR_PATH" "$OUTPUT_PATH"; then echo "✓ AppImage built successfully: $OUTPUT_PATH" else echo "❌ Failed to build AppImage using $APPIMAGETOOL_PATH" exit 1 fi fi echo "--- AppImage Build Finished ---" exit 0