mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 00:26:21 +03:00
* launcher: log session/IME env block at startup (#548) Adds log_session_env, called once per launch from each packaging target (deb, rpm, AppImage, Nix). Emits a single env={ ... } block covering display (XDG_SESSION_TYPE, WAYLAND_DISPLAY, DISPLAY, XDG_CURRENT_DESKTOP), IME (GTK_IM_MODULE, XMODIFIERS, QT_IM_MODULE), and Claude-specific overrides (CLAUDE_USE_WAYLAND, CLAUDE_TITLEBAR_STYLE, CLAUDE_GTK_IM_MODULE). Empty/unset values are emitted as `KEY=` (rather than omitted) so absence is unambiguous in bug reports. Pure observability — no behavior change. Closes #548 Co-Authored-By: Claude <claude@anthropic.com> * test: consolidate log_session_env BATS coverage (#548) Collapse the four log_session_env cases into two, and tighten the assertions in both: - Old test 1 (substring match per key) + old test 4 (block braces on their own lines) → one test using exact-line equality on the `lines` array. Locks block structure and per-key formatting in a single pass; substring matching could not catch a regression that re-ordered keys, dropped indentation, or merged lines. - Old test 2 (unset values are KEY=) + old test 3 (empty-string is KEY=) → one test covering both code paths. Exact-line match proves the value after `=` is truly empty; the previous `*'KEY='*` substring would have matched `KEY=value` and the old test-3 regex was fragile (depended on trailing newline being literal `$'\n'` vs end-of-string `$`). Net: 77 → 42 lines, 4 → 2 cases, stronger guarantees. No change to the helper itself or the call sites — issue #548 acceptance criteria still hold. --------- Co-authored-by: Claude <claude@anthropic.com>
330 lines
11 KiB
Bash
Executable File
330 lines
11 KiB
Bash
Executable File
#!/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
|
|
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" || 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...'
|
|
# 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/" || 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" || 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/" || exit 1
|
|
fi
|
|
echo 'Application files copied to Electron resources directory'
|
|
|
|
# Copy shared launcher library (launcher-common.sh sources doctor.sh
|
|
# at runtime, so both must live in the same directory)
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
mkdir -p "$appdir_path/usr/lib/claude-desktop" || exit 1
|
|
cp "$(dirname "$script_dir")/launcher-common.sh" "$appdir_path/usr/lib/claude-desktop/" || exit 1
|
|
cp "$(dirname "$script_dir")/doctor.sh" "$appdir_path/usr/lib/claude-desktop/" || exit 1
|
|
echo 'Shared launcher library + doctor 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
|
|
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" || exit 1
|
|
|
|
# --- Create AppRun Script ---
|
|
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")")
|
|
|
|
# Source shared launcher library
|
|
source "$appdir/usr/lib/claude-desktop/launcher-common.sh"
|
|
|
|
# Handle --doctor flag before anything else
|
|
if [[ "${1:-}" == '--doctor' ]]; then
|
|
electron_path="$appdir/usr/lib/node_modules/electron/dist/electron"
|
|
run_doctor "$electron_path"
|
|
exit $?
|
|
fi
|
|
|
|
# Setup logging and environment
|
|
setup_logging || exit 1
|
|
setup_electron_env
|
|
cleanup_orphaned_cowork_daemon
|
|
cleanup_stale_lock
|
|
cleanup_stale_cowork_socket
|
|
|
|
# Detect display backend
|
|
detect_display_backend
|
|
|
|
# Log startup info
|
|
log_message '--- Claude Desktop AppImage Start ---'
|
|
log_message "Timestamp: $(date)"
|
|
log_message "Arguments: $@"
|
|
log_message "APPDIR: $appdir"
|
|
log_session_env
|
|
|
|
# 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"
|
|
|
|
# Build electron args (appimage mode adds --no-sandbox)
|
|
build_electron_args 'appimage'
|
|
|
|
# Add app path LAST - Chromium flags must come before this
|
|
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
|
|
EOF
|
|
chmod +x "$appdir_path/AppRun" || exit 1
|
|
echo 'AppRun script created'
|
|
|
|
# --- 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" || 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...'
|
|
# 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" || 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."
|
|
fi
|
|
|
|
# --- Create AppStream Metadata ---
|
|
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"
|
|
|
|
# Generate the AppStream XML file
|
|
# Use MIT license based on LICENSE-MIT file in repo
|
|
# ID follows reverse DNS convention
|
|
cat > "$appdata_file" << EOF
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<component type="desktop-application">
|
|
<id>$component_id</id>
|
|
<metadata_license>CC0-1.0</metadata_license>
|
|
<project_license>MIT</project_license>
|
|
<developer id="io.github.aaddrick">
|
|
<name>aaddrick</name>
|
|
</developer>
|
|
|
|
<name>Claude Desktop</name>
|
|
<summary>Unofficial desktop client for Claude AI</summary>
|
|
|
|
<description>
|
|
<p>
|
|
Provides a desktop experience for interacting with Claude AI, wrapping the web interface.
|
|
</p>
|
|
</description>
|
|
|
|
<launchable type="desktop-id">${component_id}.desktop</launchable>
|
|
|
|
<icon type="stock">${component_id}</icon>
|
|
<url type="homepage">https://github.com/aaddrick/claude-desktop-debian</url>
|
|
<screenshots>
|
|
<screenshot type="default">
|
|
<image>https://github.com/user-attachments/assets/93080028-6f71-48bd-8e59-5149d148cd45</image>
|
|
</screenshot>
|
|
</screenshots>
|
|
<provides>
|
|
<binary>AppRun</binary>
|
|
</provides>
|
|
|
|
<categories>
|
|
<category>Network</category>
|
|
<category>Utility</category>
|
|
</categories>
|
|
|
|
<content_rating type="oars-1.1" />
|
|
|
|
<releases>
|
|
<release version="$version" date="$(date +%Y-%m-%d)">
|
|
<description>
|
|
<p>Version $version.</p>
|
|
</description>
|
|
</release>
|
|
</releases>
|
|
|
|
</component>
|
|
EOF
|
|
echo "AppStream metadata created at $appdata_file"
|
|
|
|
|
|
# --- Get appimagetool ---
|
|
appimagetool_path=''
|
|
|
|
# Check system PATH first
|
|
if command -v appimagetool &> /dev/null; then
|
|
appimagetool_path=$(command -v appimagetool)
|
|
echo "Found appimagetool in PATH: $appimagetool_path"
|
|
fi
|
|
|
|
# Check for previously downloaded versions
|
|
for arch in x86_64 aarch64; do
|
|
[[ -n $appimagetool_path ]] && break
|
|
local_path="$work_dir/appimagetool-${arch}.AppImage"
|
|
if [[ -f $local_path ]]; then
|
|
appimagetool_path="$local_path"
|
|
echo "Found downloaded ${arch} appimagetool: $appimagetool_path"
|
|
fi
|
|
done
|
|
|
|
# Download if not found
|
|
if [[ -z $appimagetool_path ]]; then
|
|
echo 'Downloading appimagetool...'
|
|
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"
|
|
|
|
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"
|
|
export ARCH="$architecture"
|
|
echo "Using ARCH=$ARCH"
|
|
|
|
# Local build - no update information
|
|
if [[ $GITHUB_ACTIONS != 'true' ]]; then
|
|
echo 'Running locally - building AppImage without update information'
|
|
echo '(Update info and zsync files are only generated in GitHub Actions for releases)'
|
|
|
|
if ! "$appimagetool_path" "$appdir_path" "$output_path"; then
|
|
echo "Failed to build AppImage using $appimagetool_path" >&2
|
|
exit 1
|
|
fi
|
|
echo "AppImage built successfully: $output_path"
|
|
echo '--- AppImage Build Finished ---'
|
|
exit 0
|
|
fi
|
|
|
|
# GitHub Actions build - embed update information
|
|
echo 'Running in GitHub Actions - embedding update information for automatic updates...'
|
|
|
|
# Install zsync if needed for .zsync file generation
|
|
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|<username>|<repository>|<tag>|<filename-pattern>
|
|
update_info="gh-releases-zsync|aaddrick|claude-desktop-debian|latest|claude-desktop-*-${architecture}.AppImage.zsync"
|
|
echo "Update info: $update_info"
|
|
|
|
if ! "$appimagetool_path" --updateinformation "$update_info" "$appdir_path" "$output_path"; then
|
|
echo "Failed to build AppImage using $appimagetool_path" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "AppImage built successfully with embedded update info: $output_path"
|
|
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
|
|
|
|
echo '--- AppImage Build Finished ---'
|
|
|
|
exit 0
|