Files
aaddrick--claude-desktop-de…/build.sh
T
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

315 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
#===============================================================================
# Claude Desktop Linux Build Script
# Repackages Anthropic's official Claude Desktop for Linux .deb into the
# formats Anthropic doesn't serve (RPM, AppImage, Nix) plus our own .deb.
#===============================================================================
# Global variables (set by functions, used throughout)
architecture=''
distro_family='' # debian, rpm, nix, or unknown
version=''
release_tag='' # Optional release tag (e.g., v3.0.0+claude1.17377.2) for unique package versions
build_format='' # Will be set based on distro if not specified
cleanup_action='yes'
perform_cleanup=false
test_flags_mode=false
local_deb_path=''
source_dir=''
original_user=''
original_home=''
project_root=''
work_dir=''
app_staging_dir=''
asar_exec=''
claude_extract_dir=''
final_output_path=''
official_deb_url=''
official_deb_sha256=''
official_deb_filename=''
official_deb_depends=''
official_deb_recommends=''
# Package metadata (constants). The package is named
# claude-desktop-unofficial so it can coexist with Anthropic's official
# claude-desktop package; the ELF inside the install tree keeps the
# upstream basename 'claude-desktop'.
readonly PACKAGE_NAME='claude-desktop-unofficial'
readonly WM_CLASS='Claude'
export WM_CLASS
readonly MAINTAINER='Claude Desktop Linux Maintainers'
readonly DESCRIPTION='Claude Desktop for Linux'
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/_common.sh
source "$script_dir/scripts/_common.sh"
# shellcheck source=scripts/setup/detect-host.sh
source "$script_dir/scripts/setup/detect-host.sh"
# shellcheck source=scripts/setup/dependencies.sh
source "$script_dir/scripts/setup/dependencies.sh"
# shellcheck source=scripts/setup/official-deb.sh
source "$script_dir/scripts/setup/official-deb.sh"
# shellcheck source=scripts/patches/app-asar.sh
source "$script_dir/scripts/patches/app-asar.sh"
# shellcheck source=scripts/patches/quick-window.sh
source "$script_dir/scripts/patches/quick-window.sh"
# shellcheck source=scripts/patches/org-plugins.sh
source "$script_dir/scripts/patches/org-plugins.sh"
# shellcheck source=scripts/patches/virtiofsd-probe.sh
source "$script_dir/scripts/patches/virtiofsd-probe.sh"
# shellcheck source=scripts/patches/cowork-bwrap.sh
source "$script_dir/scripts/patches/cowork-bwrap.sh"
# shellcheck source=scripts/patches/config.sh
source "$script_dir/scripts/patches/config.sh"
#===============================================================================
# Packaging Functions
#===============================================================================
run_packaging() {
section_header 'Call Packaging Script'
if [[ $build_format == 'nix' ]]; then
echo 'Nix build mode - skipping packaging (Nix derivation handles installation)'
section_footer 'Call Packaging Script'
return 0
fi
local output_path=''
local script_name file_pattern pkg_file
case "$build_format" in
deb)
script_name='deb.sh'
file_pattern="${PACKAGE_NAME}_${version}_${architecture}.deb"
;;
rpm)
script_name='rpm.sh'
file_pattern="${PACKAGE_NAME}-${version}*.rpm"
;;
appimage)
script_name='appimage.sh'
file_pattern="${PACKAGE_NAME}-${version}-${architecture}.AppImage"
;;
esac
if [[ $build_format == 'deb' || $build_format == 'rpm' ]]; then
echo "Calling ${build_format^^} packaging script for $architecture..."
chmod +x "scripts/packaging/$script_name" || exit 1
if ! "scripts/packaging/$script_name" \
"$version" "$architecture" "$work_dir" "$app_staging_dir" \
"$PACKAGE_NAME" "$MAINTAINER" "$DESCRIPTION"; then
echo "${build_format^^} packaging script failed." >&2
exit 1
fi
pkg_file=$(find "$work_dir" -maxdepth 1 -name "$file_pattern" | head -n 1)
echo "${build_format^^} Build complete!"
if [[ -n $pkg_file && -f $pkg_file ]]; then
output_path="./$(basename "$pkg_file")"
mv "$pkg_file" "$output_path" || exit 1
echo "Package created at: $output_path"
else
echo "Warning: Could not determine final .${build_format} file path."
output_path='Not Found'
fi
# The amd64 deb leg also emits a transitional dummy package for
# the claude-desktop -> claude-desktop-unofficial rename (built
# by deb.sh); move it next to the main .deb so CI picks it up.
local transitional_deb="$work_dir/claude-desktop_1.16000.0-1_all.deb"
if [[ $build_format == 'deb' && -f $transitional_deb ]]; then
mv "$transitional_deb" . || exit 1
echo "Transitional package created at: ./$(basename "$transitional_deb")"
fi
elif [[ $build_format == 'appimage' ]]; then
echo "Calling AppImage packaging script for $architecture..."
chmod +x "scripts/packaging/$script_name" || exit 1
if ! "scripts/packaging/$script_name" \
"$version" "$architecture" "$work_dir" "$app_staging_dir" "$PACKAGE_NAME"; then
echo 'AppImage packaging script failed.' >&2
exit 1
fi
local appimage_file
appimage_file=$(find "$work_dir" -maxdepth 1 -name "${PACKAGE_NAME}-${version}-${architecture}.AppImage" | head -n 1)
echo 'AppImage Build complete!'
if [[ -n $appimage_file && -f $appimage_file ]]; then
output_path="./$(basename "$appimage_file")"
mv "$appimage_file" "$output_path" || exit 1
echo "Package created at: $output_path"
section_header 'Generate .desktop file for AppImage'
local desktop_file="./${PACKAGE_NAME}-appimage.desktop"
echo "Generating .desktop file for AppImage at $desktop_file..."
cat > "$desktop_file" << EOF
[Desktop Entry]
Name=Claude (AppImage)
Comment=Claude Desktop (AppImage Version $version)
Exec=$(basename "$output_path") %u
Icon=$PACKAGE_NAME
Type=Application
Terminal=false
Categories=Office;Utility;Network;
MimeType=x-scheme-handler/claude;
StartupWMClass=$WM_CLASS
X-AppImage-Version=$version
X-AppImage-Name=Claude Desktop (AppImage)
EOF
echo '.desktop file generated.'
else
echo 'Warning: Could not determine final .AppImage file path.'
output_path='Not Found'
fi
fi
# Store for print_next_steps
final_output_path="$output_path"
}
cleanup_build() {
section_header 'Cleanup'
if [[ $perform_cleanup != true ]]; then
echo "Skipping cleanup of intermediate build files in $work_dir."
return
fi
echo "Cleaning up intermediate build files in $work_dir..."
if rm -rf "$work_dir"; then
echo "Cleanup complete ($work_dir removed)."
else
echo 'Cleanup command failed.'
fi
}
print_next_steps() {
echo -e '\n\033[1;34m====== Next Steps ======\033[0m'
case "$build_format" in
deb|rpm)
if [[ $final_output_path != 'Not Found' && -e $final_output_path ]]; then
local pkg_type install_cmd alt_cmd
if [[ $build_format == 'deb' ]]; then
pkg_type='Debian'
install_cmd="sudo apt install $final_output_path"
alt_cmd="sudo dpkg -i $final_output_path"
else
pkg_type='RPM'
install_cmd="sudo dnf install $final_output_path"
alt_cmd="sudo rpm -i $final_output_path"
fi
echo -e "To install the $pkg_type package, run:"
echo -e " \033[1;32m$install_cmd\033[0m"
echo -e " (or \`$alt_cmd\`)"
else
echo -e "${build_format^^} package file not found. Cannot provide installation instructions."
fi
;;
appimage)
if [[ $final_output_path != 'Not Found' && -e $final_output_path ]]; then
echo -e "AppImage created at: \033[1;36m$final_output_path\033[0m"
echo -e '\n\033[1;33mIMPORTANT:\033[0m This AppImage requires \033[1;36mGear Lever\033[0m for proper desktop integration'
# shellcheck disable=SC2016 # backticks intentional for display
echo -e 'and to handle the `claude://` login process correctly.'
echo -e '\nTo install Gear Lever:'
echo -e ' 1. Install via Flatpak:'
echo -e ' \033[1;32mflatpak install flathub it.mijorus.gearlever\033[0m'
echo -e ' 2. Integrate your AppImage with just one click:'
echo -e ' - Open Gear Lever'
echo -e " - Drag and drop \033[1;36m$final_output_path\033[0m into Gear Lever"
echo -e " - Click 'Integrate' to add it to your app menu"
if [[ ${GITHUB_ACTIONS:-} == 'true' ]]; then
echo -e '\n This AppImage includes embedded update information!'
else
echo -e '\n This locally-built AppImage does not include update information.'
echo -e ' For automatic updates, download release versions: https://github.com/aaddrick/claude-desktop-debian/releases'
fi
else
echo -e 'AppImage file not found. Cannot provide usage instructions.'
fi
;;
esac
echo -e '\033[1;34m======================\033[0m'
}
#===============================================================================
# Main Execution
#===============================================================================
main() {
# Phase 1: Setup
detect_architecture
detect_distro
check_system_requirements
parse_arguments "$@"
# Early exit for test mode
if [[ $test_flags_mode == true ]]; then
echo '--- Test Flags Mode Enabled ---'
echo "Build Format: $build_format"
echo "Clean Action: $cleanup_action"
echo 'Exiting without build.'
exit 0
fi
if [[ $build_format != 'nix' ]]; then
check_dependencies
fi
setup_work_directory
if [[ $build_format != 'nix' ]]; then
setup_nodejs
setup_asar
else
# Nix provides node and asar in PATH
asar_exec=$(command -v asar)
if [[ -z $asar_exec ]]; then
echo 'Error: asar not found in PATH (expected Nix to provide it)' >&2
exit 1
fi
fi
# Phase 2: Fetch and extract the official .deb
if [[ $build_format == 'nix' && -z $local_deb_path ]]; then
echo 'Error: --deb is required when --build nix is specified' >&2
exit 1
fi
fetch_official_deb
# The staged app dir IS the extracted official tree; the patch stage
# (if any patches are active) mutates it in place.
app_staging_dir="$claude_extract_dir/usr/lib/claude-desktop"
# Phase 3: Conditional patch stage (patch-zero when active_patches
# is empty — the official app.asar ships byte-identical)
patch_app_asar
cd "$project_root" || exit 1
# Packaging scripts read icons from the extracted share/ tree and
# re-emit the per-arch dependency contract from the official control
# file verbatim (arm64 recommends a different qemu stack than amd64).
export CLAUDE_EXTRACT_DIR="$claude_extract_dir"
export OFFICIAL_DEB_DEPENDS="$official_deb_depends"
export OFFICIAL_DEB_RECOMMENDS="$official_deb_recommends"
# Phase 4: Package
run_packaging
# Phase 5: Cleanup and finish
cleanup_build
echo 'Build process finished.'
if [[ $build_format != 'nix' ]]; then
print_next_steps
fi
}
# Run main with all script arguments
main "$@"
exit 0