refactor: apply bash style guide to all build scripts

Apply consistent bash style guide (style.ysap.sh) across all scripts:

- Change shebang to #!/usr/bin/env bash
- Remove set -e/set -euo pipefail, add explicit error handling
- Convert indentation from 4 spaces to tabs
- Use lowercase for variables, UPPERCASE only for exports/constants
- Use [[ ]] for conditionals, (( )) for arithmetic
- Use single quotes for literals, double quotes for expansion
- Remove emoji characters from output messages

Files refactored:
- build.sh (~991 lines)
- scripts/build-appimage.sh (~311 lines)
- scripts/build-deb-package.sh (~242 lines)
- scripts/launcher-common.sh (~95 lines)

Tested: AppImage build completes successfully in distrobox.

Fixes #179

Co-Authored-By: Claude <claude@anthropic.com>
This commit is contained in:
aaddrick
2026-01-22 22:05:52 -05:00
parent 2941d39d55
commit 2663f529af
4 changed files with 1132 additions and 1103 deletions

1505
build.sh

File diff suppressed because it is too large Load Diff

View File

@@ -1,168 +1,168 @@
#!/bin/bash
set -e
#!/usr/bin/env bash
# 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
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
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"
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"
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"
appdir_path="$work_dir/${component_id}.AppDir"
rm -rf "$appdir_path"
mkdir -p "$appdir_path/usr/bin" || exit 1
mkdir -p "$appdir_path/usr/lib" || exit 1
mkdir -p "$appdir_path/usr/share/icons/hicolor/256x256/apps" || exit 1
mkdir -p "$appdir_path/usr/share/applications" || exit 1
echo "📦 Staging application files into AppDir..."
echo 'Staging application files into AppDir...'
# Copy node_modules first to set up Electron directory structure
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/"
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/" || exit 1
fi
# Install app.asar in Electron's resources directory where process.resourcesPath points
RESOURCES_DIR="$APPDIR_PATH/usr/lib/node_modules/electron/dist/resources"
mkdir -p "$RESOURCES_DIR"
if [ -f "$APP_STAGING_DIR/app.asar" ]; then
cp -a "$APP_STAGING_DIR/app.asar" "$RESOURCES_DIR/"
resources_dir="$appdir_path/usr/lib/node_modules/electron/dist/resources"
mkdir -p "$resources_dir" || exit 1
if [[ -f $app_staging_dir/app.asar ]]; then
cp -a "$app_staging_dir/app.asar" "$resources_dir/" || exit 1
fi
if [ -d "$APP_STAGING_DIR/app.asar.unpacked" ]; then
cp -a "$APP_STAGING_DIR/app.asar.unpacked" "$RESOURCES_DIR/"
if [[ -d $app_staging_dir/app.asar.unpacked ]]; then
cp -a "$app_staging_dir/app.asar.unpacked" "$resources_dir/" || exit 1
fi
echo "✓ Application files copied to Electron resources directory"
echo 'Application files copied to Electron resources directory'
# Copy shared launcher library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p "$APPDIR_PATH/usr/lib/claude-desktop"
cp "$SCRIPT_DIR/launcher-common.sh" "$APPDIR_PATH/usr/lib/claude-desktop/"
echo "✓ Shared launcher library copied"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p "$appdir_path/usr/lib/claude-desktop" || exit 1
cp "$script_dir/launcher-common.sh" "$appdir_path/usr/lib/claude-desktop/" || exit 1
echo 'Shared launcher library copied'
# 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
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
echo 'Electron executable not found or not executable in staging area.' >&2
echo "Path checked: $bundled_electron_path" >&2
echo 'AppImage requires Electron to be bundled. Ensure the main script copies it correctly.' >&2
exit 1
fi
# Ensure the bundled electron is executable (redundant check, but safe)
chmod +x "$BUNDLED_ELECTRON_PATH"
chmod +x "$bundled_electron_path" || exit 1
# --- Create AppRun Script ---
echo "🚀 Creating AppRun script..."
cat > "$APPDIR_PATH/AppRun" << 'EOF'
#!/bin/bash
set -e
echo 'Creating AppRun script...'
cat > "$appdir_path/AppRun" << 'EOF'
#!/usr/bin/env bash
# Find the location of the AppRun script
APPDIR=$(dirname "$(readlink -f "$0")")
appdir=$(dirname "$(readlink -f "$0")")
# Source shared launcher library
source "$APPDIR/usr/lib/claude-desktop/launcher-common.sh"
source "$appdir/usr/lib/claude-desktop/launcher-common.sh"
# Setup logging and environment
setup_logging
setup_logging || exit 1
setup_electron_env
# Detect display backend
detect_display_backend
# Log startup info
log_message "--- Claude Desktop AppImage Start ---"
log_message '--- Claude Desktop AppImage Start ---'
log_message "Timestamp: $(date)"
log_message "Arguments: $@"
log_message "APPDIR: $APPDIR"
log_message "APPDIR: $appdir"
# Path to the bundled Electron executable and app
ELECTRON_EXEC="$APPDIR/usr/lib/node_modules/electron/dist/electron"
APP_PATH="$APPDIR/usr/lib/node_modules/electron/dist/resources/app.asar"
electron_exec="$appdir/usr/lib/node_modules/electron/dist/electron"
app_path="$appdir/usr/lib/node_modules/electron/dist/resources/app.asar"
# Build electron args (appimage mode adds --no-sandbox)
build_electron_args "appimage"
build_electron_args 'appimage'
# Add app path LAST - Chromium flags must come before this
ELECTRON_ARGS+=("$APP_PATH")
electron_args+=("$app_path")
# Change to HOME directory before exec'ing Electron to avoid CWD permission issues
cd "$HOME" || exit 1
# Execute Electron
log_message "Executing: $ELECTRON_EXEC ${ELECTRON_ARGS[*]} $*"
exec "$ELECTRON_EXEC" "${ELECTRON_ARGS[@]}" "$@" >> "$LOG_FILE" 2>&1
log_message "Executing: $electron_exec ${electron_args[*]} $*"
exec "$electron_exec" "${electron_args[@]}" "$@" >> "$log_file" 2>&1
EOF
chmod +x "$APPDIR_PATH/AppRun"
echo "✓ AppRun script created"
chmod +x "$appdir_path/AppRun" || exit 1
echo 'AppRun script created'
# --- Create Desktop Entry (Bundled inside AppDir) ---
echo "📝 Creating bundled desktop entry..."
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
cat > "$appdir_path/$component_id.desktop" << EOF
[Desktop Entry]
Name=Claude
Exec=AppRun %u
Icon=$COMPONENT_ID
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-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/"
mkdir -p "$appdir_path/usr/share/applications" || exit 1
cp "$appdir_path/$component_id.desktop" "$appdir_path/usr/share/applications/" || exit 1
echo 'Bundled desktop entry created and copied to usr/share/applications/'
# --- Copy Icons ---
echo "🎨 Copying 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"
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" || exit 1
# Top-level icon (used by appimagetool) - Should match the Icon field in .desktop
cp "$icon_source_path" "$appdir_path/${component_id}.png" || exit 1
# Top-level icon without extension (fallback for some tools)
cp "$icon_source_path" "$appdir_path/${component_id}" || exit 1
# Hidden .DirIcon (fallback for some systems/tools)
cp "$icon_source_path" "$appdir_path/.DirIcon" || exit 1
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."
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"
echo 'Creating AppStream metadata...'
metadata_dir="$appdir_path/usr/share/metainfo"
mkdir -p "$metadata_dir" || exit 1
# 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
appdata_file="$metadata_dir/${component_id}.appdata.xml"
# Generate the AppStream XML file
# Use MIT license based on LICENSE-MIT file in repo
# ID follows reverse DNS convention
cat > "$APPDATA_FILE" << EOF
cat > "$appdata_file" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>$COMPONENT_ID</id>
<id>$component_id</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<developer id="io.github.aaddrick">
@@ -178,9 +178,9 @@ cat > "$APPDATA_FILE" << EOF
</p>
</description>
<launchable type="desktop-id">${COMPONENT_ID}.desktop</launchable> <!-- Reference the actual .desktop file -->
<launchable type="desktop-id">${component_id}.desktop</launchable>
<icon type="stock">${COMPONENT_ID}</icon> <!-- Use the icon name from .desktop -->
<icon type="stock">${component_id}</icon>
<url type="homepage">https://github.com/aaddrick/claude-desktop-debian</url>
<screenshots>
<screenshot type="default">
@@ -188,7 +188,7 @@ cat > "$APPDATA_FILE" << EOF
</screenshot>
</screenshots>
<provides>
<binary>AppRun</binary> <!-- Provide the actual binary -->
<binary>AppRun</binary>
</provides>
<categories>
@@ -199,113 +199,116 @@ cat > "$APPDATA_FILE" << EOF
<content_rating type="oars-1.1" />
<releases>
<release version="$VERSION" date="$(date +%Y-%m-%d)">
<release version="$version" date="$(date +%Y-%m-%d)">
<description>
<p>Version $VERSION.</p>
<p>Version $version.</p>
</description>
</release>
</releases>
</component>
EOF
echo "AppStream metadata created at $APPDATA_FILE"
echo "AppStream metadata created at $appdata_file"
# --- Get appimagetool ---
APPIMAGETOOL_PATH=""
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"
appimagetool_path=$(command -v appimagetool)
echo "Found appimagetool in PATH: $appimagetool_path"
elif [[ -f $work_dir/appimagetool-x86_64.AppImage ]]; then
appimagetool_path="$work_dir/appimagetool-x86_64.AppImage"
echo "Found downloaded x86_64 appimagetool: $appimagetool_path"
elif [[ -f $work_dir/appimagetool-aarch64.AppImage ]]; then
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
echo 'Downloading appimagetool...'
# Determine architecture for download URL
tool_arch=''
case "$architecture" in
'amd64') tool_arch='x86_64' ;;
'arm64') tool_arch='aarch64' ;;
*)
echo "Unsupported architecture for appimagetool download: $architecture" >&2
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"
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
if wget -q -O "$appimagetool_path" "$appimagetool_url"; then
chmod +x "$appimagetool_path" || exit 1
echo "Downloaded appimagetool to $appimagetool_path"
else
echo "Failed to download appimagetool from $appimagetool_url" >&2
rm -f "$appimagetool_path"
exit 1
fi
fi
# --- Build AppImage ---
echo "📦 Building AppImage..."
OUTPUT_FILENAME="${PACKAGE_NAME}-${VERSION}-${ARCHITECTURE}.AppImage"
OUTPUT_PATH="$WORK_DIR/$OUTPUT_FILENAME"
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
if [[ $GITHUB_ACTIONS == 'true' ]]; then
echo 'Running in GitHub Actions - embedding update information for automatic updates...'
# Format: gh-releases-zsync|<username>|<repository>|<tag>|<filename-pattern>
# 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"
# 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
# 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
# Format: gh-releases-zsync|<username>|<repository>|<tag>|<filename-pattern>
# 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"
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" >&2
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
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"
if "$appimagetool_path" "$appdir_path" "$output_path"; then
echo "AppImage built successfully: $output_path"
else
echo "Failed to build AppImage using $appimagetool_path" >&2
exit 1
fi
fi
echo "--- AppImage Build Finished ---"
echo '--- AppImage Build Finished ---'
exit 0

View File

@@ -1,85 +1,84 @@
#!/bin/bash
set -e
#!/usr/bin/env bash
# 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"
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
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"
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"
package_root="$work_dir/package"
install_dir="$package_root/usr"
# Clean previous package structure if it exists
rm -rf "$PACKAGE_ROOT"
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"
echo "Creating package structure in $package_root..."
mkdir -p "$package_root/DEBIAN" || exit 1
mkdir -p "$install_dir/lib/$package_name" || exit 1
mkdir -p "$install_dir/share/applications" || exit 1
mkdir -p "$install_dir/share/icons" || exit 1
mkdir -p "$install_dir/bin" || exit 1
# --- Icon Installation ---
echo "🎨 Installing icons..."
# Map icon sizes to their corresponding extracted files (relative to WORK_DIR)
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"
['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
icon_dir="$install_dir/share/icons/hicolor/${size}x${size}/apps"
mkdir -p "$icon_dir" || exit 1
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" || exit 1
else
echo "Warning: Missing ${size}x${size} icon at $icon_source_path"
fi
done
echo "✓ Icons installed"
echo 'Icons installed'
# --- Copy Application Files ---
echo "📦 Copying application files from $APP_STAGING_DIR..."
echo "Copying application files from $app_staging_dir..."
# Copy local electron first 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/"
if [[ -d $app_staging_dir/node_modules ]]; then
echo 'Copying packaged electron...'
cp -r "$app_staging_dir/node_modules" "$install_dir/lib/$package_name/" || exit 1
fi
# Install app.asar in Electron's resources directory where process.resourcesPath points
RESOURCES_DIR="$INSTALL_DIR/lib/$PACKAGE_NAME/node_modules/electron/dist/resources"
mkdir -p "$RESOURCES_DIR"
cp "$APP_STAGING_DIR/app.asar" "$RESOURCES_DIR/"
cp -r "$APP_STAGING_DIR/app.asar.unpacked" "$RESOURCES_DIR/"
echo "✓ Application files copied to Electron resources directory"
resources_dir="$install_dir/lib/$package_name/node_modules/electron/dist/resources"
mkdir -p "$resources_dir" || exit 1
cp "$app_staging_dir/app.asar" "$resources_dir/" || exit 1
cp -r "$app_staging_dir/app.asar.unpacked" "$resources_dir/" || exit 1
echo 'Application files copied to Electron resources directory'
# Copy shared launcher library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cp "$SCRIPT_DIR/launcher-common.sh" "$INSTALL_DIR/lib/$PACKAGE_NAME/"
echo "✓ Shared launcher library copied"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cp "$script_dir/launcher-common.sh" "$install_dir/lib/$package_name/" || exit 1
echo 'Shared launcher library copied'
# --- Create Desktop Entry ---
echo "📝 Creating desktop entry..."
cat > "$INSTALL_DIR/share/applications/claude-desktop.desktop" << EOF
echo 'Creating desktop entry...'
cat > "$install_dir/share/applications/claude-desktop.desktop" << EOF
[Desktop Entry]
Name=Claude
Exec=/usr/bin/claude-desktop %u
@@ -90,106 +89,108 @@ Categories=Office;Utility;
MimeType=x-scheme-handler/claude;
StartupWMClass=Claude
EOF
echo "✓ Desktop entry created"
echo 'Desktop entry created'
# --- Create Launcher Script ---
echo "🚀 Creating launcher script..."
cat > "$INSTALL_DIR/bin/claude-desktop" << EOF
#!/bin/bash
echo 'Creating launcher script...'
cat > "$install_dir/bin/claude-desktop" << EOF
#!/usr/bin/env bash
# Source shared launcher library
source "/usr/lib/$PACKAGE_NAME/launcher-common.sh"
source "/usr/lib/$package_name/launcher-common.sh"
# Setup logging and environment
setup_logging
setup_logging || exit 1
setup_electron_env
# Log startup info
log_message "--- Claude Desktop Launcher Start ---"
log_message '--- Claude Desktop Launcher Start ---'
log_message "Timestamp: \$(date)"
log_message "Arguments: \$@"
# Check for display
if ! check_display; then
log_message "No display detected (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
log_message 'No display detected (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
# Detect display backend
detect_display_backend
if [ "\$IS_WAYLAND" = true ]; then
log_message "Wayland detected"
if [[ \$is_wayland == true ]]; then
log_message 'Wayland detected'
fi
# Determine Electron executable path
ELECTRON_EXEC="electron"
LOCAL_ELECTRON_PATH="/usr/lib/$PACKAGE_NAME/node_modules/electron/dist/electron"
if [ -f "\$LOCAL_ELECTRON_PATH" ]; then
ELECTRON_EXEC="\$LOCAL_ELECTRON_PATH"
log_message "Using local Electron: \$ELECTRON_EXEC"
electron_exec='electron'
local_electron_path="/usr/lib/$package_name/node_modules/electron/dist/electron"
if [[ -f \$local_electron_path ]]; then
electron_exec="\$local_electron_path"
log_message "Using local Electron: \$electron_exec"
else
if command -v electron &> /dev/null; then
log_message "Using global Electron: \$ELECTRON_EXEC"
else
log_message "Error: Electron executable not found"
if command -v zenity &> /dev/null; then
zenity --error --text="Claude Desktop cannot start because the Electron framework is missing."
elif command -v kdialog &> /dev/null; then
kdialog --error "Claude Desktop cannot start because the Electron framework is missing."
fi
exit 1
fi
if command -v electron &> /dev/null; then
log_message "Using global Electron: \$electron_exec"
else
log_message 'Error: Electron executable not found'
if command -v zenity &> /dev/null; then
zenity --error \
--text='Claude Desktop cannot start because the Electron framework is missing.'
elif command -v kdialog &> /dev/null; then
kdialog --error \
'Claude Desktop cannot start because the Electron framework is missing.'
fi
exit 1
fi
fi
# App path
APP_PATH="/usr/lib/$PACKAGE_NAME/node_modules/electron/dist/resources/app.asar"
app_path="/usr/lib/$package_name/node_modules/electron/dist/resources/app.asar"
# Build electron args
build_electron_args "deb"
build_electron_args 'deb'
# Add app path LAST
ELECTRON_ARGS+=("\$APP_PATH")
electron_args+=("\$app_path")
# Change to application directory
APP_DIR="/usr/lib/$PACKAGE_NAME"
log_message "Changing directory to \$APP_DIR"
cd "\$APP_DIR" || { log_message "Failed to cd to \$APP_DIR"; exit 1; }
app_dir="/usr/lib/$package_name"
log_message "Changing directory to \$app_dir"
cd "\$app_dir" || { log_message "Failed to cd to \$app_dir"; exit 1; }
# Execute Electron
log_message "Executing: \$ELECTRON_EXEC \${ELECTRON_ARGS[*]} \$*"
"\$ELECTRON_EXEC" "\${ELECTRON_ARGS[@]}" "\$@" >> "\$LOG_FILE" 2>&1
EXIT_CODE=\$?
log_message "Electron exited with code: \$EXIT_CODE"
log_message "--- Claude Desktop Launcher End ---"
exit \$EXIT_CODE
log_message "Executing: \$electron_exec \${electron_args[*]} \$*"
"\$electron_exec" "\${electron_args[@]}" "\$@" >> "\$log_file" 2>&1
exit_code=\$?
log_message "Electron exited with code: \$exit_code"
log_message '--- Claude Desktop Launcher End ---'
exit \$exit_code
EOF
chmod +x "$INSTALL_DIR/bin/claude-desktop"
echo "✓ Launcher script created"
chmod +x "$install_dir/bin/claude-desktop" || exit 1
echo 'Launcher script created'
# --- Create Control File ---
echo "📄 Creating control file..."
# Electron is bundled with its own Node.js runtime, so nodejs/npm are not runtime dependencies.
# p7zip is only used at build time to extract the installer.
# No external dependencies are required at runtime.
echo 'Creating control file...'
# Electron is bundled with its own Node.js runtime, so nodejs/npm are not
# runtime dependencies. p7zip is only used at build time to extract the
# installer. No external dependencies are required at runtime.
cat > "$PACKAGE_ROOT/DEBIAN/control" << EOF
Package: $PACKAGE_NAME
Version: $VERSION
Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER
Description: $DESCRIPTION
cat > "$package_root/DEBIAN/control" << EOF
Package: $package_name
Version: $version
Architecture: $architecture
Maintainer: $maintainer
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.)
EOF
echo "✓ Control file created"
echo 'Control file created'
# --- Create Postinst Script ---
echo "⚙️ Creating postinst script..."
cat > "$PACKAGE_ROOT/DEBIAN/postinst" << EOF
echo 'Creating postinst script...'
cat > "$package_root/DEBIAN/postinst" << EOF
#!/bin/sh
set -e
@@ -197,11 +198,12 @@ set -e
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
# 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
LOCAL_SANDBOX_PATH="/usr/lib/$package_name/node_modules/electron/dist/chrome-sandbox"
if [ -f "\$LOCAL_SANDBOX_PATH" ]; then
SANDBOX_PATH="\$LOCAL_SANDBOX_PATH"
fi
@@ -212,32 +214,32 @@ if [ -n "\$SANDBOX_PATH" ] && [ -f "\$SANDBOX_PATH" ]; then
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
echo "Warning: chrome-sandbox binary not found in local package at \$LOCAL_SANDBOX_PATH. Sandbox may not function correctly."
fi
exit 0
EOF
chmod +x "$PACKAGE_ROOT/DEBIAN/postinst"
echo "✓ Postinst script created"
chmod +x "$package_root/DEBIAN/postinst" || exit 1
echo 'Postinst script created'
# --- Build .deb Package ---
echo "📦 Building .deb package..."
DEB_FILE="$WORK_DIR/${PACKAGE_NAME}_${VERSION}_${ARCHITECTURE}.deb"
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"
echo 'Setting DEBIAN directory permissions...'
chmod 755 "$package_root/DEBIAN" || exit 1
# Fix script permissions in DEBIAN directory
echo "Setting script permissions..."
chmod 755 "$PACKAGE_ROOT/DEBIAN/postinst"
echo 'Setting script permissions...'
chmod 755 "$package_root/DEBIAN/postinst" || exit 1
if ! dpkg-deb --build "$PACKAGE_ROOT" "$DEB_FILE"; then
echo "❌ Failed to build .deb package"
exit 1
if ! dpkg-deb --build "$package_root" "$deb_file"; then
echo 'Failed to build .deb package' >&2
exit 1
fi
echo "✓ .deb package built successfully: $DEB_FILE"
echo "--- Debian Package Build Finished ---"
echo "Deb package built successfully: $deb_file"
echo '--- Debian Package Build Finished ---'
exit 0
exit 0

123
scripts/launcher-common.sh Normal file → Executable file
View File

@@ -1,95 +1,96 @@
#!/bin/bash
#!/usr/bin/env bash
# Common launcher functions for Claude Desktop (AppImage and deb)
# This file is sourced by both launchers to avoid code duplication
# Setup logging directory and file
# Sets: LOG_DIR, LOG_FILE
# Sets: log_dir, log_file
setup_logging() {
LOG_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/claude-desktop-debian"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/launcher.log"
log_dir="${XDG_CACHE_HOME:-$HOME/.cache}/claude-desktop-debian"
mkdir -p "$log_dir" || return 1
log_file="$log_dir/launcher.log"
}
# Log a message to the log file
# Usage: log_message "message"
log_message() {
echo "$1" >> "$LOG_FILE"
echo "$1" >> "$log_file"
}
# Detect display backend (Wayland vs X11)
# Sets: IS_WAYLAND, USE_X11_ON_WAYLAND
# Sets: is_wayland, use_x11_on_wayland
detect_display_backend() {
# Detect if Wayland is likely running
IS_WAYLAND=false
if [ -n "$WAYLAND_DISPLAY" ]; then
IS_WAYLAND=true
fi
# Detect if Wayland is likely running
is_wayland=false
if [[ -n $WAYLAND_DISPLAY ]]; then
is_wayland=true
fi
# Determine display backend mode
# Default: Use X11/XWayland on Wayland sessions for global hotkey support
# Set CLAUDE_USE_WAYLAND=1 to use native Wayland (global hotkeys won't work)
USE_X11_ON_WAYLAND=true
if [ "$CLAUDE_USE_WAYLAND" = "1" ]; then
USE_X11_ON_WAYLAND=false
fi
# Determine display backend mode
# Default: Use X11/XWayland on Wayland sessions for global hotkey support
# Set CLAUDE_USE_WAYLAND=1 to use native Wayland (global hotkeys won't work)
use_x11_on_wayland=true
if [[ $CLAUDE_USE_WAYLAND == '1' ]]; then
use_x11_on_wayland=false
fi
}
# Check if we have a valid display (not running from TTY)
# Returns: 0 if display available, 1 if not
check_display() {
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
return 1
fi
return 0
if [[ -z $DISPLAY && -z $WAYLAND_DISPLAY ]]; then
return 1
fi
return 0
}
# Build Electron arguments array based on display backend
# Requires: IS_WAYLAND, USE_X11_ON_WAYLAND to be set (call detect_display_backend first)
# Sets: ELECTRON_ARGS array
# Requires: is_wayland, use_x11_on_wayland to be set
# (call detect_display_backend first)
# Sets: electron_args array
# Arguments: $1 = "appimage" or "deb" (affects --no-sandbox behavior)
build_electron_args() {
local package_type="${1:-deb}"
local package_type="${1:-deb}"
# Initialize args array
ELECTRON_ARGS=()
# Initialize args array
electron_args=()
# AppImage always needs --no-sandbox due to FUSE constraints
if [ "$package_type" = "appimage" ]; then
ELECTRON_ARGS+=("--no-sandbox")
fi
# AppImage always needs --no-sandbox due to FUSE constraints
if [[ $package_type == 'appimage' ]]; then
electron_args+=('--no-sandbox')
fi
# Disable CustomTitlebar for better Linux integration
ELECTRON_ARGS+=("--disable-features=CustomTitlebar")
# Disable CustomTitlebar for better Linux integration
electron_args+=('--disable-features=CustomTitlebar')
# Add compatibility flags based on display backend
if [ "$IS_WAYLAND" = true ]; then
if [ "$USE_X11_ON_WAYLAND" = true ]; then
# Default: Use X11 via XWayland for global hotkey support
log_message "Using X11 backend via XWayland (for global hotkey support)"
# Deb package needs --no-sandbox for XWayland mode too
if [ "$package_type" = "deb" ]; then
ELECTRON_ARGS+=("--no-sandbox")
fi
ELECTRON_ARGS+=("--ozone-platform=x11")
else
# Native Wayland mode (user opted in via CLAUDE_USE_WAYLAND=1)
log_message "Using native Wayland backend (global hotkeys may not work)"
if [ "$package_type" = "deb" ]; then
ELECTRON_ARGS+=("--no-sandbox")
fi
ELECTRON_ARGS+=("--enable-features=UseOzonePlatform,WaylandWindowDecorations")
ELECTRON_ARGS+=("--ozone-platform=wayland")
ELECTRON_ARGS+=("--enable-wayland-ime")
ELECTRON_ARGS+=("--wayland-text-input-version=3")
fi
else
# X11 session - no special flags needed
log_message "X11 session detected"
fi
# Add compatibility flags based on display backend
if [[ $is_wayland == true ]]; then
if [[ $use_x11_on_wayland == true ]]; then
# Default: Use X11 via XWayland for global hotkey support
log_message 'Using X11 backend via XWayland (for global hotkey support)'
# Deb package needs --no-sandbox for XWayland mode too
if [[ $package_type == 'deb' ]]; then
electron_args+=('--no-sandbox')
fi
electron_args+=('--ozone-platform=x11')
else
# Native Wayland mode (user opted in via CLAUDE_USE_WAYLAND=1)
log_message 'Using native Wayland backend (global hotkeys may not work)'
if [[ $package_type == 'deb' ]]; then
electron_args+=('--no-sandbox')
fi
electron_args+=('--enable-features=UseOzonePlatform,WaylandWindowDecorations')
electron_args+=('--ozone-platform=wayland')
electron_args+=('--enable-wayland-ime')
electron_args+=('--wayland-text-input-version=3')
fi
else
# X11 session - no special flags needed
log_message 'X11 session detected'
fi
}
# Set common environment variables
setup_electron_env() {
export ELECTRON_FORCE_IS_PACKAGED=true
export ELECTRON_USE_SYSTEM_TITLE_BAR=1
export ELECTRON_FORCE_IS_PACKAGED=true
export ELECTRON_USE_SYSTEM_TITLE_BAR=1
}