Files
wehub-resource-sync 3e076d4dd9
Deploy Worker / deploy (push) Failing after 1s
Shellcheck / Check shell scripts (push) Failing after 1s
CI / Build Packages (amd64 - appimage) (push) Has been skipped
CI / Build Packages (amd64 - deb) (push) Has been skipped
CI / Build Packages (amd64 - rpm) (push) Has been skipped
CI / Build Packages (arm64 - appimage) (push) Has been skipped
CI / Build Packages (arm64 - deb) (push) Has been skipped
CI / Test Flags Parsing (push) Failing after 1s
BATS Tests / BATS unit tests (push) Failing after 1s
CI / Build Packages (arm64 - rpm) (push) Has been skipped
CI / Test Build Artifacts (amd64) (push) Has been skipped
CI / Test Build Artifacts (arm64) (push) Has been skipped
CI / Update APT Repository (push) Has been cancelled
CI / Mirror Official .deb to Release (push) Has been cancelled
CI / Update DNF Repository (push) Has been cancelled
CI / Update AUR Package (push) Has been cancelled
CI / Create Release (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:29:21 +08:00

380 lines
14 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...'
# The staging dir is the extracted official usr/lib/claude-desktop tree
# (Electron ELF, chrome-sandbox, resources/, locales/, ...); ship it as-is.
mkdir -p "$appdir_path/usr/lib/claude-desktop" || exit 1
cp -a "$app_staging_dir/." "$appdir_path/usr/lib/claude-desktop/" || exit 1
echo 'Official application tree copied'
# 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)"
cp "$(dirname "$script_dir")/launcher-common.sh" "$appdir_path/usr/lib/claude-desktop/" || exit 1
sed -i "s/@@WM_CLASS@@/$WM_CLASS/" "$appdir_path/usr/lib/claude-desktop/launcher-common.sh"
cp "$(dirname "$script_dir")/doctor.sh" "$appdir_path/usr/lib/claude-desktop/" || exit 1
echo 'Shared launcher library + doctor copied'
# Ensure the official binary made it into the AppDir
bundled_app_path="$appdir_path/usr/lib/claude-desktop/claude-desktop"
echo "Checking for executable at: $bundled_app_path"
if [[ ! -f $bundled_app_path ]]; then
echo 'Claude Desktop binary not found in staging area.' >&2
echo "Path checked: $bundled_app_path" >&2
exit 1
fi
chmod +x "$bundled_app_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"
# The official Electron binary; it auto-loads the co-located
# resources/app.asar, so no app path is ever passed (issue #696).
app_exec="$appdir/usr/lib/claude-desktop/claude-desktop"
# Handle --doctor flag before anything else
if [[ "${1:-}" == '--doctor' ]]; then
run_doctor "$app_exec"
exit $?
fi
# --version never reaches the terminal via Electron: the launcher
# redirects all app output to the log (#772). Answer it here instead.
if [[ "${1:-}" == '--version' ]]; then
echo '@@PACKAGE_NAME@@ @@VERSION@@'
exit 0
fi
# Setup logging and environment
setup_logging || exit 1
setup_electron_env
cleanup_orphaned_cowork_daemon
cleanup_stale_desktop_helpers
cleanup_stale_lock
cleanup_stale_cowork_socket
# APPIMAGE is set by the AppImage runtime to the persistent image path;
# an extracted/direct run leaves it unset and the heal no-ops.
heal_autostart_entry "${APPIMAGE:-}"
backup_user_config
# 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
# Build Chromium switches (appimage mode adds --no-sandbox for FUSE)
build_electron_args 'appimage'
# Change to HOME directory before exec'ing the app to avoid CWD permission issues
cd "$HOME" || exit 1
# Execute the official binary and keep AppRun alive so explicit quit can
# clean up Desktop-owned helpers that outlive the main process.
log_message "Executing: $app_exec ${electron_args[*]} $*"
run_electron_and_cleanup "$app_exec" "${electron_args[@]}" "$@"
exit $?
EOF
chmod +x "$appdir_path/AppRun" || exit 1
# The AppRun heredoc is quoted (runtime expansion), so the build-time
# values for the --version fast-path are stamped in afterwards.
sed -i "s/@@PACKAGE_NAME@@/$package_name/; s/@@VERSION@@/$version/" \
"$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=$WM_CLASS
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 official 256x256 hicolor icon as the main AppImage icon
icon_source_path="${CLAUDE_EXTRACT_DIR:?}/usr/share/icons/hicolor/256x256/apps/claude-desktop.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
# project_license describes the app the user launches (the proprietary
# Claude binary), not the MIT packaging scripts
# 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>LicenseRef-proprietary</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 is a native binary that must run on the HOST machine, not
# the package's target architecture: CI cross-builds (e.g. an arm64
# package on an ubuntu-latest/x86_64 runner) need the x86_64 tool even
# though $architecture says arm64. Select strictly by uname -m here;
# the target architecture is only used later for the embedded ARCH.
host_arch=$(uname -m)
case "$host_arch" in
x86_64|aarch64) ;;
*)
echo "Unsupported host architecture for appimagetool: $host_arch" >&2
exit 1
;;
esac
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 a previously downloaded HOST-arch tool
if [[ -z $appimagetool_path ]]; then
local_path="$work_dir/appimagetool-${host_arch}.AppImage"
if [[ -f $local_path ]]; then
appimagetool_path="$local_path"
echo "Found downloaded ${host_arch} appimagetool: $appimagetool_path"
fi
fi
# Download if not found
if [[ -z $appimagetool_path ]]; then
echo 'Downloading appimagetool...'
appimagetool_url="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${host_arch}.AppImage"
appimagetool_path="$work_dir/appimagetool-${host_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
# Normalize AppDir permissions before squashing. The staging copy above
# uses `cp -a`, which preserves source modes, and a restrictive build
# umask can leave directories at 0700. mksquashfs records those verbatim,
# so a user who later runs the AppImage can't traverse into
# app.asar.unpacked/ — silently breaking Cowork's daemon auto-launch (the
# fork is guarded by fs.existsSync(), false on a directory it can't read).
# Canonical modes: dirs and already-executable files 755, the rest 644.
echo 'Normalizing AppDir permissions...'
find "$appdir_path" -type d -exec chmod 755 {} + || exit 1
find "$appdir_path" -type f -exec chmod u=rwX,go=rX {} + || exit 1
# --- Build AppImage ---
echo 'Building AppImage...'
output_filename="${package_name}-${version}-${architecture}.AppImage"
output_path="$work_dir/$output_filename"
# ARCH names the TARGET architecture (canonical uname-style), which can
# differ from the host running the tool during a cross-build. It only
# covers naming/validation: appimagetool ALWAYS embeds the runtime stub
# bundled with the tool itself, which is host-arch. On a cross-build
# that bakes an x86_64 stub into an arm64 AppImage, which then can't
# start on target hardware (caught by test-artifacts on the first
# native-arm64 run). Fetch the TARGET-arch runtime from the same
# release as the tool and force it in with --runtime-file.
case "$architecture" in
amd64) export ARCH='x86_64' ;;
arm64) export ARCH='aarch64' ;;
*)
echo "Unsupported target architecture for ARCH: $architecture" >&2
exit 1
;;
esac
echo "Using ARCH=$ARCH"
runtime_path="$work_dir/appimage-runtime-${ARCH}"
if [[ ! -f $runtime_path ]]; then
runtime_url="https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-${ARCH}"
echo "Downloading AppImage runtime for ${ARCH}..."
if ! wget -q -O "$runtime_path" "$runtime_url"; then
echo "Failed to download AppImage runtime from $runtime_url" >&2
rm -f "$runtime_path"
exit 1
fi
fi
# 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" --runtime-file "$runtime_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>
# The 'claude-desktop-*' wildcard is deliberately NOT renamed along with
# $package_name: it matches both the old claude-desktop-* and the new
# claude-desktop-unofficial-* artifact names, so AppImages installed
# before the rename keep self-updating. Do not narrow it.
update_info="gh-releases-zsync|aaddrick|claude-desktop-debian|latest|claude-desktop-*-${architecture}.AppImage.zsync"
echo "Update info: $update_info"
if ! "$appimagetool_path" --runtime-file "$runtime_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