mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 00:26:21 +03:00
The APT repository update was failing with exit code 254 because reprepro rejected duplicate packages. When multiple releases use the same Claude Desktop version (e.g., v1.3.0, v1.3.1, v1.3.2 all using claude1.1.799), the packages had identical versions causing conflicts. Changes: - Add --release-tag flag to build.sh to accept the full release tag - Extract wrapper version from tag (v1.3.2+claude1.1.799 -> 1.3.2) - Append wrapper version as Debian revision suffix (1.1.799-1.3.2) - Update CI workflows to pass release tag on tag builds This ensures each release produces packages with unique versions that APT can properly handle for upgrades (1.1.799-1.3.2 > 1.1.799-1.3.1). Fixes workflow failure in run #21318689829 Co-Authored-By: Claude <claude@anthropic.com>
1131 lines
38 KiB
Bash
Executable File
1131 lines
38 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
#===============================================================================
|
|
# Claude Desktop Debian Build Script
|
|
# Repackages Claude Desktop (Electron app) for Debian/Ubuntu Linux
|
|
#===============================================================================
|
|
|
|
# Global variables (set by functions, used throughout)
|
|
architecture=''
|
|
distro_family='' # debian, rpm, or unknown
|
|
claude_download_url=''
|
|
claude_exe_filename=''
|
|
version=''
|
|
release_tag='' # Optional release tag (e.g., v1.3.2+claude1.1.799) 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_exe_path=''
|
|
original_user=''
|
|
original_home=''
|
|
project_root=''
|
|
work_dir=''
|
|
app_staging_dir=''
|
|
chosen_electron_module_path=''
|
|
asar_exec=''
|
|
claude_extract_dir=''
|
|
electron_resources_dest=''
|
|
node_pty_build_dir=''
|
|
final_output_path=''
|
|
|
|
# Package metadata (constants)
|
|
readonly PACKAGE_NAME='claude-desktop'
|
|
readonly MAINTAINER='Claude Desktop Linux Maintainers'
|
|
readonly DESCRIPTION='Claude Desktop for Linux'
|
|
|
|
#===============================================================================
|
|
# Utility Functions
|
|
#===============================================================================
|
|
|
|
check_command() {
|
|
if ! command -v "$1" &> /dev/null; then
|
|
echo "$1 not found"
|
|
return 1
|
|
else
|
|
echo "$1 found"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
section_header() {
|
|
echo -e "\033[1;36m--- $1 ---\033[0m"
|
|
}
|
|
|
|
section_footer() {
|
|
echo -e "\033[1;36m--- End $1 ---\033[0m"
|
|
}
|
|
|
|
#===============================================================================
|
|
# Setup Functions
|
|
#===============================================================================
|
|
|
|
detect_architecture() {
|
|
section_header 'Architecture Detection'
|
|
echo 'Detecting system architecture...'
|
|
|
|
local raw_arch
|
|
raw_arch=$(uname -m) || {
|
|
echo 'Failed to detect architecture' >&2
|
|
exit 1
|
|
}
|
|
echo "Detected machine architecture: $raw_arch"
|
|
|
|
case "$raw_arch" in
|
|
x86_64)
|
|
claude_download_url='https://downloads.claude.ai/releases/win32/x64/1.1.799/Claude-2e02b656cbbb1f14a5a81a4ed79b1c5ea1427507.exe'
|
|
architecture='amd64'
|
|
claude_exe_filename='Claude-Setup-x64.exe'
|
|
echo 'Configured for amd64 (x86_64) build.'
|
|
;;
|
|
aarch64)
|
|
claude_download_url='https://downloads.claude.ai/releases/win32/arm64/1.1.799/Claude-2e02b656cbbb1f14a5a81a4ed79b1c5ea1427507.exe'
|
|
architecture='arm64'
|
|
claude_exe_filename='Claude-Setup-arm64.exe'
|
|
echo 'Configured for arm64 (aarch64) build.'
|
|
;;
|
|
*)
|
|
echo "Unsupported architecture: $raw_arch. This script supports x86_64 (amd64) and aarch64 (arm64)." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "Target Architecture: $architecture"
|
|
section_footer 'Architecture Detection'
|
|
}
|
|
|
|
detect_distro() {
|
|
section_header 'Distribution Detection'
|
|
echo 'Detecting Linux distribution family...'
|
|
|
|
if [[ -f /etc/debian_version ]]; then
|
|
distro_family='debian'
|
|
echo "Detected Debian-based distribution"
|
|
echo " Debian version: $(cat /etc/debian_version)"
|
|
elif [[ -f /etc/fedora-release ]]; then
|
|
distro_family='rpm'
|
|
echo "Detected Fedora"
|
|
echo " $(cat /etc/fedora-release)"
|
|
elif [[ -f /etc/redhat-release ]]; then
|
|
distro_family='rpm'
|
|
echo "Detected Red Hat-based distribution"
|
|
echo " $(cat /etc/redhat-release)"
|
|
else
|
|
distro_family='unknown'
|
|
echo "Warning: Could not detect distribution family"
|
|
echo " AppImage build will still work, but native packages (deb/rpm) may not"
|
|
fi
|
|
|
|
echo "Distribution: $(grep 'PRETTY_NAME' /etc/os-release 2>/dev/null | cut -d'"' -f2 || echo 'Unknown')"
|
|
echo "Distribution family: $distro_family"
|
|
section_footer 'Distribution Detection'
|
|
}
|
|
|
|
check_system_requirements() {
|
|
# Allow running as root in CI/container environments
|
|
if (( EUID == 0 )); then
|
|
if [[ -n ${CI:-} || -n ${GITHUB_ACTIONS:-} || -f /.dockerenv ]]; then
|
|
echo 'Running as root in CI/container environment (allowed)'
|
|
else
|
|
echo 'This script should not be run using sudo or as the root user.' >&2
|
|
echo 'It will prompt for sudo password when needed for specific actions.' >&2
|
|
echo 'Please run as a normal user.' >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
original_user=$(whoami)
|
|
original_home=$(getent passwd "$original_user" | cut -d: -f6)
|
|
if [[ -z $original_home ]]; then
|
|
echo "Could not determine home directory for user $original_user." >&2
|
|
exit 1
|
|
fi
|
|
echo "Running as user: $original_user (Home: $original_home)"
|
|
|
|
# Check for NVM and source it if found
|
|
if [[ -d $original_home/.nvm ]]; then
|
|
echo "Found NVM installation for user $original_user, checking for Node.js 20+..."
|
|
export NVM_DIR="$original_home/.nvm"
|
|
if [[ -s $NVM_DIR/nvm.sh ]]; then
|
|
# shellcheck disable=SC1091
|
|
\. "$NVM_DIR/nvm.sh"
|
|
local node_bin_path=''
|
|
node_bin_path=$(nvm which current | xargs dirname 2>/dev/null || \
|
|
find "$NVM_DIR/versions/node" -maxdepth 2 -type d -name 'bin' | sort -V | tail -n 1)
|
|
|
|
if [[ -n $node_bin_path && -d $node_bin_path ]]; then
|
|
echo "Adding NVM Node bin path to PATH: $node_bin_path"
|
|
export PATH="$node_bin_path:$PATH"
|
|
else
|
|
echo 'Warning: Could not determine NVM Node bin path.'
|
|
fi
|
|
else
|
|
echo 'Warning: nvm.sh script not found or not sourceable.'
|
|
fi
|
|
fi
|
|
|
|
echo 'System Information:'
|
|
echo "Distribution: $(grep 'PRETTY_NAME' /etc/os-release 2>/dev/null | cut -d'"' -f2 || echo 'Unknown')"
|
|
echo "Distribution family: $distro_family"
|
|
echo "Target Architecture: $architecture"
|
|
}
|
|
|
|
parse_arguments() {
|
|
section_header 'Argument Parsing'
|
|
|
|
project_root="$(pwd)"
|
|
work_dir="$project_root/build"
|
|
app_staging_dir="$work_dir/electron-app"
|
|
|
|
# Set default build format based on detected distro
|
|
case "$distro_family" in
|
|
debian) build_format='deb' ;;
|
|
rpm) build_format='rpm' ;;
|
|
*) build_format='appimage' ;;
|
|
esac
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
-b|--build|-c|--clean|-e|--exe|-r|--release-tag)
|
|
if [[ -z ${2:-} || $2 == -* ]]; then
|
|
echo "Error: Argument for $1 is missing" >&2
|
|
exit 1
|
|
fi
|
|
case "$1" in
|
|
-b|--build) build_format="$2" ;;
|
|
-c|--clean) cleanup_action="$2" ;;
|
|
-e|--exe) local_exe_path="$2" ;;
|
|
-r|--release-tag) release_tag="$2" ;;
|
|
esac
|
|
shift 2
|
|
;;
|
|
--test-flags)
|
|
test_flags_mode=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
echo "Usage: $0 [--build deb|rpm|appimage] [--clean yes|no] [--exe /path/to/installer.exe] [--release-tag TAG] [--test-flags]"
|
|
echo ' --build: Specify the build format (deb, rpm, or appimage).'
|
|
echo " Default: auto-detected based on distro (current: $build_format)"
|
|
echo ' --clean: Specify whether to clean intermediate build files (yes or no). Default: yes'
|
|
echo ' --exe: Use a local Claude installer exe instead of downloading'
|
|
echo ' --release-tag: Release tag (e.g., v1.3.2+claude1.1.799) to append wrapper version to package'
|
|
echo ' --test-flags: Parse flags, print results, and exit without building.'
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
echo 'Use -h or --help for usage information.' >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate arguments
|
|
build_format="${build_format,,}"
|
|
cleanup_action="${cleanup_action,,}"
|
|
|
|
if [[ $build_format != 'deb' && $build_format != 'rpm' && $build_format != 'appimage' ]]; then
|
|
echo "Invalid build format specified: '$build_format'. Must be 'deb', 'rpm', or 'appimage'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Warn if building native package for wrong distro
|
|
if [[ $build_format == 'deb' && $distro_family != 'debian' ]]; then
|
|
echo "Warning: Building .deb package on non-Debian system ($distro_family). This may fail." >&2
|
|
elif [[ $build_format == 'rpm' && $distro_family != 'rpm' ]]; then
|
|
echo "Warning: Building .rpm package on non-RPM system ($distro_family). This may fail." >&2
|
|
fi
|
|
if [[ $cleanup_action != 'yes' && $cleanup_action != 'no' ]]; then
|
|
echo "Invalid cleanup option specified: '$cleanup_action'. Must be 'yes' or 'no'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Selected build format: $build_format"
|
|
echo "Cleanup intermediate files: $cleanup_action"
|
|
|
|
[[ $cleanup_action == 'yes' ]] && perform_cleanup=true
|
|
|
|
section_footer 'Argument Parsing'
|
|
}
|
|
|
|
check_dependencies() {
|
|
echo 'Checking dependencies...'
|
|
local deps_to_install=''
|
|
local common_deps='p7zip wget wrestool icotool convert'
|
|
local all_deps="$common_deps"
|
|
|
|
# Add format-specific dependencies
|
|
case "$build_format" in
|
|
deb) all_deps="$all_deps dpkg-deb" ;;
|
|
rpm) all_deps="$all_deps rpmbuild" ;;
|
|
esac
|
|
|
|
# Command-to-package mappings per distro family
|
|
declare -A debian_pkgs=(
|
|
[p7zip]='p7zip-full' [wget]='wget' [wrestool]='icoutils'
|
|
[icotool]='icoutils' [convert]='imagemagick'
|
|
[dpkg-deb]='dpkg-dev' [rpmbuild]='rpm'
|
|
)
|
|
declare -A rpm_pkgs=(
|
|
[p7zip]='p7zip p7zip-plugins' [wget]='wget' [wrestool]='icoutils'
|
|
[icotool]='icoutils' [convert]='ImageMagick'
|
|
[dpkg-deb]='dpkg' [rpmbuild]='rpm-build'
|
|
)
|
|
|
|
local cmd
|
|
for cmd in $all_deps; do
|
|
if ! check_command "$cmd"; then
|
|
case "$distro_family" in
|
|
debian)
|
|
deps_to_install="$deps_to_install ${debian_pkgs[$cmd]}"
|
|
;;
|
|
rpm)
|
|
deps_to_install="$deps_to_install ${rpm_pkgs[$cmd]}"
|
|
;;
|
|
*)
|
|
echo "Warning: Cannot auto-install '$cmd' on unknown distro. Please install manually." >&2
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
|
|
if [[ -n $deps_to_install ]]; then
|
|
echo "System dependencies needed:$deps_to_install"
|
|
|
|
# Determine if we need sudo (skip if already root)
|
|
local sudo_cmd='sudo'
|
|
if (( EUID == 0 )); then
|
|
sudo_cmd=''
|
|
echo 'Installing as root (no sudo needed)...'
|
|
else
|
|
echo 'Attempting to install using sudo...'
|
|
if ! sudo -v; then
|
|
echo 'Failed to validate sudo credentials. Please ensure you can run sudo.' >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
case "$distro_family" in
|
|
debian)
|
|
if ! $sudo_cmd apt update; then
|
|
echo "Failed to run 'apt update'." >&2
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC2086
|
|
if ! $sudo_cmd apt install -y $deps_to_install; then
|
|
echo "Failed to install dependencies using 'apt install'." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
rpm)
|
|
# shellcheck disable=SC2086
|
|
if ! $sudo_cmd dnf install -y $deps_to_install; then
|
|
echo "Failed to install dependencies using 'dnf install'." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Cannot auto-install dependencies on unknown distro." >&2
|
|
echo "Please install these packages manually: $deps_to_install" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
echo 'System dependencies installed successfully.'
|
|
fi
|
|
}
|
|
|
|
setup_work_directory() {
|
|
rm -rf "$work_dir"
|
|
mkdir -p "$work_dir" || exit 1
|
|
mkdir -p "$app_staging_dir" || exit 1
|
|
}
|
|
|
|
setup_nodejs() {
|
|
section_header 'Node.js Setup'
|
|
echo 'Checking Node.js version...'
|
|
|
|
local node_version_ok=false
|
|
if command -v node &> /dev/null; then
|
|
local node_version node_major
|
|
node_version=$(node --version | cut -d'v' -f2)
|
|
node_major="${node_version%%.*}"
|
|
echo "System Node.js version: v$node_version"
|
|
|
|
if (( node_major >= 20 )); then
|
|
echo "System Node.js version is adequate (v$node_version)"
|
|
node_version_ok=true
|
|
else
|
|
echo "System Node.js version is too old (v$node_version). Need v20+"
|
|
fi
|
|
else
|
|
echo 'Node.js not found in system'
|
|
fi
|
|
|
|
if [[ $node_version_ok == true ]]; then
|
|
section_footer 'Node.js Setup'
|
|
return 0
|
|
fi
|
|
|
|
# Node.js version inadequate - install locally
|
|
echo 'Installing Node.js v20 locally in build directory...'
|
|
|
|
local node_arch
|
|
case "$architecture" in
|
|
amd64) node_arch='x64' ;;
|
|
arm64) node_arch='arm64' ;;
|
|
*)
|
|
echo "Unsupported architecture for Node.js: $architecture" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
local node_version_to_install='20.18.1'
|
|
local node_tarball="node-v${node_version_to_install}-linux-${node_arch}.tar.xz"
|
|
local node_url="https://nodejs.org/dist/v${node_version_to_install}/${node_tarball}"
|
|
local node_install_dir="$work_dir/node"
|
|
|
|
echo "Downloading Node.js v${node_version_to_install} for ${node_arch}..."
|
|
cd "$work_dir" || exit 1
|
|
if ! wget -O "$node_tarball" "$node_url"; then
|
|
echo "Failed to download Node.js from $node_url" >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
echo 'Extracting Node.js...'
|
|
if ! tar -xf "$node_tarball"; then
|
|
echo 'Failed to extract Node.js tarball' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
mv "node-v${node_version_to_install}-linux-${node_arch}" "$node_install_dir" || exit 1
|
|
export PATH="$node_install_dir/bin:$PATH"
|
|
|
|
if command -v node &> /dev/null; then
|
|
echo "Local Node.js installed successfully: $(node --version)"
|
|
else
|
|
echo 'Failed to install local Node.js' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
rm -f "$node_tarball"
|
|
cd "$project_root" || exit 1
|
|
section_footer 'Node.js Setup'
|
|
}
|
|
|
|
setup_electron_asar() {
|
|
section_header 'Electron & Asar Handling'
|
|
|
|
echo "Ensuring local Electron and Asar installation in $work_dir..."
|
|
cd "$work_dir" || exit 1
|
|
|
|
if [[ ! -f package.json ]]; then
|
|
echo "Creating temporary package.json in $work_dir for local install..."
|
|
echo '{"name":"claude-desktop-build","version":"0.0.1","private":true}' > package.json
|
|
fi
|
|
|
|
local electron_dist_path="$work_dir/node_modules/electron/dist"
|
|
local asar_bin_path="$work_dir/node_modules/.bin/asar"
|
|
local install_needed=false
|
|
|
|
[[ ! -d $electron_dist_path ]] && echo 'Electron distribution not found.' && install_needed=true
|
|
[[ ! -f $asar_bin_path ]] && echo 'Asar binary not found.' && install_needed=true
|
|
|
|
if [[ $install_needed == true ]]; then
|
|
echo "Installing Electron and Asar locally into $work_dir..."
|
|
if ! npm install --no-save electron @electron/asar; then
|
|
echo 'Failed to install Electron and/or Asar locally.' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo 'Electron and Asar installation command finished.'
|
|
else
|
|
echo 'Local Electron distribution and Asar binary already present.'
|
|
fi
|
|
|
|
if [[ -d $electron_dist_path ]]; then
|
|
echo "Found Electron distribution directory at $electron_dist_path."
|
|
chosen_electron_module_path="$(realpath "$work_dir/node_modules/electron")"
|
|
echo "Setting Electron module path for copying to $chosen_electron_module_path."
|
|
else
|
|
echo "Failed to find Electron distribution directory at '$electron_dist_path' after installation attempt." >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -f $asar_bin_path ]]; then
|
|
asar_exec="$(realpath "$asar_bin_path")"
|
|
echo "Found local Asar binary at $asar_exec."
|
|
else
|
|
echo "Failed to find Asar binary at '$asar_bin_path' after installation attempt." >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
cd "$project_root" || exit 1
|
|
|
|
if [[ -z $chosen_electron_module_path || ! -d $chosen_electron_module_path ]]; then
|
|
echo 'Critical error: Could not resolve a valid Electron module path to copy.' >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Using Electron module path: $chosen_electron_module_path"
|
|
echo "Using asar executable: $asar_exec"
|
|
section_footer 'Electron & Asar Handling'
|
|
}
|
|
|
|
#===============================================================================
|
|
# Download and Extract Functions
|
|
#===============================================================================
|
|
|
|
download_claude_installer() {
|
|
section_header 'Download the latest Claude executable'
|
|
|
|
local claude_exe_path="$work_dir/$claude_exe_filename"
|
|
|
|
if [[ -n $local_exe_path ]]; then
|
|
echo "Using local Claude installer: $local_exe_path"
|
|
if [[ ! -f $local_exe_path ]]; then
|
|
echo "Local installer file not found: $local_exe_path" >&2
|
|
exit 1
|
|
fi
|
|
cp "$local_exe_path" "$claude_exe_path" || exit 1
|
|
echo 'Local installer copied to build directory'
|
|
else
|
|
echo "Downloading Claude Desktop installer for $architecture..."
|
|
if ! wget -O "$claude_exe_path" "$claude_download_url"; then
|
|
echo "Failed to download Claude Desktop installer from $claude_download_url" >&2
|
|
exit 1
|
|
fi
|
|
echo "Download complete: $claude_exe_filename"
|
|
fi
|
|
|
|
echo "Extracting resources from $claude_exe_filename into separate directory..."
|
|
claude_extract_dir="$work_dir/claude-extract"
|
|
mkdir -p "$claude_extract_dir" || exit 1
|
|
|
|
if ! 7z x -y "$claude_exe_path" -o"$claude_extract_dir"; then
|
|
echo 'Failed to extract installer' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
cd "$claude_extract_dir" || exit 1
|
|
local nupkg_path_relative
|
|
nupkg_path_relative=$(find . -maxdepth 1 -name 'AnthropicClaude-*.nupkg' | head -1)
|
|
|
|
if [[ -z $nupkg_path_relative ]]; then
|
|
echo "Could not find AnthropicClaude nupkg file in $claude_extract_dir" >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo "Found nupkg: $nupkg_path_relative (in $claude_extract_dir)"
|
|
|
|
version=$(echo "$nupkg_path_relative" | LC_ALL=C grep -oP 'AnthropicClaude-\K[0-9]+\.[0-9]+\.[0-9]+(?=-full|-arm64-full)')
|
|
if [[ -z $version ]]; then
|
|
echo "Could not extract version from nupkg filename: $nupkg_path_relative" >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo "Detected Claude version: $version"
|
|
|
|
# Extract wrapper version from release tag if provided (e.g., v1.3.2+claude1.1.799 -> 1.3.2)
|
|
if [[ -n $release_tag ]]; then
|
|
local wrapper_version
|
|
# Extract version between 'v' and '+claude' (e.g., v1.3.2+claude1.1.799 -> 1.3.2)
|
|
wrapper_version=$(echo "$release_tag" | LC_ALL=C grep -oP '^v\K[0-9]+\.[0-9]+\.[0-9]+(?=\+claude)')
|
|
if [[ -n $wrapper_version ]]; then
|
|
version="${version}-${wrapper_version}"
|
|
echo "Package version with wrapper suffix: $version"
|
|
else
|
|
echo "Warning: Could not extract wrapper version from release tag: $release_tag" >&2
|
|
fi
|
|
fi
|
|
|
|
if ! 7z x -y "$nupkg_path_relative"; then
|
|
echo 'Failed to extract nupkg' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo 'Resources extracted from nupkg'
|
|
|
|
cd "$project_root" || exit 1
|
|
}
|
|
|
|
#===============================================================================
|
|
# Patching Functions
|
|
#===============================================================================
|
|
|
|
patch_app_asar() {
|
|
echo 'Processing app.asar...'
|
|
cp "$claude_extract_dir/lib/net45/resources/app.asar" "$app_staging_dir/" || exit 1
|
|
cp -a "$claude_extract_dir/lib/net45/resources/app.asar.unpacked" "$app_staging_dir/" || exit 1
|
|
cd "$app_staging_dir" || exit 1
|
|
"$asar_exec" extract app.asar app.asar.contents || exit 1
|
|
|
|
# Frame fix wrapper
|
|
echo 'Creating BrowserWindow frame fix wrapper...'
|
|
local original_main
|
|
original_main=$(node -e "const pkg = require('./app.asar.contents/package.json'); console.log(pkg.main);")
|
|
echo "Original main entry: $original_main"
|
|
|
|
cp "$project_root/scripts/frame-fix-wrapper.js" app.asar.contents/frame-fix-wrapper.js || exit 1
|
|
|
|
cat > app.asar.contents/frame-fix-entry.js << EOFENTRY
|
|
// Load frame fix first
|
|
require('./frame-fix-wrapper.js');
|
|
// Then load original main
|
|
require('./${original_main}');
|
|
EOFENTRY
|
|
|
|
# Patch BrowserWindow creation
|
|
echo 'Searching and patching BrowserWindow creation in main process files...'
|
|
find app.asar.contents/.vite/build -type f -name '*.js' -exec grep -l 'BrowserWindow' {} \; > /tmp/bw-files.txt
|
|
|
|
local file
|
|
while IFS= read -r file; do
|
|
if [[ -f $file ]]; then
|
|
echo "Patching $file for native frames..."
|
|
sed -i 's/frame[[:space:]]*:[[:space:]]*false/frame:true/g' "$file"
|
|
sed -i 's/frame[[:space:]]*:[[:space:]]*!0/frame:true/g' "$file"
|
|
sed -i 's/frame[[:space:]]*:[[:space:]]*!1/frame:true/g' "$file"
|
|
sed -i 's/titleBarStyle[[:space:]]*:[[:space:]]*[^,}]*/titleBarStyle:""/g' "$file"
|
|
echo "Patched $file"
|
|
fi
|
|
done < /tmp/bw-files.txt
|
|
rm -f /tmp/bw-files.txt
|
|
|
|
# Update package.json
|
|
echo 'Modifying package.json to load frame fix and add node-pty...'
|
|
node -e "
|
|
const fs = require('fs');
|
|
const pkg = require('./app.asar.contents/package.json');
|
|
pkg.originalMain = pkg.main;
|
|
pkg.main = 'frame-fix-entry.js';
|
|
pkg.optionalDependencies = pkg.optionalDependencies || {};
|
|
pkg.optionalDependencies['node-pty'] = '^1.0.0';
|
|
fs.writeFileSync('./app.asar.contents/package.json', JSON.stringify(pkg, null, 2));
|
|
console.log('Updated package.json: main entry and node-pty dependency');
|
|
"
|
|
|
|
# Create stub native module
|
|
echo 'Creating stub native module...'
|
|
mkdir -p app.asar.contents/node_modules/@ant/claude-native || exit 1
|
|
cp "$project_root/scripts/claude-native-stub.js" \
|
|
app.asar.contents/node_modules/@ant/claude-native/index.js || exit 1
|
|
|
|
mkdir -p app.asar.contents/resources/i18n || exit 1
|
|
cp "$claude_extract_dir/lib/net45/resources/"*-*.json app.asar.contents/resources/i18n/ || exit 1
|
|
|
|
# Patch title bar detection
|
|
patch_titlebar_detection
|
|
|
|
# Patch tray menu handler
|
|
patch_tray_menu_handler
|
|
|
|
# Patch tray icon selection
|
|
patch_tray_icon_selection
|
|
|
|
# Patch quick window
|
|
patch_quick_window
|
|
|
|
# Add Linux Claude Code support
|
|
patch_linux_claude_code
|
|
}
|
|
|
|
patch_titlebar_detection() {
|
|
echo '##############################################################'
|
|
echo "Removing '!' from 'if (\"!\"isWindows && isMainWindow) return null;'"
|
|
echo 'detection flag to enable title bar'
|
|
|
|
local search_base='app.asar.contents/.vite/renderer/main_window/assets'
|
|
local target_pattern='MainWindowPage-*.js'
|
|
|
|
echo "Searching for '$target_pattern' within '$search_base'..."
|
|
local target_files
|
|
mapfile -t target_files < <(find "$search_base" -type f -name "$target_pattern")
|
|
local num_files=${#target_files[@]}
|
|
|
|
case $num_files in
|
|
0)
|
|
echo "Error: No file matching '$target_pattern' found within '$search_base'." >&2
|
|
exit 1
|
|
;;
|
|
1)
|
|
local target_file="${target_files[0]}"
|
|
echo "Found target file: $target_file"
|
|
sed -i -E 's/if\(!([a-zA-Z]+)[[:space:]]*&&[[:space:]]*([a-zA-Z]+)\)/if(\1 \&\& \2)/g' "$target_file"
|
|
|
|
if grep -q -E 'if\(![a-zA-Z]+[[:space:]]*&&[[:space:]]*[a-zA-Z]+\)' "$target_file"; then
|
|
echo "Error: Failed to replace patterns in $target_file." >&2
|
|
exit 1
|
|
fi
|
|
echo "Successfully replaced patterns in $target_file"
|
|
;;
|
|
*)
|
|
echo "Error: Expected exactly one file matching '$target_pattern' within '$search_base', but found $num_files." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
echo '##############################################################'
|
|
}
|
|
|
|
patch_tray_menu_handler() {
|
|
echo 'Patching tray menu handler function to prevent concurrent calls and add DBus cleanup delay...'
|
|
|
|
local tray_func tray_var first_const
|
|
tray_func=$(grep -oP 'on\("menuBarEnabled",\(\)=>\{\K\w+(?=\(\)\})' app.asar.contents/.vite/build/index.js)
|
|
if [[ -z $tray_func ]]; then
|
|
echo 'Failed to extract tray menu function name' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo " Found tray function: $tray_func"
|
|
|
|
tray_var=$(grep -oP "\}\);let \K\w+(?==null;(?:async )?function ${tray_func})" app.asar.contents/.vite/build/index.js)
|
|
if [[ -z $tray_var ]]; then
|
|
echo 'Failed to extract tray variable name' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo " Found tray variable: $tray_var"
|
|
|
|
sed -i "s/function ${tray_func}(){/async function ${tray_func}(){/g" app.asar.contents/.vite/build/index.js
|
|
|
|
first_const=$(grep -oP "async function ${tray_func}\(\)\{.*?const \K\w+(?==)" app.asar.contents/.vite/build/index.js | head -1)
|
|
if [[ -z $first_const ]]; then
|
|
echo 'Failed to extract first const variable name in function' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
echo " Found first const variable: $first_const"
|
|
|
|
if ! grep -q "${tray_func}._running" app.asar.contents/.vite/build/index.js; then
|
|
sed -i "s/async function ${tray_func}(){/async function ${tray_func}(){if(${tray_func}._running)return;${tray_func}._running=true;setTimeout(()=>${tray_func}._running=false,1500);/g" app.asar.contents/.vite/build/index.js
|
|
echo " Added mutex guard to ${tray_func}()"
|
|
fi
|
|
|
|
if ! grep -q "await new Promise.*setTimeout" app.asar.contents/.vite/build/index.js | grep -q "${tray_var}"; then
|
|
sed -i "s/${tray_var}\&\&(${tray_var}\.destroy(),${tray_var}=null)/${tray_var}\&\&(${tray_var}.destroy(),${tray_var}=null,await new Promise(r=>setTimeout(r,250)))/g" app.asar.contents/.vite/build/index.js
|
|
echo " Added DBus cleanup delay after ${tray_var}.destroy()"
|
|
fi
|
|
|
|
echo 'Tray menu handler patched'
|
|
echo '##############################################################'
|
|
|
|
# Patch nativeTheme handler
|
|
echo 'Patching nativeTheme handler to skip tray updates during startup...'
|
|
if ! grep -q '_trayStartTime' app.asar.contents/.vite/build/index.js; then
|
|
sed -i -E 's/(oe\.nativeTheme\.on\(\s*"updated"\s*,\s*\(\)\s*=>\s*\{)/let _trayStartTime=Date.now();\1/g' app.asar.contents/.vite/build/index.js
|
|
sed -i -E "s/\((\w+)\(\)\s*,\s*${tray_func}\(\)\s*,/(\1(),Date.now()-_trayStartTime>3e3\&\&${tray_func}(),/g" app.asar.contents/.vite/build/index.js
|
|
echo ' Added startup delay check to nativeTheme handler (3 second window)'
|
|
fi
|
|
echo '##############################################################'
|
|
}
|
|
|
|
patch_tray_icon_selection() {
|
|
echo 'Patching tray icon selection for Linux visibility...'
|
|
if grep -qP ':\w="TrayIconTemplate\.png"' app.asar.contents/.vite/build/index.js; then
|
|
sed -i -E 's/:(\w)="TrayIconTemplate\.png"/:\1=oe.nativeTheme.shouldUseDarkColors?"TrayIconTemplate-Dark.png":"TrayIconTemplate.png"/g' app.asar.contents/.vite/build/index.js
|
|
echo 'Patched tray icon selection for Linux theme support'
|
|
else
|
|
echo 'Tray icon selection pattern not found or already patched'
|
|
fi
|
|
echo '##############################################################'
|
|
}
|
|
|
|
patch_quick_window() {
|
|
if ! grep -q 'e.blur(),e.hide()' app.asar.contents/.vite/build/index.js; then
|
|
sed -i 's/e.hide()/e.blur(),e.hide()/' app.asar.contents/.vite/build/index.js
|
|
echo 'Added blur() call to fix quick window submit issue'
|
|
fi
|
|
}
|
|
|
|
patch_linux_claude_code() {
|
|
if ! grep -q 'process.arch==="arm64"?"linux-arm64":"linux-x64"' app.asar.contents/.vite/build/index.js; then
|
|
sed -i 's/if(process.platform==="win32")return"win32-x64";/if(process.platform==="win32")return"win32-x64";if(process.platform==="linux")return process.arch==="arm64"?"linux-arm64":"linux-x64";/' app.asar.contents/.vite/build/index.js
|
|
echo 'Added support for linux claude code binary'
|
|
else
|
|
echo 'Linux claude code binary support already present'
|
|
fi
|
|
}
|
|
|
|
install_node_pty() {
|
|
section_header 'Installing node-pty for terminal support'
|
|
|
|
node_pty_build_dir="$work_dir/node-pty-build"
|
|
mkdir -p "$node_pty_build_dir" || exit 1
|
|
cd "$node_pty_build_dir" || exit 1
|
|
echo '{"name":"node-pty-build","version":"1.0.0","private":true}' > package.json
|
|
|
|
echo 'Installing node-pty (this will compile native module for Linux)...'
|
|
if npm install node-pty 2>&1; then
|
|
echo 'node-pty installed successfully'
|
|
|
|
if [[ -d $node_pty_build_dir/node_modules/node-pty ]]; then
|
|
echo 'Copying node-pty JavaScript files into app.asar.contents...'
|
|
mkdir -p "$app_staging_dir/app.asar.contents/node_modules/node-pty" || exit 1
|
|
cp -r "$node_pty_build_dir/node_modules/node-pty/lib" \
|
|
"$app_staging_dir/app.asar.contents/node_modules/node-pty/" || exit 1
|
|
cp "$node_pty_build_dir/node_modules/node-pty/package.json" \
|
|
"$app_staging_dir/app.asar.contents/node_modules/node-pty/" || exit 1
|
|
echo 'node-pty JavaScript files copied'
|
|
else
|
|
echo 'node-pty installation directory not found'
|
|
fi
|
|
else
|
|
echo 'Failed to install node-pty - terminal features may not work'
|
|
fi
|
|
|
|
cd "$app_staging_dir" || exit 1
|
|
section_footer 'node-pty installation'
|
|
}
|
|
|
|
finalize_app_asar() {
|
|
"$asar_exec" pack app.asar.contents app.asar || exit 1
|
|
|
|
mkdir -p "$app_staging_dir/app.asar.unpacked/node_modules/@ant/claude-native" || exit 1
|
|
cp "$project_root/scripts/claude-native-stub.js" \
|
|
"$app_staging_dir/app.asar.unpacked/node_modules/@ant/claude-native/index.js" || exit 1
|
|
|
|
# Copy node-pty native binaries
|
|
if [[ -d $node_pty_build_dir/node_modules/node-pty/build/Release ]]; then
|
|
echo 'Copying node-pty native binaries to unpacked directory...'
|
|
mkdir -p "$app_staging_dir/app.asar.unpacked/node_modules/node-pty/build/Release" || exit 1
|
|
cp -r "$node_pty_build_dir/node_modules/node-pty/build/Release/"* \
|
|
"$app_staging_dir/app.asar.unpacked/node_modules/node-pty/build/Release/" || exit 1
|
|
chmod +x "$app_staging_dir/app.asar.unpacked/node_modules/node-pty/build/Release/"* 2>/dev/null || true
|
|
echo 'node-pty native binaries copied'
|
|
else
|
|
echo 'node-pty native binaries not found - terminal features may not work'
|
|
fi
|
|
}
|
|
|
|
#===============================================================================
|
|
# Staging Functions
|
|
#===============================================================================
|
|
|
|
stage_electron() {
|
|
echo 'Copying chosen electron installation to staging area...'
|
|
mkdir -p "$app_staging_dir/node_modules/" || exit 1
|
|
local electron_dir_name
|
|
electron_dir_name=$(basename "$chosen_electron_module_path")
|
|
echo "Copying from $chosen_electron_module_path to $app_staging_dir/node_modules/"
|
|
cp -a "$chosen_electron_module_path" "$app_staging_dir/node_modules/" || exit 1
|
|
|
|
local staged_electron_bin="$app_staging_dir/node_modules/$electron_dir_name/dist/electron"
|
|
if [[ -f $staged_electron_bin ]]; then
|
|
echo "Setting executable permission on staged Electron binary: $staged_electron_bin"
|
|
chmod +x "$staged_electron_bin" || exit 1
|
|
else
|
|
echo "Warning: Staged Electron binary not found at expected path: $staged_electron_bin"
|
|
fi
|
|
|
|
# Copy Electron locale files
|
|
local electron_resources_src="$chosen_electron_module_path/dist/resources"
|
|
electron_resources_dest="$app_staging_dir/node_modules/$electron_dir_name/dist/resources"
|
|
if [[ -d $electron_resources_src ]]; then
|
|
echo 'Copying Electron locale resources...'
|
|
mkdir -p "$electron_resources_dest" || exit 1
|
|
cp -a "$electron_resources_src"/* "$electron_resources_dest/" || exit 1
|
|
echo 'Electron locale resources copied'
|
|
else
|
|
echo "Warning: Electron resources directory not found at $electron_resources_src"
|
|
fi
|
|
}
|
|
|
|
process_icons() {
|
|
section_header 'Icon Processing'
|
|
|
|
cd "$claude_extract_dir" || exit 1
|
|
local exe_path='lib/net45/claude.exe'
|
|
if [[ ! -f $exe_path ]]; then
|
|
echo "Cannot find claude.exe at expected path: $claude_extract_dir/$exe_path" >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
echo "Extracting application icons from $exe_path..."
|
|
if ! wrestool -x -t 14 "$exe_path" -o claude.ico; then
|
|
echo 'Failed to extract icons from exe' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
|
|
if ! icotool -x claude.ico; then
|
|
echo 'Failed to convert icons' >&2
|
|
cd "$project_root" || exit 1
|
|
exit 1
|
|
fi
|
|
cp claude_*.png "$work_dir/" || exit 1
|
|
echo "Application icons extracted and copied to $work_dir"
|
|
|
|
cd "$project_root" || exit 1
|
|
|
|
# Process tray icons
|
|
local claude_locale_src="$claude_extract_dir/lib/net45/resources"
|
|
echo 'Copying and processing tray icon files for Linux...'
|
|
if [[ ! -d $claude_locale_src ]]; then
|
|
echo "Warning: Claude resources directory not found at $claude_locale_src"
|
|
section_footer 'Icon Processing'
|
|
return
|
|
fi
|
|
|
|
cp "$claude_locale_src/Tray"* "$electron_resources_dest/" 2>/dev/null || \
|
|
echo 'Warning: No tray icon files found'
|
|
|
|
# Find ImageMagick command
|
|
local magick_cmd=''
|
|
command -v magick &> /dev/null && magick_cmd='magick'
|
|
[[ -z $magick_cmd ]] && command -v convert &> /dev/null && magick_cmd='convert'
|
|
|
|
if [[ -z $magick_cmd ]]; then
|
|
echo 'Warning: ImageMagick not found - tray icons may appear invisible'
|
|
echo 'Tray icon files copied (unprocessed)'
|
|
section_footer 'Icon Processing'
|
|
return
|
|
fi
|
|
|
|
echo "Processing tray icons for Linux visibility (using $magick_cmd)..."
|
|
local icon_file icon_name
|
|
for icon_file in "$electron_resources_dest"/TrayIconTemplate*.png; do
|
|
[[ ! -f $icon_file ]] && continue
|
|
icon_name=$(basename "$icon_file")
|
|
if "$magick_cmd" "$icon_file" -channel A -fx 'a>0?1:0' +channel \
|
|
"PNG32:$icon_file" 2>/dev/null; then
|
|
echo " Processed $icon_name (100% opaque)"
|
|
else
|
|
echo " Failed to process $icon_name"
|
|
fi
|
|
done
|
|
echo 'Tray icon files copied and processed'
|
|
|
|
section_footer 'Icon Processing'
|
|
}
|
|
|
|
copy_locale_files() {
|
|
local claude_locale_src="$claude_extract_dir/lib/net45/resources"
|
|
echo 'Copying Claude locale JSON files to Electron resources directory...'
|
|
if [[ -d $claude_locale_src ]]; then
|
|
cp "$claude_locale_src/"*-*.json "$electron_resources_dest/" || exit 1
|
|
echo 'Claude locale JSON files copied to Electron resources directory'
|
|
else
|
|
echo "Warning: Claude locale source directory not found at $claude_locale_src"
|
|
fi
|
|
|
|
echo "app.asar processed and staged in $app_staging_dir"
|
|
}
|
|
|
|
#===============================================================================
|
|
# Packaging Functions
|
|
#===============================================================================
|
|
|
|
run_packaging() {
|
|
section_header 'Call Packaging Script'
|
|
|
|
local output_path=''
|
|
local script_name file_pattern pkg_file
|
|
|
|
case "$build_format" in
|
|
deb)
|
|
script_name='build-deb-package.sh'
|
|
file_pattern="${PACKAGE_NAME}_${version}_${architecture}.deb"
|
|
;;
|
|
rpm)
|
|
script_name='build-rpm-package.sh'
|
|
file_pattern="${PACKAGE_NAME}-${version}*.rpm"
|
|
;;
|
|
appimage)
|
|
script_name='build-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/$script_name" || exit 1
|
|
if ! "scripts/$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
|
|
|
|
elif [[ $build_format == 'appimage' ]]; then
|
|
echo "Calling AppImage packaging script for $architecture..."
|
|
chmod +x scripts/build-appimage.sh || exit 1
|
|
if ! scripts/build-appimage.sh \
|
|
"$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=claude-desktop
|
|
Type=Application
|
|
Terminal=false
|
|
Categories=Office;Utility;Network;
|
|
MimeType=x-scheme-handler/claude;
|
|
StartupWMClass=Claude
|
|
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
|
|
|
|
check_dependencies
|
|
setup_work_directory
|
|
setup_nodejs
|
|
setup_electron_asar
|
|
|
|
# Phase 2: Download and extract
|
|
download_claude_installer
|
|
|
|
# Phase 3: Patch and prepare
|
|
patch_app_asar
|
|
install_node_pty
|
|
finalize_app_asar
|
|
stage_electron
|
|
process_icons
|
|
copy_locale_files
|
|
|
|
cd "$project_root" || exit 1
|
|
|
|
# Phase 4: Package
|
|
run_packaging
|
|
|
|
# Phase 5: Cleanup and finish
|
|
cleanup_build
|
|
|
|
echo 'Build process finished.'
|
|
print_next_steps
|
|
}
|
|
|
|
# Run main with all script arguments
|
|
main "$@"
|
|
|
|
exit 0
|