diff --git a/docs/linux/installer-latest-experimental-build.sh b/docs/linux/installer-latest-experimental-build.sh index 24327768..8c442ddc 100755 --- a/docs/linux/installer-latest-experimental-build.sh +++ b/docs/linux/installer-latest-experimental-build.sh @@ -1,42 +1,40 @@ #!/bin/bash -set -euo pipefail # Exit immediately if a command exits with a non-zero status. -# Check if jq is installed. This is only used in dev builds and not actual prod builds as jq -# is not a default in most systems -if ! command -v jq &> /dev/null -then - echo "jq is not installed. This check is only present in dev builds" - # Provide installation command based on the package manager. - # Adjust the command according to the target Linux distribution's package manager. - echo "To install jq, run one of the following commands based on your Linux distribution:" - - # Suggest installation commands for different package managers - echo "Ubuntu/Debian: sudo apt-get install jq" - echo "Fedora: sudo dnf install jq" - echo "CentOS/RHEL: sudo yum install jq" - echo "Arch Linux: sudo pacman -S jq" - - # Wait for the user to press any key - read -n 1 -s -r -p "Press any key to continue after installing jq" - echo -fi -# Continue with the rest of the script... -# Define common variables -DESKTOP_DIR=$HOME/.local/share/applications -GITHUB_REPO="phcode-dev/phoenix-desktop" -RELEASES_URL="https://api.github.com/repos/$GITHUB_REPO/releases" -# Fetch the list of releases, assuming the first prerelease in the list is the latest, get the prerelase api url -API_URL=$(wget -qO- "$RELEASES_URL" | jq -r '[.[] | select(.prerelease == true)][0].url') -echo "$API_URL" +set -euo pipefail +# Phoenix Code Linux installer. +# +# Downloads the latest Phoenix Code AppImage from the update JSON, then probes +# the downloaded AppImage with `--version` (early-exit handler at +# src-electron/main.js:23-26). If the probe succeeds, no apt/dnf/pacman install +# is attempted — the system already has every shared library Phoenix Code +# needs at startup, regardless of which package name provides it. +# +# Beyond the --version probe, the installer also checks for the gnome-keyring +# daemon binary — libsecret needs the daemon for keytar credential storage, +# and --version exits before keytar is require()d. Sudo is only requested if +# libs or the keyring daemon are actually missing. +# +# On Ubuntu 24.04+ / Debian 13+, Chromium's unprivileged userns sandbox can +# be blocked by the kernel sysctl. The app still launches because the wrapper +# script falls back to --no-sandbox; we no longer install an AppArmor profile +# to keep the kernel sandbox enabled (the renderer-RCE-to-syscall threat the +# kernel sandbox guards against is narrow, and Phoenix Code's actual attack +# surface flows through the IPC bridge which the kernel sandbox doesn't gate). +# +# Also wires up a desktop entry + `phcode` wrapper script that handles +# AppImage launch fallbacks. + +DESKTOP_DIR=$HOME/.local/share/applications +UPDATE_JSON_URL="https://updates.phcode.io/tauri/update-latest-experimental-build.json" ICON_URL="https://updates.phcode.io/icons/phoenix_icon.png" INSTALL_DIR="$HOME/.phoenix-code" LINK_DIR="$HOME/.local/bin" -DESKTOP_DIR="$HOME/.local/share/applications" # Directory for desktop entries DESKTOP_ENTRY_NAME="PhoenixCode.desktop" -DESKTOP_APP_NAME="Phoenix Code Experimental Build" +DESKTOP_APP_NAME="Phoenix Code" DESKTOP_ENTRY="$DESKTOP_DIR/$DESKTOP_ENTRY_NAME" -SCRIPT_NAME="phcode" # Name of the script to invoke the binary +SCRIPT_NAME="phcode" BINARY_NAME="phoenix-code" +APPIMAGE_NAME="phoenix-code.AppImage" declare -a MIME_TYPES=( "text/html" @@ -65,328 +63,357 @@ declare -a MIME_TYPES=( "text/cjs" ) - -# Define color variables for easy reference GREEN="\e[32m" YELLOW="\e[33m" RED="\e[31m" RESET="\e[0m" -# Cleanup Function -# -# Purpose: -# The cleanup function is designed to ensure that the script cleans up any temporary -# resources it used during its execution. Specifically, it removes the temporary directory -# created at the beginning of the script, along with all its contents. This is crucial for -# maintaining a clean file system and avoiding the accumulation of unused files. -# -# Behavior: -# - The function targets a temporary directory specified by the `$TMP_DIR` variable. -# - It uses `rm -rf` to recursively and forcefully remove this directory and all its contents. -# This includes any temporary files, downloaded assets, or other data generated during the script's execution. -# - The cleanup is registered with a `trap` command on the `EXIT` signal, ensuring it executes -# automatically when the script exits, regardless of the exit point. This includes normal completion, -# errors, or manual termination via signals like SIGINT (Ctrl+C). -# -# Considerations: -# - The function assumes that `$TMP_DIR` is correctly set to the path of the temporary directory. -# If `$TMP_DIR` is unset or incorrectly set, the function may not perform as intended. -# - Using `rm -rf` carries the risk of deleting significant data if misused. It's crucial that `$TMP_DIR` -# is always a temporary directory intended for deletion and does not overlap with any critical system or user directories. -# - The function does not provide a confirmation prompt before deletion, so it's assumed that any data within -# `$TMP_DIR` is expendable and safe to delete. -# -# Usage: -# This function is not intended to be called directly by the user. It's automatically triggered by the `trap` -# command set up at the beginning of the script. However, if manual cleanup is necessary, ensure that `$TMP_DIR` -# is correctly set to the temporary directory path before invoking the function. -# cleanup() { - rm -rf "$TMP_DIR" # Command to remove the temporary directory and its contents. + rm -rf "$TMP_DIR" } +# Surface user interrupts (Ctrl+C / kill) with a clear message and exit 130, +# while still running cleanup. Without this, dpkg unpacks and sudo prompts can +# make the script look unresponsive to SIGINT for a beat. +on_interrupt() { + echo + echo -e "${RED}Interrupted by user. Cleaning up...${RESET}" + cleanup + exit 130 +} +trap cleanup EXIT +trap on_interrupt INT TERM -trap cleanup EXIT # Register the cleanup function to be called on script exit. +TMP_DIR=$(mktemp -d) +configure_wget_options() { + local wget_version + wget_version=$(wget --version | head -n1 | awk '{print $3}') + local major_version + major_version=$(echo "$wget_version" | cut -d. -f1) -TMP_DIR=$(mktemp -d) + if [[ "$major_version" -ge 2 ]]; then + echo "-c --tries=10 --timeout=30 --waitretry=5 --progress=bar --retry-connrefused -O" + else + echo "-c -N --tries=10 --timeout=30 --waitretry=5 --retry-connrefused --show-progress -qO" + fi +} -# Create Invocation Script Function -# -# Purpose: -# Generates a Bash script that serves as a wrapper for invoking the installed binary of Phoenix Code. -# This script is intended to simplify the execution of Phoenix Code by providing a more accessible -# command interface. The generated script is placed in a user-accessible binary directory, making it -# possible to run Phoenix Code from anywhere in the system without needing to specify the full path -# to the binary. -# -# Parameters: -# - binary_path: The absolute path to the directory where the Phoenix Code binary is located. -# - script_name: The name to be given to the invocation script. This name is used both for the -# script file itself and for the symlink created in the user's binary directory. -# - link_dir: The directory where a symlink to the invocation script will be placed, typically -# a location in the user's PATH, such as ~/.local/bin, for easy access. -# -# Behavior: -# 1. Creates a new Bash script in the specified 'binary_path' with the 'script_name'. -# 2. Writes a shebang line, a warning comment indicating the script is auto-generated, and a -# command to execute the Phoenix Code binary with any passed arguments ("$@"). -# 3. Sets executable permissions on the newly created script. -# 4. Copies the script to the specified 'link_dir', creating a symlink in a user-accessible location. -# -# Considerations: -# - It's crucial that 'binary_path' and 'link_dir' are valid directories and that 'script_name' -# does not conflict with existing files in these directories to prevent unintended overwrites. -# - The function does not check for the existence of 'binary_path' or 'link_dir'; it assumes -# these directories are already created and accessible. -# - The generated script includes a warning comment advising against deletion, as it is -# essential for the proper functioning of Phoenix Code when invoked through this script. -# -# Usage: -# This function is intended to be called during the installation or upgrade process of Phoenix Code, -# after the binary has been placed in its final location. It should not be called arbitrarily, as it -# assumes a specific setup and context established by the installer script. -# +check_architecture() { + local arch + arch=$(uname -m) + if [ "$arch" != "x86_64" ]; then + echo -e "${RED}This installer only supports 64-bit x86 (x86_64). Detected: $arch.${RESET}" + exit 1 + fi +} + +# Runtime-library probe: ask the AppImage to print its version and exit. This +# is handled at src-electron/main.js:23-26, but `require('electron')` runs +# before that handler — which means Chromium initializes its sandbox bits even +# for --version. On Ubuntu 24.04+ / Debian 13+ where unprivileged user +# namespaces are restricted, that init can SIGTRAP. Pass --no-sandbox so the +# probe isolates to "are the system shared libraries present and loadable?" +# regardless of the kernel's userns policy. +# +# The `{ … } 2>/dev/null` wrapper suppresses bash's own "Trace/breakpoint trap +# (core dumped)" report when the subprocess dies on signal — those messages +# look alarming but are harmless: we already detect failure via the exit code. +verify_appimage_launches() { + { "$1" --no-sandbox --version >/dev/null 2>&1; } 2>/dev/null +} + +# Returns 0 if the gnome-keyring secret-service daemon binary is on PATH. +# `--version` can't test this because keytar is only require()d at first use, +# so we need an independent presence check. +gnome_keyring_present() { + command -v gnome-keyring-daemon >/dev/null 2>&1 +} + +downloadLatestReleaseInfo() { + local release_info_file="$TMP_DIR/latest_release.json" + if [ ! -f "$release_info_file" ]; then + >&2 echo -e "${GREEN}Fetching the latest release information...${RESET}" + wget -qO "$release_info_file" "$UPDATE_JSON_URL" || { + >&2 echo -e "${RED}Failed to fetch release info from $UPDATE_JSON_URL. Check your internet connection.${RESET}" + exit 1 + } + fi + grep -Po '"version"\s*:\s*"\K[^"]+' "$release_info_file" | head -n1 +} + +# Parses the linux-x86_64 download URL out of the update JSON without needing jq. +getLinuxAppImageUrl() { + awk ' + /"linux-x86_64"[[:space:]]*:/ { in_linux=1 } + in_linux && /"url"[[:space:]]*:/ { + if (match($0, /"url"[[:space:]]*:[[:space:]]*"[^"]+"/)) { + s = substr($0, RSTART, RLENGTH) + sub(/^"url"[[:space:]]*:[[:space:]]*"/, "", s) + sub(/"$/, "", s) + print s + exit + } + } + ' "$TMP_DIR/latest_release.json" +} + +# Generates the `phcode` wrapper script. The wrapper runs the AppImage directly, +# falling back to --appimage-extract-and-run on FUSE failure or to --no-sandbox +# on userns/AppArmor failure (Ubuntu 24.04+ / Debian 13+ blocks Chromium's +# unprivileged user namespace sandbox unless our AppArmor profile is loaded). create_invocation_script() { - local binary_path="$1" + local install_dir="$1" local script_name="$2" local link_dir="$3" - echo "Creating an invocation script for the binary..." - # Start of the generated script - echo "#!/bin/bash" > "$binary_path/$script_name.sh" - # Add a warning comment about the script being auto-generated - echo "# DO NOT DELETE: This script is generated by the Phoenix Code installer." >> "$binary_path/$script_name" - echo "$binary_path/$BINARY_NAME \"\$@\"" >> "$binary_path/$script_name" - chmod +x "$binary_path/$script_name" - - echo "Copying the invocation script to $link_dir..." - mkdir -p "$link_dir" # Ensure the directory exists - cp "$binary_path/$script_name" "$link_dir/$script_name" + echo "Creating an invocation script for the AppImage..." + cat > "$install_dir/$script_name" <"\$ERR"; then + if grep -qiE 'fuse|libfuse|fusermount|/dev/fuse' "\$ERR"; then + rm -f "\$ERR" + exec "\$APPIMAGE" --appimage-extract-and-run "\$@" + fi + if grep -qiE 'userns|apparmor|sandbox|setuid|namespace|Check failed' "\$ERR"; then + rm -f "\$ERR" + exec "\$APPIMAGE" --no-sandbox "\$@" + fi + cat "\$ERR" >&2 + rm -f "\$ERR" + exit 1 +fi +rm -f "\$ERR" +EOF + chmod +x "$install_dir/$script_name" + mkdir -p "$link_dir" + cp "$install_dir/$script_name" "$link_dir/$script_name" echo -e "Invocation script created at: $link_dir/$script_name" } -# Install Dependencies Function -# -# Purpose: -# Installs the necessary dependencies for Phoenix Code to run properly on the user's system. -# This function identifies the user's Linux distribution and installs the appropriate packages -# using the distribution's package manager. It supports Ubuntu/Debian, Fedora/RHEL/CentOS, and -# Arch Linux distributions. -# -# Behavior: -# 1. Checks for administrative privileges by attempting a non-interactive `sudo` command. -# If the command fails, it prompts the user to enter their password for `sudo` access. -# 2. Determines the Linux distribution by examining the contents of `/etc/os-release`. -# 3. Based on the identified distribution, executes the appropriate package manager command -# with the required packages. Supported package managers are `apt` for Debian-based systems, -# `dnf` for Fedora/RHEL/CentOS, and `pacman` for Arch Linux. -# 4. Updates the package index (if applicable) and installs the packages. -# -# Considerations: -# - The function requires internet access to fetch package information and install packages. -# - It assumes that the user has `sudo` privileges to install packages system-wide. -# - The list of dependencies is hardcoded within the function. If Phoenix Code's dependencies change, -# this function must be updated accordingly. -# - Unsupported distributions will result in an error message and termination of the script, -# as the required dependencies cannot be installed automatically. -# - This function does not handle potential errors from package installation commands. It's -# recommended to monitor the output for any issues that may require manual intervention. -# -# Usage: -# This function is intended to be called during the Phoenix Code installation process, typically -# before attempting to run or configure the Phoenix Code binary. It ensures that all prerequisites -# are met for a successful Phoenix Code execution. -# -install_dependencies() { - echo "Attempting to install required dependencies..." - - # Inform the user that the next steps require administrative access - echo "The installation of dependencies requires administrative access. You may be prompted to enter your password." - # Check if the script can execute sudo commands without interaction - if ! sudo -n true 2>/dev/null; then - echo "Please enter your password to proceed with the installation of dependencies." +# Returns "gnome-keyring" unless the current session is KDE. +# On KDE, kwallet implements the Secret Service interface (KF 5.97+ / ksecretd +# in 2026), and installing gnome-keyring causes a D-Bus activation race for +# org.freedesktop.secrets — see VSCode issue #189672. +gnome_keyring_pkg_if_needed() { + if [[ "${XDG_CURRENT_DESKTOP:-}" =~ KDE ]]; then + echo "" + else + echo "gnome-keyring" fi +} - # Attempt to identify the Linux distribution - if [ -f /etc/os-release ]; then - . /etc/os-release - DISTRO=$ID +# Picks the libfuse2 package name for whatever Ubuntu/Debian release we're on. +# Ubuntu 24.04 Noble+ / Debian 13 Trixie+ / Kali rolling / Mint 22+ / Neon 24+ +# ship libfuse2t64 (the post-y2038 rename); older releases (Ubuntu 20.04 Focal +# / 22.04 Jammy, Debian 11/12, Mint 20/21, Neon 22) ship the pre-rename +# libfuse2. Both provide the same libfuse.so.2 SONAME, so picking by repo +# availability is correct. +choose_fuse_package_apt() { + if apt-cache show libfuse2t64 >/dev/null 2>&1; then + echo libfuse2t64 else - echo "Unable to identify the operating system." - exit 1 + echo libfuse2 fi +} - # Install dependencies based on the distribution - case "$DISTRO" in - ubuntu|debian|linuxmint) - echo "Detected an Ubuntu/Debian based distribution." - sudo apt update - sudo apt install libgtk-3-0 libwebkit2gtk-4.0-37 \ - gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ - gstreamer1.0-tools +# Per-distro package install. Only called when verify_appimage_launches failed +# OR the keyring daemon is missing — i.e., we've already decided we need sudo. +install_packages_for_distro() { + local distro="$1" keyring="$2" + # RHEL 10 does not ship libXScrnSaver in any default channel — left off the + # dnf list intentionally; Electron's XScreenSaver calls no-op on Wayland. + # libxss + libnotify are listed for Arch because gtk3 does NOT pull them in + # transitively but Electron dlopen's them at runtime. + local -a pkgs=() + case "$distro" in + ubuntu|debian|linuxmint|kali|neon) pkgs=("$(choose_fuse_package_apt)" libsecret-1-0) ;; + fedora|rhel|centos) pkgs=(fuse-libs fuse3-libs libsecret) ;; + arch|manjaro|cachyos) pkgs=(fuse2 libsecret libxss libnotify) ;; + esac + [ -n "$keyring" ] && pkgs+=("$keyring") + + case "$distro" in + ubuntu|debian|linuxmint|kali|neon) + # No `yes |` pipeline: under `set -o pipefail`, `yes` dies of SIGPIPE + # after apt exits and the pipeline returns 141, silently aborting the + # installer. Use apt-get -y, and keep user /etc config files via the + # force-conf* options so an unrelated pending upgrade can't prompt. + echo "Detected an Ubuntu/Debian-based distribution." + sudo apt-get update + # shellcheck disable=SC2068 + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ + -o Dpkg::Options::="--force-confdef" \ + -o Dpkg::Options::="--force-confold" \ + ${pkgs[@]} ;; fedora|rhel|centos) - echo "Detected a Fedora/Red Hat based distribution." - sudo dnf install webkit2gtk3 gtk3 \ - gstreamer1-plugins-base gstreamer1-plugins-good - ;; - arch|manjaro) - echo "Detected an Arch Linux based distribution." - sudo pacman -Syu - sudo pacman -S webkit2gtk gtk3 - ;; - *) - echo "Unsupported distribution. Please manually install the required dependencies." - exit 1 - ;; + echo "Detected a Fedora/Red Hat-based distribution." + # shellcheck disable=SC2068 + sudo dnf install -y ${pkgs[@]} + ;; + arch|manjaro|cachyos) + echo "Detected an Arch-based distribution." + sudo pacman -Sy + # shellcheck disable=SC2068 + sudo pacman -S --noconfirm --needed ${pkgs[@]} + ;; esac } -# Verify and Install Dependencies Function -# -# Purpose: -# This function is responsible for verifying if the Phoenix Code application can be successfully -# launched with the current set of installed dependencies. If the initial verification fails, it -# attempts to install the necessary dependencies and then re-verifies the application launch. -# -# Behavior: -# 1. Changes the current directory to the temporary directory where Phoenix Code is located. -# 2. Sets the executable permission for the Phoenix Code binary and attempts to run it with a -# verification-specific command (e.g., `--runVerify`). This step is intended to check if the -# application can start without installing additional dependencies. -# 3. If the initial verification succeeds, the function exits with a success status (0), indicating -# no further action is needed. -# 4. If the initial verification fails, the function proceeds to invoke `install_dependencies` to -# install any missing dependencies specific to the user's Linux distribution. -# 5. After installing dependencies, it attempts to verify the application launch again. If this -# second attempt succeeds, the function exits with a success status (0). -# 6. If the verification fails even after installing dependencies, the function prints an error -# message advising the user to check the application requirements or contact support, and -# exits with an error status (1). -# 7. Finally, the function ensures to change back to the original directory where the script was -# invoked, preserving the user's shell environment. -# -# Considerations: -# - The function assumes that the Phoenix Code binary is located in a temporary directory and -# that this directory is correctly set in the `$TMP_DIR` variable. -# - It relies on the Phoenix Code binary supporting a `--runVerify` command or equivalent for -# verification purposes. If such a command is not available, this part of the function needs -# adjustment. -# - Dependency installation requires administrative privileges; hence, the user might be prompted -# for a password to allow `sudo` operations. -# - The function does not handle all possible error scenarios during dependency installation. -# Users should monitor the output for potential issues that might need manual intervention. + +# Decides what (if anything) the user's system is missing for Phoenix Code to +# run, then installs only the missing piece(s). Three independent probes: # -# Usage: -# This function is intended to be called during the installation or upgrade process of Phoenix Code, -# before finalizing the installation, to ensure that all system requirements are met for the -# application to run properly. +# 1. System shared libraries — probed by running the downloaded AppImage +# with `--version`. main.js exits immediately on this flag, AFTER libfuse +# mounts the AppImage and AFTER the Electron binary loads its NEEDED libs. +# 2. gnome-keyring daemon — binary-presence check; `--version` doesn't +# exercise keytar so it can't verify libsecret/Secret Service end-to-end. # -verify_and_install_dependencies() { - cd "$TMP_DIR/phoenix-code" - # Ensure the binary is executable - chmod +x "./phoenix-code" - # First attempt to verify the application launch - if ./phoenix-code --runVerify; then - echo "Application launch verification successful." - return 0 # Exit the function successfully if verification succeeds - else - echo "Initial verification failed. Attempting to install dependencies..." - install_dependencies # Function to install required dependencies +# Sudo is requested ONLY if at least one of the probes fails. +ensure_runtime_dependencies() { + local appimage="$1" + if [ ! -f /etc/os-release ]; then + echo -e "${RED}Unable to identify the operating system (no /etc/os-release).${RESET}" + exit 1 fi + . /etc/os-release + local distro="${ID:-}" + case "$distro" in + ubuntu|debian|linuxmint|kali|neon|fedora|rhel|centos|arch|manjaro|cachyos) ;; + *) + echo -e "${RED}Unsupported distribution: $distro. Please install libfuse2/fuse2, libsecret, and (on non-KDE) gnome-keyring manually.${RESET}" + exit 1 + ;; + esac + local keyring; keyring=$(gnome_keyring_pkg_if_needed) - # Second attempt to verify the application launch after installing dependencies - if ./phoenix-code --runVerify; then - echo "Application launch verification successful after installing dependencies." - cd - - return 0 # Exit the function successfully if verification succeeds + local libs_ok=1 keyring_ok=1 + verify_appimage_launches "$appimage" || libs_ok=0 + if [ -n "$keyring" ] && ! gnome_keyring_present; then keyring_ok=0; fi + + if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ]; then + echo -e "${GREEN}All runtime dependencies already present.${RESET}" + return 0 + fi + + if [ "$libs_ok" = 0 ]; then + echo "Installing missing runtime libraries..." else - echo "Verification failed even after installing dependencies. Please check the application requirements or contact support." - cd - - return 1 # Return an error code to indicate failure + echo "Installing system keychain daemon..." + fi + echo "This step requires administrative access." + if ! sudo -n true 2>/dev/null; then + echo "Please enter your password to proceed." + fi + + install_packages_for_distro "$distro" "$keyring" + # Re-probe libraries; if still failing, surface a clear warning but don't + # abort — the AppImage may have a runtime-specific issue we can't fix here. + if [ "$libs_ok" = 0 ] && ! verify_appimage_launches "$appimage"; then + echo -e "${YELLOW}WARN: AppImage still fails --version after dep install.${RESET}" fi } -# Set Phoenix Code as the default application for specified MIME types -set_default_application() { - local desktop_file="$DESKTOP_ENTRY_NAME" # Name of the Phoenix Code desktop entry file - for mime_type in "${MIME_TYPES[@]}"; do - # Skip setting default application for text/html - if [ "$mime_type" = "text/html" ]; then - continue # Skip to the next iteration +# Returns 0 if any of the common display managers is configured to autologin +# the current user. All probed files are world-readable; no sudo, no prompts. +# Used only to enrich the post-install keyring hint with a probable cause. +autologin_detected() { + local user="${USER:-$(id -un)}" + local f + for f in /etc/gdm3/custom.conf /etc/gdm3/daemon.conf /etc/gdm/custom.conf; do + [ -r "$f" ] && grep -qE '^[[:space:]]*AutomaticLoginEnable[[:space:]]*=[[:space:]]*[Tt]rue' "$f" && return 0 + done + for f in /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.d/*.conf; do + [ -r "$f" ] && grep -qE "^[[:space:]]*autologin-user[[:space:]]*=[[:space:]]*${user}([[:space:]]|$)" "$f" && return 0 + done + for f in /etc/sddm.conf /etc/sddm.conf.d/*.conf; do + [ -r "$f" ] && grep -qE "^[[:space:]]*User[[:space:]]*=[[:space:]]*${user}([[:space:]]|$)" "$f" && return 0 + done + return 1 +} + +# If the user's login keyring is currently locked, print a one-time hint so +# the first-launch unlock dialog isn't a surprise. Uses the side-effect-free +# Locked property on org.freedesktop.Secret.Collection — querying it does NOT +# trigger a prompt. Silently no-ops when busctl is missing, when no Secret +# Service is on the bus (e.g., kwallet-only setups where this message would be +# wrong), or when the keyring is already unlocked. +print_keyring_hint_if_locked() { + command -v busctl >/dev/null 2>&1 || return 0 + local locked + locked=$(busctl --user --no-pager call org.freedesktop.secrets \ + /org/freedesktop/secrets/collection/login \ + org.freedesktop.DBus.Properties Get \ + ss org.freedesktop.Secret.Collection Locked 2>/dev/null \ + | awk '/^b /{print $NF}') + [ "$locked" = "true" ] || return 0 + + echo + echo -e "${YELLOW}Note: your system keychain is currently locked.${RESET}" + echo -e "${YELLOW}The first time Phoenix Code stores or reads a credential, gnome-keyring${RESET}" + echo -e "${YELLOW}will ask once for your login password to unlock it. This is a one-time${RESET}" + echo -e "${YELLOW}per-session Linux behavior — not a Phoenix Code prompt.${RESET}" + if autologin_detected; then + echo -e "${YELLOW}(Autologin is enabled on this machine, which is why PAM did not${RESET}" + echo -e "${YELLOW} auto-unlock the keyring at session start.)${RESET}" + fi +} + +set_default_application() { + local desktop_file="$DESKTOP_ENTRY_NAME" + + if [ "${KDE_SESSION_VERSION:-0}" -gt 0 ] && ! command -v qtpaths &> /dev/null; then + local qtpaths_locations=( + "/usr/lib/qt6/bin" + "/usr/lib/qt5/bin" + "/usr/lib64/qt6/bin" + "/usr/lib64/qt5/bin" + "/opt/qt6/bin" + "/opt/qt5/bin" + ) + for qtpath_dir in "${qtpaths_locations[@]}"; do + if [ -x "$qtpath_dir/qtpaths" ] || [ -x "$qtpath_dir/qtpaths6" ]; then + export PATH="$qtpath_dir:$PATH" + break fi + done + fi - xdg-mime default "$desktop_file" "$mime_type" + for mime_type in "${MIME_TYPES[@]}"; do + if [ "$mime_type" = "text/html" ]; then + continue + fi + xdg-mime default "$desktop_file" "$mime_type" done echo -e "${GREEN}Success! You can now right-click on files in your file manager and choose Phoenix Code to edit them.${RESET}" } -# Copy Files to Destination Function -# -# Purpose: -# This function is responsible for setting up the final installation directory for Phoenix Code, -# moving the necessary files into place, creating the invocation script, and setting up the desktop -# entry for the application. It ensures that all components of Phoenix Code are correctly installed -# and configured in the user's environment. -# -# Behavior: -# 1. Creates the installation directory at `$INSTALL_DIR`, where Phoenix Code and its related files -# will reside. -# 2. Similarly, ensures the desktop entry directory `$DESKTOP_DIR` exists for application launchers. -# 3. Moves the downloaded and possibly compiled Phoenix Code binary and other necessary files from -# the temporary directory (`$TMP_DIR`) to the installation directory. -# 4. Downloads and moves the application icon to the installation directory for use in the desktop entry. -# 5. Sets the correct executable permissions for the Phoenix Code binary to ensure it can be run by the user. -# 6. Calls `create_invocation_script` to generate a wrapper script for easier execution of Phoenix Code. -# 7. Constructs a `.desktop` file for Phoenix Code, defining how it should appear in application menus and -# launchers, and places this file in the desktop entry directory. -# 8. Updates the system's desktop database to ensure the new application entry is recognized and available -# in application menus and launchers. This step might involve commands like `update-desktop-database` -# or `kbuildsycoca5`, depending on the desktop environment. -# -# Considerations: -# - The function assumes that the necessary files for Phoenix Code are already present in `$TMP_DIR` and -# that this directory is correctly set. -# - It relies on specific directory paths (`$INSTALL_DIR`, `$DESKTOP_DIR`, `$LINK_DIR`) being set and -# accessible. If these variables are unset or incorrect, the function may not perform as intended. -# - Administrative privileges may be required for certain operations, such as updating the desktop database -# or setting permissions, depending on the system's configuration. -# - Care is taken to avoid overwriting existing files or directories without confirmation, but this function -# does perform significant file system operations that could potentially conflict with existing user data -# or system settings. -# -# Usage: -# This function is intended to be called as part of the Phoenix Code installation process, after the -# application's binary and other necessary files have been prepared in a temporary location. It finalizes -# the installation by placing all components in their appropriate locations within the user's system. -# -copyFilesToDestination(){ +copyFilesToDestination() { echo "Setting up the installation directory at $INSTALL_DIR..." mkdir -p "$INSTALL_DIR" mkdir -p "$DESKTOP_DIR" - echo -e "${YELLOW}Installation directory set up at: $INSTALL_DIR${RESET}" - echo "Moving the necessary files to the installation directory..." - echo -e "Phoenix Code files moved to: $INSTALL_DIR" - mv "$TMP_DIR"/phoenix-code/* "$INSTALL_DIR/" || { - echo -e "${RED}Failed to move the files to the installation directory. Please check the permissions and try again.${RESET}" + mv "$TMP_DIR/$APPIMAGE_NAME" "$INSTALL_DIR/$APPIMAGE_NAME" || { + echo -e "${RED}Failed to move the AppImage to the installation directory. Please check the permissions and try again.${RESET}" exit 1 } - # Move the icon to the installation directory mv "$TMP_DIR/icon.png" "$INSTALL_DIR/" - echo "Setting the correct permissions for the executable..." - chmod +x "$INSTALL_DIR/phoenix-code" || { - echo -e "${RED}Failed to set executable permissions. Please check the file path and permissions.${RESET}" + chmod +x "$INSTALL_DIR/$APPIMAGE_NAME" || { + echo -e "${RED}Failed to set executable permissions on the AppImage.${RESET}" exit 1 } - echo -e "Executable permissions set for: $INSTALL_DIR/$BINARY_NAME" + echo -e "AppImage installed at: $INSTALL_DIR/$APPIMAGE_NAME" - mkdir -p "$LINK_DIR" # Ensure the directory exists - # Call the function to create and copy the invocation script + mkdir -p "$LINK_DIR" create_invocation_script "$INSTALL_DIR" "$SCRIPT_NAME" "$LINK_DIR" - # Convert MIME types array to semicolon-separated string for the desktop entry - MIME_TYPES_STRING=$(IFS=";"; echo "${MIME_TYPES[*]}") + + local mime_types_string + mime_types_string=$(IFS=";"; echo "${MIME_TYPES[*]}") echo "Creating desktop entry..." cat > "$DESKTOP_ENTRY" < /dev/null; then - update-desktop-database "$DESKTOP_DIR" - echo -e "Desktop database updated in: $DESKTOP_DIR" + update-desktop-database "$DESKTOP_DIR" fi - - # Update the KDE desktop database if KDE is in use - if [ "$XDG_CURRENT_DESKTOP" = "KDE" ]; then + if [ "${XDG_CURRENT_DESKTOP:-}" = "KDE" ]; then if command -v kbuildsycoca5 &> /dev/null; then - kbuildsycoca5 + kbuildsycoca5 fi fi - # Set Phoenix Code as the default application for the MIME types - set_default_application - echo -e "${GREEN}Installation completed successfully. Phoenix Code is now installed.${RESET}" - -} -downloadLatestReleaseInfo() { - local release_info_file="$TMP_DIR/latest_release.json" - - if [ -f "$release_info_file" ]; then - # Only extract and echo the version number, without any additional messages - grep -Po '"tag_name": "dev-app-v\K[\d.]+(?=")' "$release_info_file" - return + if [[ "${XDG_CURRENT_DESKTOP:-}" =~ LXQt ]]; then + if command -v xdg-desktop-menu &> /dev/null; then + xdg-desktop-menu forceupdate + echo -e "${YELLOW}Please log out and log back in to see Phoenix Code in the panel.${RESET}" + else + echo -e "${RED}Failed to update LXQt menu. Please log out and log back in to see Phoenix Code in the panel.${RESET}" + fi fi - # Direct informational messages to stderr to avoid them being captured or displayed unexpectedly - >&2 echo -e "${GREEN}Fetching the latest release information from $GITHUB_REPO...${RESET} " - wget -qO "$release_info_file" "$API_URL" || { - >&2 echo -e "${RED}Failed to fetch the latest release information. Please check your internet connection and try again.${RESET}" - exit 1 - } - - # Only extract and echo the version number after successful download - grep -Po '"tag_name": "dev-app-v\K[\d.]+(?=")' "$release_info_file" + set_default_application + echo -e "${GREEN}Installation completed successfully. Phoenix Code is now installed.${RESET}" + print_keyring_hint_if_locked } -# Download Latest Release Information Function -# -# Purpose: -# Retrieves the latest release information for Phoenix Code from its GitHub repository. This function -# is designed to fetch the JSON data of the latest release using GitHub's API, allowing the script to -# determine the most recent version of Phoenix Code and any associated assets like binaries or icons. -# -# Behavior: -# 1. Checks if the release information file (`latest_release.json`) already exists in the temporary -# directory (`$TMP_DIR`). If it does, the function assumes the latest release information has already -# been fetched and proceeds to extract the version number from this file without re-downloading. -# 2. If the release information file does not exist, the function uses `wget` to download the JSON data -# from the GitHub API URL specified by `$API_URL`, which should point to the latest release endpoint -# of the Phoenix Code GitHub repository. -# 3. The JSON data is saved to `latest_release.json` in the temporary directory. -# 4. Extracts the version number from the downloaded JSON data using `grep` with a Perl-compatible regular -# expression (PCRE) that looks for the `tag_name` field. This field is expected to contain the version -# number prefixed by `prod-app-v` (e.g., `prod-app-v1.2.3`), and the function extracts the version number -# portion. -# 5. Echoes the extracted version number for use by the calling context. -# -# Considerations: -# - This function relies on external utilities `wget` and `grep` with PCRE support, which must be available -# in the execution environment. -# - The GitHub API URL (`$API_URL`) must be correctly set to the latest release endpoint of the Phoenix Code -# repository for this function to work as intended. -# - The function assumes a specific version naming convention (`prod-app-vX.Y.Z`). If this convention changes, -# the `grep` pattern used to extract the version number may need to be updated. -# - Network connectivity is required to fetch the release information from GitHub. If the script is run in an -# environment without internet access, or if GitHub is unreachable, this function will fail to retrieve the -# latest release information. -# - The function does not perform error handling for the `wget` command. If the download fails, the script may -# exit or behave unexpectedly depending on the `set -e` setting. -# -# Usage: -# This function is intended to be called during the installation or upgrade process to determine the latest -# version of Phoenix Code. The extracted version number can be used to compare with the currently installed -# version and decide whether an upgrade is necessary. -# -downloadAndInstall(){ +downloadAndInstall() { echo "Using temporary directory $TMP_DIR for processing" downloadLatestReleaseInfo > /dev/null - CURRENT_GLIBC_VERSION=$(ldd --version | grep "ldd" | awk '{print $NF}') - echo "Current GLIBC version: $CURRENT_GLIBC_VERSION" - - BEST_MATCH_URL="" - BEST_MATCH_VERSION=0 - echo "Searching for a compatible binary..." - - while read -r BINARY_URL; do - BINARY_GLIBC_VERSION=$(echo "$BINARY_URL" | grep -oP 'GLIBC-\K[\d\.]+(?=\.tar\.gz)') - if awk -v bin_ver="$BINARY_GLIBC_VERSION" -v cur_ver="$CURRENT_GLIBC_VERSION" -v best_ver="$BEST_MATCH_VERSION" 'BEGIN { bin_ver += 0; cur_ver += 0; best_ver += 0; exit !(bin_ver <= cur_ver && bin_ver > best_ver) }'; then - BEST_MATCH_URL="$BINARY_URL" - BEST_MATCH_VERSION="$BINARY_GLIBC_VERSION" - echo "Found a new best match: $BEST_MATCH_URL with GLIBC version $BEST_MATCH_VERSION" - fi - done < <(grep -oP '"browser_download_url": "\K(.*_linux_bin-GLIBC-[\d\.]+\.tar\.gz)(?=")' "$TMP_DIR/latest_release.json") + check_architecture - if [ -z "$BEST_MATCH_URL" ]; then - echo -e "${RED}No compatible binary found for the current GLIBC version ($CURRENT_GLIBC_VERSION). Exiting installation.${RESET}" + local appimage_url + appimage_url=$(getLinuxAppImageUrl) + if [ -z "$appimage_url" ]; then + echo -e "${RED}Could not find a linux-x86_64 AppImage URL in the release info.${RESET}" exit 1 fi - echo -e "${YELLOW}Downloading the compatible binary from $BEST_MATCH_URL...${RESET}" - wget -c -N --tries=10 --timeout=30 --waitretry=5 --retry-connrefused --show-progress -qO "$TMP_DIR/phoenix-code.tar.gz" "$BEST_MATCH_URL" || { - echo -e "${RED}Failed to download the binary. Please check your internet connection and try again.${RESET}" + echo -e "${YELLOW}Downloading the AppImage from $appimage_url...${RESET}" + local wget_opts + wget_opts=$(configure_wget_options) + # shellcheck disable=SC2086 + wget $wget_opts "$TMP_DIR/$APPIMAGE_NAME" "$appimage_url" || { + echo -e "${RED}Failed to download the AppImage. Please check your internet connection and try again.${RESET}" exit 1 } - echo -e "Downloading the icon..." - wget -c -N --tries=10 --timeout=30 --waitretry=5 --retry-connrefused --show-progress -qO "$TMP_DIR/icon.png" "$ICON_URL" || { - echo -e "${RED}Failed to download Icon${RESET}"; - exit 1; - } - echo "Extracting the binary to $TMP_DIR..." - tar -xzf "$TMP_DIR/phoenix-code.tar.gz" -C "$TMP_DIR" || { - echo -e "${RED}Failed to extract the binary. The downloaded file might be corrupt.${RESET}" + + echo "Downloading the icon..." + # shellcheck disable=SC2086 + wget $wget_opts "$TMP_DIR/icon.png" "$ICON_URL" || { + echo -e "${RED}Failed to download the icon.${RESET}" exit 1 } - if [ -f "$TMP_DIR/phoenix-code/phoenix-code-experimental-build" ]; then - echo "The file $TMP_DIR/phoenix-code/phoenix-code-experimental-build exist. Dev build detected." - mv "$TMP_DIR/phoenix-code/phoenix-code-experimental-build" "$TMP_DIR/phoenix-code/phoenix-code" - fi + chmod +x "$TMP_DIR/$APPIMAGE_NAME" - # Verify binary execution and install dependencies if necessary - if ! verify_and_install_dependencies; then - echo -e "${RED}Unable to successfully verify application launch. Exiting installation.${RESET}" - exit 1 - fi + ensure_runtime_dependencies "$TMP_DIR/$APPIMAGE_NAME" } -# Install Function -# -# Purpose: -# Facilitates the entire installation process of Phoenix Code on the user's system. This includes -# checking for previous installations, downloading and installing the latest version, setting up -# necessary directories, and ensuring that all dependencies are met. It also handles user prompts -# for repair or reinstallation options if Phoenix Code is already installed. -# -# Behavior: -# 1. Checks if Phoenix Code is already installed by looking for specific files or directories -# associated with the installation (e.g., the invocation script in `$LINK_DIR` or the main -# directory in `$INSTALL_DIR`). If found, prompts the user with an option to repair (reinstall). -# 2. If the user opts for repair, the function proceeds to uninstall the existing installation and -# then reinstalls the application, ensuring the user ends up with the latest version. -# 3. For a fresh installation or repair, the function calls `downloadAndInstall` to fetch the latest -# release and install it. This step involves downloading the binary, setting permissions, and -# moving files to their correct locations. -# 4. After installing the binary, `copyFilesToDestination` is invoked to set up the desktop entry, -# create an invocation script, and perform other necessary post-installation configurations. -# -# Considerations: -# - The function assumes that the user has sufficient permissions to write to the target directories -# and install Phoenix Code. Administrative privileges may be required for certain operations. -# - If the repair option is chosen, all existing Phoenix Code files and configurations in the installation -# directory will be replaced. Users should ensure that any custom configurations or data are backed up -# before proceeding. -# - Network connectivity is required to download the latest version of Phoenix Code from the repository. -# - The function does not explicitly handle all potential error scenarios, such as download failures or -# permission issues. It relies on the `set -e` option to halt execution on any unhandled errors. -# -# Usage: -# This function is intended to be invoked when the script is run without specific options (e.g., not an -# uninstall or upgrade). It can be directly called from the command line when the user wishes to install -# Phoenix Code, or it may be triggered by default when the script is executed without arguments. -# + +# Temporary cleanup for files from older installer versions. +uninstallBetaAppImage() { + rm -f "$LINK_DIR"/phoenix_icon.png +} + install() { - # Check if the application is already installed if [ -f "$LINK_DIR/$SCRIPT_NAME" ] || [ -d "$INSTALL_DIR" ]; then echo -e "${YELLOW}Phoenix Code appears to be already installed.${RESET}" - - # Simplified prompt for reinstall without detailed explanation - read -r -p "Would you like to reinstall it? (y/N): " response - case "$response" in - [Yy]* ) + if [ ! -t 0 ]; then + echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" + uninstall + downloadAndInstall + copyFilesToDestination + else + read -r -p "Would you like to reinstall it? (y/N): " response + case "$response" in + [Yy]* ) echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" uninstall downloadAndInstall copyFilesToDestination ;; - * ) + * ) echo -e "${RED}Reinstall aborted by the user.${RESET}" exit 0 ;; - esac + esac + fi else downloadAndInstall copyFilesToDestination fi } -# Temporary code to clean up earlier beta installations -function uninstallBetaAppImage() { - rm -f "$LINK_DIR"/phoenix_icon.png -} -# Upgrade Function -# -# Purpose: -# Manages the upgrade process for Phoenix Code, ensuring that the user's installation is updated -# to the latest version available. This function checks the currently installed version against -# the latest version available in the GitHub repository. If a newer version is found, it proceeds -# to download and install the update, effectively replacing the old version with the new one. -# -# Behavior: -# 1. Verifies that Phoenix Code is already installed by checking for the existence of the installation -# directory and the invocation script. If not found, it exits with an error message indicating that -# Phoenix Code must be installed before it can be upgraded. -# 2. Determines the currently installed version of Phoenix Code by invoking the binary with a version -# check command (e.g., `phoenix-code --version`) and captures the output. -# 3. Fetches the latest release information from the GitHub repository using the `downloadLatestReleaseInfo` -# function, which retrieves the version number of the latest release. -# 4. Compares the currently installed version with the latest version. If the installed version is older, -# it initiates the upgrade process by calling `downloadAndInstall`, which handles the download and -# installation of the new version. -# 5. If the currently installed version is already up to date, it informs the user that no upgrade is necessary. -# -# Considerations: -# - The function relies on the correct implementation of version checking in the Phoenix Code binary. -# The binary must support a command-line option to output its version, and the output format must be -# consistent for correct parsing and comparison. -# - Network connectivity is required to fetch the latest release information from the GitHub repository. -# The upgrade process will fail if the script cannot connect to GitHub. -# - The user must have sufficient permissions to overwrite the existing installation files and to -# execute network requests (e.g., downloading the latest release). -# - The upgrade process replaces the existing installation files. Users should ensure that any -# necessary backups or custom configurations are saved before proceeding with the upgrade. -# -# Usage: -# This function is intended to be called when the user explicitly wants to upgrade their installation -# of Phoenix Code to the latest version. It can be triggered by passing an `--upgrade` option to the -# script, as defined in the script's main case statement handling command-line arguments. -# upgrade() { echo -e "${YELLOW}Checking for upgrades to Phoenix Code...${RESET}" - # Ensure Phoenix Code is installed if [ ! -f "$LINK_DIR/$SCRIPT_NAME" ] && [ ! -d "$INSTALL_DIR" ]; then echo -e "${RED}Phoenix Code is not installed. Please install it first.${RESET}" exit 1 fi - # Get the current installed version - CURRENT_VERSION=$("$INSTALL_DIR/phoenix-code" --version) - echo "Current installed version: $CURRENT_VERSION" - LATEST_VERSION=$(downloadLatestReleaseInfo) - # Now LATEST_VERSION should only contain the version number without extra messages - echo "Latest available version: $LATEST_VERSION" + local current_version + current_version=$("$INSTALL_DIR/$APPIMAGE_NAME" --version 2>/dev/null || true) + echo "Current installed version: ${current_version:-unknown}" + + local latest_version + latest_version=$(downloadLatestReleaseInfo) + echo "Latest available version: $latest_version" - # Compare versions and upgrade if the latest version is greater - if [ "$(printf '%s\n' "$LATEST_VERSION" "$CURRENT_VERSION" | sort -V | tail -n1)" = "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "$CURRENT_VERSION" ]; then + if [ -n "$latest_version" ] && [ "$(printf '%s\n' "$latest_version" "$current_version" | sort -V | tail -n1)" = "$latest_version" ] && [ "$latest_version" != "$current_version" ]; then echo -e "${YELLOW}A newer version of Phoenix Code is available. Proceeding with the upgrade...${RESET}" downloadAndInstall uninstall @@ -659,48 +547,12 @@ upgrade() { else echo "Your Phoenix Code installation is up-to-date." fi - } -# Uninstall Function -# -# Purpose: -# Handles the removal of Phoenix Code from the user's system. This function is responsible for -# cleaning up all files, directories, and links created during the installation process, effectively -# reverting any changes made by the installer script. It ensures that Phoenix Code and its components -# are uninstalled cleanly, leaving no residual files or configurations. -# -# Behavior: -# 1. Checks for and removes the invocation script located in the user's binary directory (`$LINK_DIR`). -# This step prevents the Phoenix Code command from being accessible after uninstallation. -# 2. Deletes the desktop entry file (`$DESKTOP_ENTRY`) to remove Phoenix Code from application menus -# and launchers. This step is crucial for desktop environments to recognize that the application has been uninstalled. -# 3. Updates the desktop database, if necessary, to reflect the removal of the Phoenix Code desktop entry. -# This may involve commands like `update-desktop-database` or `kbuildsycoca5`, depending on the desktop environment. -# 4. Removes the Phoenix Code installation directory (`$INSTALL_DIR`) and all its contents, including -# the main binary, configuration files, and any other related files placed during installation. -# 5. Prints a confirmation message indicating that the uninstallation process has been completed successfully. -# -# Considerations: -# - The function assumes that the paths stored in `$LINK_DIR`, `$DESKTOP_ENTRY`, and `$INSTALL_DIR` accurately -# reflect the locations used during the installation. If these variables are incorrect, the uninstallation -# process may not remove all components. -# - Administrative privileges may be required for some operations, especially if Phoenix Code was installed -# in system-wide directories. -# - Users should be advised to back up any important data or configurations related to Phoenix Code before -# initiating the uninstallation process, as this function will remove all associated files without recovery options. -# - The function does not provide a rollback mechanism. Once the uninstallation is initiated, the process cannot -# be reversed through this script. -# -# Usage: -# This function is intended to be invoked when the user wishes to completely remove Phoenix Code from their system. -# It can be triggered by passing an `--uninstall` option to the script, as defined in the script's main case statement -# handling command-line arguments. Users should be prompted to confirm their intention to uninstall before this -# function is executed to prevent accidental data loss. -# + uninstall() { echo -e "${YELLOW}Starting uninstallation of Phoenix Code...${RESET}" uninstallBetaAppImage - # Remove the invocation script from ~/.local/bin + if [ -f "$LINK_DIR/$SCRIPT_NAME" ]; then echo -e "${YELLOW}Removing invocation script from $LINK_DIR...${RESET}" rm "$LINK_DIR/$SCRIPT_NAME" @@ -708,34 +560,33 @@ uninstall() { echo -e "${RED}Invocation script not found in $LINK_DIR. Skipping...${RESET}" fi - # Delete the desktop entry if [ -f "$DESKTOP_ENTRY" ]; then echo -e "${YELLOW}Removing desktop entry...${RESET}" rm "$DESKTOP_ENTRY" - - # Update the desktop database for GNOME, Unity, XFCE, etc. - echo -e "${YELLOW}Updating desktop database...${RESET}" if command -v update-desktop-database &> /dev/null; then - update-desktop-database "$DESKTOP_DIR" + update-desktop-database "$DESKTOP_DIR" fi - - # Update the KDE desktop database if KDE is in use - if [ "$XDG_CURRENT_DESKTOP" = "KDE" ]; then - if command -v kbuildsycoca5 &> /dev/null; then - kbuildsycoca5 - fi + if [ "${XDG_CURRENT_DESKTOP:-}" = "KDE" ]; then + if command -v kbuildsycoca5 &> /dev/null; then + kbuildsycoca5 + fi + fi + if [[ "${XDG_CURRENT_DESKTOP:-}" =~ LXQt ]]; then + if command -v xdg-desktop-menu &> /dev/null; then + xdg-desktop-menu forceupdate + fi fi else - echo -e "${RED}Desktop entry not found. Skipping...${RESET}" + echo -e "${RED}Desktop entry not found. Skipping...${RESET}" fi - # Remove the installation directory and its contents if [ -d "$INSTALL_DIR" ]; then echo -e "${YELLOW}Removing installation directory and its contents...${RESET}" rm -rf "$INSTALL_DIR" else echo -e "${RED}Installation directory not found. Skipping...${RESET}" fi + echo -e "${GREEN}Uninstallation of Phoenix Code completed.${RESET}" } @@ -750,7 +601,7 @@ show_help() { echo echo "Without any options, the script will install Phoenix Code." } -# Check for GUI session by looking for DISPLAY or WAYLAND_DISPLAY variables + if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then echo "This script should only be run from terminals in GUI sessions." exit 1 @@ -758,22 +609,20 @@ fi case "${1-}" in -h|--help) - show_help # Function to show help + show_help ;; --uninstall) - uninstall # Function to uninstall + uninstall ;; --upgrade) - upgrade # Function to upgrade + upgrade ;; "") - # This case handles when $1 is unset (acts as a default action) - install # Function to install + install ;; *) - # This case handles unexpected arguments echo "Invalid option: $1" >&2 - show_help # Assuming you have a function to show usage information + show_help exit 1 ;; esac diff --git a/docs/linux/installer.sh b/docs/linux/installer.sh index 8c442ddc..87f8b54e 100755 --- a/docs/linux/installer.sh +++ b/docs/linux/installer.sh @@ -1,40 +1,25 @@ #!/bin/bash -set -euo pipefail - -# Phoenix Code Linux installer. -# -# Downloads the latest Phoenix Code AppImage from the update JSON, then probes -# the downloaded AppImage with `--version` (early-exit handler at -# src-electron/main.js:23-26). If the probe succeeds, no apt/dnf/pacman install -# is attempted — the system already has every shared library Phoenix Code -# needs at startup, regardless of which package name provides it. -# -# Beyond the --version probe, the installer also checks for the gnome-keyring -# daemon binary — libsecret needs the daemon for keytar credential storage, -# and --version exits before keytar is require()d. Sudo is only requested if -# libs or the keyring daemon are actually missing. -# -# On Ubuntu 24.04+ / Debian 13+, Chromium's unprivileged userns sandbox can -# be blocked by the kernel sysctl. The app still launches because the wrapper -# script falls back to --no-sandbox; we no longer install an AppArmor profile -# to keep the kernel sandbox enabled (the renderer-RCE-to-syscall threat the -# kernel sandbox guards against is narrow, and Phoenix Code's actual attack -# surface flows through the IPC bridge which the kernel sandbox doesn't gate). -# -# Also wires up a desktop entry + `phcode` wrapper script that handles -# AppImage launch fallbacks. - -DESKTOP_DIR=$HOME/.local/share/applications -UPDATE_JSON_URL="https://updates.phcode.io/tauri/update-latest-experimental-build.json" +set -euo pipefail # Exit immediately if a command exits with a non-zero status. +# Define common variables +DESKTOP_DIR=$HOME/.local/share/applications # Directory for desktop entries +GITHUB_REPO="phcode-dev/phoenix-desktop" +API_URL="https://api.github.com/repos/$GITHUB_REPO/releases/latest" ICON_URL="https://updates.phcode.io/icons/phoenix_icon.png" +GTK_URL="https://github.com/phcode-dev/dependencies/releases/download/v1.0.0/gtk.tar.xz" +WEBKIT2GTK_URL="https://github.com/phcode-dev/dependencies/releases/download/v1.0.0/webkit2gtk-4.0.tar.xz" INSTALL_DIR="$HOME/.phoenix-code" LINK_DIR="$HOME/.local/bin" DESKTOP_ENTRY_NAME="PhoenixCode.desktop" DESKTOP_APP_NAME="Phoenix Code" DESKTOP_ENTRY="$DESKTOP_DIR/$DESKTOP_ENTRY_NAME" -SCRIPT_NAME="phcode" +SCRIPT_NAME="phcode" # Name of the script to invoke the binary BINARY_NAME="phoenix-code" -APPIMAGE_NAME="phoenix-code.AppImage" +# STARTUP_WM_CLASS is set to the binary name and used as the application's class name in the desktop file. +# This class name helps the desktop environment manage application windows. If native libraries are missing +# and the application needs to be launched via a script instead of directly, this variable is updated +# accordingly before the desktop file is created. +STARTUP_WM_CLASS=$BINARY_NAME + declare -a MIME_TYPES=( "text/html" @@ -63,310 +48,460 @@ declare -a MIME_TYPES=( "text/cjs" ) + +# Define color variables for easy reference GREEN="\e[32m" YELLOW="\e[33m" RED="\e[31m" RESET="\e[0m" +# Cleanup Function +# +# Purpose: +# The cleanup function is designed to ensure that the script cleans up any temporary +# resources it used during its execution. Specifically, it removes the temporary directory +# created at the beginning of the script, along with all its contents. This is crucial for +# maintaining a clean file system and avoiding the accumulation of unused files. +# +# Behavior: +# - The function targets a temporary directory specified by the `$TMP_DIR` variable. +# - It uses `rm -rf` to recursively and forcefully remove this directory and all its contents. +# This includes any temporary files, downloaded assets, or other data generated during the script's execution. +# - The cleanup is registered with a `trap` command on the `EXIT` signal, ensuring it executes +# automatically when the script exits, regardless of the exit point. This includes normal completion, +# errors, or manual termination via signals like SIGINT (Ctrl+C). +# +# Considerations: +# - The function assumes that `$TMP_DIR` is correctly set to the path of the temporary directory. +# If `$TMP_DIR` is unset or incorrectly set, the function may not perform as intended. +# - Using `rm -rf` carries the risk of deleting significant data if misused. It's crucial that `$TMP_DIR` +# is always a temporary directory intended for deletion and does not overlap with any critical system or user directories. +# - The function does not provide a confirmation prompt before deletion, so it's assumed that any data within +# `$TMP_DIR` is expendable and safe to delete. +# +# Usage: +# This function is not intended to be called directly by the user. It's automatically triggered by the `trap` +# command set up at the beginning of the script. However, if manual cleanup is necessary, ensure that `$TMP_DIR` +# is correctly set to the temporary directory path before invoking the function. +# cleanup() { - rm -rf "$TMP_DIR" -} -# Surface user interrupts (Ctrl+C / kill) with a clear message and exit 130, -# while still running cleanup. Without this, dpkg unpacks and sudo prompts can -# make the script look unresponsive to SIGINT for a beat. -on_interrupt() { - echo - echo -e "${RED}Interrupted by user. Cleaning up...${RESET}" - cleanup - exit 130 -} -trap cleanup EXIT -trap on_interrupt INT TERM - -TMP_DIR=$(mktemp -d) - -configure_wget_options() { - local wget_version - wget_version=$(wget --version | head -n1 | awk '{print $3}') - local major_version - major_version=$(echo "$wget_version" | cut -d. -f1) - - if [[ "$major_version" -ge 2 ]]; then - echo "-c --tries=10 --timeout=30 --waitretry=5 --progress=bar --retry-connrefused -O" - else - echo "-c -N --tries=10 --timeout=30 --waitretry=5 --retry-connrefused --show-progress -qO" - fi + rm -rf "$TMP_DIR" # Command to remove the temporary directory and its contents. } -check_architecture() { - local arch - arch=$(uname -m) - if [ "$arch" != "x86_64" ]; then - echo -e "${RED}This installer only supports 64-bit x86 (x86_64). Detected: $arch.${RESET}" - exit 1 - fi -} +trap cleanup EXIT # Register the cleanup function to be called on script exit. -# Runtime-library probe: ask the AppImage to print its version and exit. This -# is handled at src-electron/main.js:23-26, but `require('electron')` runs -# before that handler — which means Chromium initializes its sandbox bits even -# for --version. On Ubuntu 24.04+ / Debian 13+ where unprivileged user -# namespaces are restricted, that init can SIGTRAP. Pass --no-sandbox so the -# probe isolates to "are the system shared libraries present and loadable?" -# regardless of the kernel's userns policy. -# -# The `{ … } 2>/dev/null` wrapper suppresses bash's own "Trace/breakpoint trap -# (core dumped)" report when the subprocess dies on signal — those messages -# look alarming but are harmless: we already detect failure via the exit code. -verify_appimage_launches() { - { "$1" --no-sandbox --version >/dev/null 2>&1; } 2>/dev/null -} -# Returns 0 if the gnome-keyring secret-service daemon binary is on PATH. -# `--version` can't test this because keytar is only require()d at first use, -# so we need an independent presence check. -gnome_keyring_present() { - command -v gnome-keyring-daemon >/dev/null 2>&1 -} +TMP_DIR=$(mktemp -d) -downloadLatestReleaseInfo() { - local release_info_file="$TMP_DIR/latest_release.json" - if [ ! -f "$release_info_file" ]; then - >&2 echo -e "${GREEN}Fetching the latest release information...${RESET}" - wget -qO "$release_info_file" "$UPDATE_JSON_URL" || { - >&2 echo -e "${RED}Failed to fetch release info from $UPDATE_JSON_URL. Check your internet connection.${RESET}" - exit 1 - } - fi - grep -Po '"version"\s*:\s*"\K[^"]+' "$release_info_file" | head -n1 -} +# Check OS Version Function +# +# Purpose: +# This function checks if the current operating system is Ubuntu 24.04, Linux Mint 22, or Kali Linux 2024.2 or newer. +# It reads the /etc/os-release file, which contains identification data for the operating system. +# +# Behavior: +# 1. The function first checks if the /etc/os-release file exists. +# 2. If the file exists, it sources the file to load the OS identification variables. +# 3. It then checks if: +# - The ID variable is set to "ubuntu" and the VERSION_ID variable is "24.04", +# - The ID variable is "linuxmint" and the VERSION_ID is "22", +# - The ID variable is "kali" and the VERSION_ID is "2024.2" or newer. +# 4. If any of these conditions are met, the function returns 0 (indicating success). +# 5. If none of these conditions are met, the function returns 1 (indicating failure). +# +# Usage: +# This function can be called to determine if special handling is needed for these specific versions of +# Ubuntu, Linux Mint, or Kali Linux, such as installing additional libraries or making compatibility adjustments. +# +# Example: +# if check_os_version; then +# echo "Supported OS version detected." +# else +# echo "Unsupported OS version." +# fi +# +# Notes: +# - This function assumes that the /etc/os-release file follows the standard format for Linux OS +# identification and that the distribution's identifiers are consistent with those specified in the script. -# Parses the linux-x86_64 download URL out of the update JSON without needing jq. -getLinuxAppImageUrl() { - awk ' - /"linux-x86_64"[[:space:]]*:/ { in_linux=1 } - in_linux && /"url"[[:space:]]*:/ { - if (match($0, /"url"[[:space:]]*:[[:space:]]*"[^"]+"/)) { - s = substr($0, RSTART, RLENGTH) - sub(/^"url"[[:space:]]*:[[:space:]]*"/, "", s) - sub(/"$/, "", s) - print s - exit - } - } - ' "$TMP_DIR/latest_release.json" +check_os_version() { + if [ -f /etc/os-release ]; then + . /etc/os-release + # Set VERSION_ID to empty string if not defined (for rolling releases) + VERSION_ID="${VERSION_ID:-}" + # Define supported OS and version combinations + case "$ID:${VERSION_ID%%.*}" in # Extract major version number + ubuntu:24) # Ubuntu 24.x + return 0 + ;; + ubuntu:25) # Ubuntu 25.x + return 0 + ;; + linuxmint:22) # Linux Mint 22.x + return 0 + ;; + neon:24) # KDE Neon 24.x + return 0 + ;; + debian:13) # Debian 13.x (Trixie) + return 0 + ;; + kali:2024|kali:20[2-9][0-9]) # Kali 2024.x or newer + return 0 + ;; + esac + fi + return 1 } -# Generates the `phcode` wrapper script. The wrapper runs the AppImage directly, -# falling back to --appimage-extract-and-run on FUSE failure or to --no-sandbox -# on userns/AppArmor failure (Ubuntu 24.04+ / Debian 13+ blocks Chromium's -# unprivileged user namespace sandbox unless our AppArmor profile is loaded). +# Create Invocation Script Function +# +# Purpose: +# Generates a Bash script that serves as a wrapper for invoking the installed binary of Phoenix Code. +# This script is intended to simplify the execution of Phoenix Code by providing a more accessible +# command interface. The generated script is placed in a user-accessible binary directory, making it +# possible to run Phoenix Code from anywhere in the system without needing to specify the full path +# to the binary. +# +# Parameters: +# - binary_path: The absolute path to the directory where the Phoenix Code binary is located. +# - script_name: The name to be given to the invocation script. This name is used both for the +# script file itself and for the symlink created in the user's binary directory. +# - link_dir: The directory where a symlink to the invocation script will be placed, typically +# a location in the user's PATH, such as ~/.local/bin, for easy access. +# +# Behavior: +# 1. Creates a new Bash script in the specified 'binary_path' with the 'script_name'. +# 2. Writes a shebang line, a warning comment indicating the script is auto-generated, and a +# command to execute the Phoenix Code binary with any passed arguments ("$@"). +# 3. Sets executable permissions on the newly created script. +# 4. Copies the script to the specified 'link_dir', creating a symlink in a user-accessible location. +# +# Considerations: +# - It's crucial that 'binary_path' and 'link_dir' are valid directories and that 'script_name' +# does not conflict with existing files in these directories to prevent unintended overwrites. +# - The function does not check for the existence of 'binary_path' or 'link_dir'; it assumes +# these directories are already created and accessible. +# - The generated script includes a warning comment advising against deletion, as it is +# essential for the proper functioning of Phoenix Code when invoked through this script. +# +# Usage: +# This function is intended to be called during the installation or upgrade process of Phoenix Code, +# after the binary has been placed in its final location. It should not be called arbitrarily, as it +# assumes a specific setup and context established by the installer script. +# create_invocation_script() { - local install_dir="$1" + local binary_path="$1" local script_name="$2" local link_dir="$3" - echo "Creating an invocation script for the AppImage..." - cat > "$install_dir/$script_name" <"\$ERR"; then - if grep -qiE 'fuse|libfuse|fusermount|/dev/fuse' "\$ERR"; then - rm -f "\$ERR" - exec "\$APPIMAGE" --appimage-extract-and-run "\$@" - fi - if grep -qiE 'userns|apparmor|sandbox|setuid|namespace|Check failed' "\$ERR"; then - rm -f "\$ERR" - exec "\$APPIMAGE" --no-sandbox "\$@" - fi - cat "\$ERR" >&2 - rm -f "\$ERR" - exit 1 -fi -rm -f "\$ERR" -EOF - chmod +x "$install_dir/$script_name" + echo "Creating an invocation script for the binary..." + # Start of the generated script + echo "#!/bin/bash" > "$binary_path/$script_name.sh" + # Add a warning comment about the script being auto-generated + echo "# DO NOT DELETE: This script is generated by the Phoenix Code installer." >> "$binary_path/$script_name" + echo "$binary_path/$BINARY_NAME \"\$@\"" >> "$binary_path/$script_name" + chmod +x "$binary_path/$script_name" + + echo "Copying the invocation script to $link_dir..." + mkdir -p "$link_dir" # Ensure the directory exists + cp "$binary_path/$script_name" "$link_dir/$script_name" - mkdir -p "$link_dir" - cp "$install_dir/$script_name" "$link_dir/$script_name" echo -e "Invocation script created at: $link_dir/$script_name" } +# Install Dependencies Function +# +# Purpose: +# Installs the necessary dependencies for Phoenix Code to run properly on the user's system. +# This function identifies the user's Linux distribution and installs the appropriate packages +# using the distribution's package manager. It supports Ubuntu/Debian, Fedora/RHEL/CentOS, and +# Arch Linux distributions. +# +# Behavior: +# 1. Checks for administrative privileges by attempting a non-interactive `sudo` command. +# If the command fails, it prompts the user to enter their password for `sudo` access. +# 2. Determines the Linux distribution by examining the contents of `/etc/os-release`. +# 3. Based on the identified distribution, executes the appropriate package manager command +# with the required packages. Supported package managers are `apt` for Debian-based systems, +# `dnf` for Fedora/RHEL/CentOS, and `pacman` for Arch Linux. +# 4. Updates the package index (if applicable) and installs the packages. +# +# Considerations: +# - The function requires internet access to fetch package information and install packages. +# - It assumes that the user has `sudo` privileges to install packages system-wide. +# - The list of dependencies is hardcoded within the function. If Phoenix Code's dependencies change, +# this function must be updated accordingly. +# - Unsupported distributions will result in an error message and termination of the script, +# as the required dependencies cannot be installed automatically. +# - This function does not handle potential errors from package installation commands. It's +# recommended to monitor the output for any issues that may require manual intervention. +# +# Usage: +# This function is intended to be called during the Phoenix Code installation process, typically +# before attempting to run or configure the Phoenix Code binary. It ensures that all prerequisites +# are met for a successful Phoenix Code execution. +# +install_dependencies() { + echo "Attempting to install required dependencies..." -# Returns "gnome-keyring" unless the current session is KDE. -# On KDE, kwallet implements the Secret Service interface (KF 5.97+ / ksecretd -# in 2026), and installing gnome-keyring causes a D-Bus activation race for -# org.freedesktop.secrets — see VSCode issue #189672. -gnome_keyring_pkg_if_needed() { - if [[ "${XDG_CURRENT_DESKTOP:-}" =~ KDE ]]; then - echo "" - else - echo "gnome-keyring" + # Inform the user that the next steps require administrative access + echo "The installation of dependencies requires administrative access. You may be prompted to enter your password." + + # Check if the script can execute sudo commands without interaction + if ! sudo -n true 2>/dev/null; then + echo "Please enter your password to proceed with the installation of dependencies." fi -} -# Picks the libfuse2 package name for whatever Ubuntu/Debian release we're on. -# Ubuntu 24.04 Noble+ / Debian 13 Trixie+ / Kali rolling / Mint 22+ / Neon 24+ -# ship libfuse2t64 (the post-y2038 rename); older releases (Ubuntu 20.04 Focal -# / 22.04 Jammy, Debian 11/12, Mint 20/21, Neon 22) ship the pre-rename -# libfuse2. Both provide the same libfuse.so.2 SONAME, so picking by repo -# availability is correct. -choose_fuse_package_apt() { - if apt-cache show libfuse2t64 >/dev/null 2>&1; then - echo libfuse2t64 + # Attempt to identify the Linux distribution + if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO=$ID else - echo libfuse2 + echo "Unable to identify the operating system." + exit 1 fi -} -# Per-distro package install. Only called when verify_appimage_launches failed -# OR the keyring daemon is missing — i.e., we've already decided we need sudo. -install_packages_for_distro() { - local distro="$1" keyring="$2" - # RHEL 10 does not ship libXScrnSaver in any default channel — left off the - # dnf list intentionally; Electron's XScreenSaver calls no-op on Wayland. - # libxss + libnotify are listed for Arch because gtk3 does NOT pull them in - # transitively but Electron dlopen's them at runtime. - local -a pkgs=() - case "$distro" in - ubuntu|debian|linuxmint|kali|neon) pkgs=("$(choose_fuse_package_apt)" libsecret-1-0) ;; - fedora|rhel|centos) pkgs=(fuse-libs fuse3-libs libsecret) ;; - arch|manjaro|cachyos) pkgs=(fuse2 libsecret libxss libnotify) ;; - esac - [ -n "$keyring" ] && pkgs+=("$keyring") - - case "$distro" in - ubuntu|debian|linuxmint|kali|neon) - # No `yes |` pipeline: under `set -o pipefail`, `yes` dies of SIGPIPE - # after apt exits and the pipeline returns 141, silently aborting the - # installer. Use apt-get -y, and keep user /etc config files via the - # force-conf* options so an unrelated pending upgrade can't prompt. - echo "Detected an Ubuntu/Debian-based distribution." - sudo apt-get update - # shellcheck disable=SC2068 - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ - -o Dpkg::Options::="--force-confdef" \ - -o Dpkg::Options::="--force-confold" \ - ${pkgs[@]} + # Install dependencies based on the distribution + case "$DISTRO" in + ubuntu|debian|linuxmint|kali) + echo "Detected an Ubuntu/Debian based distribution." + sudo apt update + yes | sudo apt install libgtk-3-0 libwebkit2gtk-4.0-37 \ + gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ + gstreamer1.0-tools ;; fedora|rhel|centos) - echo "Detected a Fedora/Red Hat-based distribution." - # shellcheck disable=SC2068 - sudo dnf install -y ${pkgs[@]} + echo "Detected a Fedora/Red Hat based distribution." + yes | sudo dnf install webkit2gtk3 gtk3 \ + gstreamer1-plugins-base gstreamer1-plugins-good + yes | sudo dnf upgrade webkit2gtk3 gtk3 \ + gstreamer1 gstreamer1-plugins-base gstreamer1-plugins-good ;; arch|manjaro|cachyos) - echo "Detected an Arch-based distribution." + echo "Detected an Arch Linux based distribution." + echo "Updating package database..." sudo pacman -Sy - # shellcheck disable=SC2068 - sudo pacman -S --noconfirm --needed ${pkgs[@]} + yes | sudo pacman -S webkit2gtk gtk3 libxml2 + ;; + *) + echo "Unsupported distribution. Please manually install the required dependencies." + exit 1 ;; esac } +download_and_install_gtk() { + # Function: create_launch_script_with_gtk + # + # Purpose: + # This function creates a launch script for the Phoenix Code application that sets the required + # GTK library paths in the LD_LIBRARY_PATH environment variable. The launch script ensures that + # the Phoenix Code application uses the correct GTK libraries, which are included in the + # application's installation directory. + # + # Parameters: + # - binary_path: The path to the directory containing the Phoenix Code binary. + # - binary_name: The name of the Phoenix Code binary file. + # + # Behavior: + # 1. The function renames the original Phoenix Code binary to append ".real" to its name. + # 2. It then creates a new launch script with the original binary name in the same directory. + # 3. The launch script sets the LD_LIBRARY_PATH environment variable to include the path to the + # GTK libraries located in the same directory as the binary. + # 4. The launch script executes the original Phoenix Code binary (now renamed) with any + # arguments passed to the script. + # 5. The function sets the appropriate executable permissions on the launch script. + # + # Usage: + # This function should be called after the Phoenix Code binary and its required GTK libraries + # have been installed. It ensures that the application uses the correct libraries when launched. + # + # Example: + # create_launch_script_with_gtk "/path/to/phoenix-code" "phoenix-code" + # + # Notes: + # - This function assumes that the GTK libraries are located in a subdirectory named "gtk" within + # the same directory as the Phoenix Code binary. + # - The function requires the mv, chmod, and cat commands to be available in the environment. + # - This piece of code should only be executed if the package manager does not distribute + # libgtk or if the version provided by the package manager is not compatible with Phoenix Code. -# Decides what (if anything) the user's system is missing for Phoenix Code to -# run, then installs only the missing piece(s). Three independent probes: -# -# 1. System shared libraries — probed by running the downloaded AppImage -# with `--version`. main.js exits immediately on this flag, AFTER libfuse -# mounts the AppImage and AFTER the Electron binary loads its NEEDED libs. -# 2. gnome-keyring daemon — binary-presence check; `--version` doesn't -# exercise keytar so it can't verify libsecret/Secret Service end-to-end. -# -# Sudo is requested ONLY if at least one of the probes fails. -ensure_runtime_dependencies() { - local appimage="$1" - if [ ! -f /etc/os-release ]; then - echo -e "${RED}Unable to identify the operating system (no /etc/os-release).${RESET}" + local URL_PREFIX="https://github.com/phcode-dev/dependencies/releases/download/v1.0.5/" + local GTK_FILE="gtk.tar.xz" + local WEBKIT2GTK_FILE="webkit2gtk-4.0.tar.xz" + + local GTK_URL="${URL_PREFIX}${GTK_FILE}" + local WEBKIT2GTK_URL="${URL_PREFIX}${WEBKIT2GTK_FILE}" + + echo -e "${YELLOW}Downloading GTK from $GTK_URL...${RESET}" + local destination="$TMP_DIR/$BINARY_NAME" + WGET_OPTS=$(configure_wget_options) + + wget $WGET_OPTS "$TMP_DIR/gtk.tar.xz" "$GTK_URL" || { + echo -e "${RED}Failed to download GTK. Please check your internet connection and try again.${RESET}" exit 1 - fi - . /etc/os-release - local distro="${ID:-}" - case "$distro" in - ubuntu|debian|linuxmint|kali|neon|fedora|rhel|centos|arch|manjaro|cachyos) ;; - *) - echo -e "${RED}Unsupported distribution: $distro. Please install libfuse2/fuse2, libsecret, and (on non-KDE) gnome-keyring manually.${RESET}" + } + echo "Extracting GTK..." + tar -xJf "$TMP_DIR/$GTK_FILE" -C "$destination" || { + echo -e "${RED}Failed to extract GTK. The downloaded file might be corrupt.${RESET}" + exit 1 + } + + if [ ! -d "/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0" ]; then + echo -e "${YELLOW}Downloading WebKit2GTK from $WEBKIT2GTK_URL...${RESET}" + wget $WGET_OPTS "$TMP_DIR/$WEBKIT2GTK_FILE" "$WEBKIT2GTK_URL" || { + echo -e "${RED}Failed to download WebKit2GTK. Please check your internet connection and try again.${RESET}" exit 1 - ;; - esac - local keyring; keyring=$(gnome_keyring_pkg_if_needed) + } + echo "Extracting WebKit2GTK..." + tar -xJf "$TMP_DIR/$WEBKIT2GTK_FILE" -C "$TMP_DIR" || { + echo -e "${RED}Failed to extract WebKit2GTK. The downloaded file might be corrupt.${RESET}" + exit 1 + } - local libs_ok=1 keyring_ok=1 - verify_appimage_launches "$appimage" || libs_ok=0 - if [ -n "$keyring" ] && ! gnome_keyring_present; then keyring_ok=0; fi + # Inform the user that the next steps require administrative access + echo "The installation of dependencies requires administrative access. You may be prompted to enter your password." - if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ]; then - echo -e "${GREEN}All runtime dependencies already present.${RESET}" - return 0 - fi + # Check if the script can execute sudo commands without interaction + if ! sudo -n true 2>/dev/null; then + echo "Please enter your password to proceed with the installation of dependencies." + fi - if [ "$libs_ok" = 0 ]; then - echo "Installing missing runtime libraries..." + echo "Installing WebKit2GTK..." + sudo cp -r "$TMP_DIR/webkit2gtk-4.0" /usr/lib/x86_64-linux-gnu/ || { + echo -e "${RED}Failed to install WebKit2GTK. Please check the permissions and try again.${RESET}" + exit 1 + } + echo -e "${GREEN}WebKit2GTK installed successfully.${RESET}" else - echo "Installing system keychain daemon..." - fi - echo "This step requires administrative access." - if ! sudo -n true 2>/dev/null; then - echo "Please enter your password to proceed." - fi - - install_packages_for_distro "$distro" "$keyring" - # Re-probe libraries; if still failing, surface a clear warning but don't - # abort — the AppImage may have a runtime-specific issue we can't fix here. - if [ "$libs_ok" = 0 ] && ! verify_appimage_launches "$appimage"; then - echo -e "${YELLOW}WARN: AppImage still fails --version after dep install.${RESET}" + echo -e "${GREEN}WebKit2GTK already installed.${RESET}" fi } -# Returns 0 if any of the common display managers is configured to autologin -# the current user. All probed files are world-readable; no sudo, no prompts. -# Used only to enrich the post-install keyring hint with a probable cause. -autologin_detected() { - local user="${USER:-$(id -un)}" - local f - for f in /etc/gdm3/custom.conf /etc/gdm3/daemon.conf /etc/gdm/custom.conf; do - [ -r "$f" ] && grep -qE '^[[:space:]]*AutomaticLoginEnable[[:space:]]*=[[:space:]]*[Tt]rue' "$f" && return 0 - done - for f in /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.d/*.conf; do - [ -r "$f" ] && grep -qE "^[[:space:]]*autologin-user[[:space:]]*=[[:space:]]*${user}([[:space:]]|$)" "$f" && return 0 - done - for f in /etc/sddm.conf /etc/sddm.conf.d/*.conf; do - [ -r "$f" ] && grep -qE "^[[:space:]]*User[[:space:]]*=[[:space:]]*${user}([[:space:]]|$)" "$f" && return 0 - done - return 1 + +create_launch_script_with_gtk() { + # Function: create_launch_script_with_gtk + # + # Purpose: + # This function creates a launch script for the Phoenix Code application that sets the required + # GTK library paths in the LD_LIBRARY_PATH environment variable. The launch script ensures that + # the Phoenix Code application uses the correct GTK libraries, which are included in the + # application's installation directory. + # + # Parameters: + # - binary_path: The path to the directory containing the Phoenix Code binary. + # - binary_name: The name of the Phoenix Code binary file. + # + # Behavior: + # 1. The function renames the original Phoenix Code binary to append ".real" to its name. + # 2. It then creates a new launch script with the original binary name in the same directory. + # 3. The launch script sets the LD_LIBRARY_PATH environment variable to include the path to the + # GTK libraries located in the same directory as the binary. + # 4. The launch script executes the original Phoenix Code binary (now renamed) with any + # arguments passed to the script. + # 5. The function sets the appropriate executable permissions on the launch script. + # + # Usage: + # This function should be called after the Phoenix Code binary and its required GTK libraries + # have been installed. It ensures that the application uses the correct libraries when launched. + # + # Example: + # create_launch_script_with_gtk "/path/to/phoenix-code" "phoenix-code" + # + # Notes: + # - This function assumes that the GTK libraries are located in a subdirectory named "gtk" within + # the same directory as the Phoenix Code binary. + # - The function requires the mv, chmod, and cat commands to be available in the environment. + # - This piece of code should only be executed if the package manager does not distribute + # libgtk or if the version provided by the package manager is not compatible with Phoenix Code. + + local binary_path="$1" + local binary_name="$BINARY_NAME" + local original_bin=$binary_name.app + local realBin="$binary_path/$original_bin" + mv "$binary_path/$binary_name" "$realBin" + echo "Creating a launch script for Phoenix Code with GTK libraries..." + cat > "$binary_path/$binary_name" </dev/null 2>&1 || return 0 - local locked - locked=$(busctl --user --no-pager call org.freedesktop.secrets \ - /org/freedesktop/secrets/collection/login \ - org.freedesktop.DBus.Properties Get \ - ss org.freedesktop.Secret.Collection Locked 2>/dev/null \ - | awk '/^b /{print $NF}') - [ "$locked" = "true" ] || return 0 +# Verify and Install Dependencies Function +# +# Purpose: +# This function is responsible for verifying if the Phoenix Code application can be successfully +# launched with the current set of installed dependencies. If the initial verification fails, it +# attempts to install the necessary dependencies and then re-verifies the application launch. +# +# Behavior: +# 1. Changes the current directory to the temporary directory where Phoenix Code is located. +# 2. Sets the executable permission for the Phoenix Code binary and attempts to run it with a +# verification-specific command (e.g., `--runVerify`). This step is intended to check if the +# application can start without installing additional dependencies. +# 3. If the initial verification succeeds, the function exits with a success status (0), indicating +# no further action is needed. +# 4. If the initial verification fails, the function proceeds to invoke `install_dependencies` to +# install any missing dependencies specific to the user's Linux distribution. +# 5. After installing dependencies, it attempts to verify the application launch again. If this +# second attempt succeeds, the function exits with a success status (0). +# 6. If the verification fails even after installing dependencies, the function prints an error +# message advising the user to check the application requirements or contact support, and +# exits with an error status (1). +# 7. Finally, the function ensures to change back to the original directory where the script was +# invoked, preserving the user's shell environment. +# +# Considerations: +# - The function assumes that the Phoenix Code binary is located in a temporary directory and +# that this directory is correctly set in the `$TMP_DIR` variable. +# - It relies on the Phoenix Code binary supporting a `--runVerify` command or equivalent for +# verification purposes. If such a command is not available, this part of the function needs +# adjustment. +# - Dependency installation requires administrative privileges; hence, the user might be prompted +# for a password to allow `sudo` operations. +# - The function does not handle all possible error scenarios during dependency installation. +# Users should monitor the output for potential issues that might need manual intervention. +# +# Usage: +# This function is intended to be called during the installation or upgrade process of Phoenix Code, +# before finalizing the installation, to ensure that all system requirements are met for the +# application to run properly. +# +verify_and_install_dependencies() { + if "$TMP_DIR/phoenix-code/phoenix-code" --runVerify; then + echo "Application launch verification successful." + return 0 + else + if check_os_version; then + echo -e "${YELLOW}Ubuntu 24.04 detected. Attempting to download and install GTK and WebKit2GTK...${RESET}" + download_and_install_gtk + create_launch_script_with_gtk "$TMP_DIR/phoenix-code" + else + echo "Initial verification failed. Attempting to install dependencies..." + install_dependencies + fi + fi - echo - echo -e "${YELLOW}Note: your system keychain is currently locked.${RESET}" - echo -e "${YELLOW}The first time Phoenix Code stores or reads a credential, gnome-keyring${RESET}" - echo -e "${YELLOW}will ask once for your login password to unlock it. This is a one-time${RESET}" - echo -e "${YELLOW}per-session Linux behavior — not a Phoenix Code prompt.${RESET}" - if autologin_detected; then - echo -e "${YELLOW}(Autologin is enabled on this machine, which is why PAM did not${RESET}" - echo -e "${YELLOW} auto-unlock the keyring at session start.)${RESET}" + if "$TMP_DIR/phoenix-code/phoenix-code" --runVerify; then + echo "Application launch verification successful after installing dependencies." + return 0 + else + echo "Verification failed even after installing dependencies. Please check the application requirements or contact support." + return 1 fi } - +# Set Phoenix Code as the default application for specified MIME types set_default_application() { - local desktop_file="$DESKTOP_ENTRY_NAME" + local desktop_file="$DESKTOP_ENTRY_NAME" # Name of the Phoenix Code desktop entry file + # Dynamically detect qtpaths if KDE is running and qtpaths is not in PATH if [ "${KDE_SESSION_VERSION:-0}" -gt 0 ] && ! command -v qtpaths &> /dev/null; then + # Common locations where qtpaths might be installed local qtpaths_locations=( "/usr/lib/qt6/bin" "/usr/lib/qt5/bin" @@ -375,6 +510,7 @@ set_default_application() { "/opt/qt6/bin" "/opt/qt5/bin" ) + for qtpath_dir in "${qtpaths_locations[@]}"; do if [ -x "$qtpath_dir/qtpaths" ] || [ -x "$qtpath_dir/qtpaths6" ]; then export PATH="$qtpath_dir:$PATH" @@ -384,36 +520,82 @@ set_default_application() { fi for mime_type in "${MIME_TYPES[@]}"; do - if [ "$mime_type" = "text/html" ]; then - continue - fi - xdg-mime default "$desktop_file" "$mime_type" + # Skip setting default application for text/html + if [ "$mime_type" = "text/html" ]; then + continue # Skip to the next iteration + fi + + xdg-mime default "$desktop_file" "$mime_type" done echo -e "${GREEN}Success! You can now right-click on files in your file manager and choose Phoenix Code to edit them.${RESET}" } -copyFilesToDestination() { +# Copy Files to Destination Function +# +# Purpose: +# This function is responsible for setting up the final installation directory for Phoenix Code, +# moving the necessary files into place, creating the invocation script, and setting up the desktop +# entry for the application. It ensures that all components of Phoenix Code are correctly installed +# and configured in the user's environment. +# +# Behavior: +# 1. Creates the installation directory at `$INSTALL_DIR`, where Phoenix Code and its related files +# will reside. +# 2. Similarly, ensures the desktop entry directory `$DESKTOP_DIR` exists for application launchers. +# 3. Moves the downloaded and possibly compiled Phoenix Code binary and other necessary files from +# the temporary directory (`$TMP_DIR`) to the installation directory. +# 4. Downloads and moves the application icon to the installation directory for use in the desktop entry. +# 5. Sets the correct executable permissions for the Phoenix Code binary to ensure it can be run by the user. +# 6. Calls `create_invocation_script` to generate a wrapper script for easier execution of Phoenix Code. +# 7. Constructs a `.desktop` file for Phoenix Code, defining how it should appear in application menus and +# launchers, and places this file in the desktop entry directory. +# 8. Updates the system's desktop database to ensure the new application entry is recognized and available +# in application menus and launchers. This step might involve commands like `update-desktop-database` +# or `kbuildsycoca5`, depending on the desktop environment. +# +# Considerations: +# - The function assumes that the necessary files for Phoenix Code are already present in `$TMP_DIR` and +# that this directory is correctly set. +# - It relies on specific directory paths (`$INSTALL_DIR`, `$DESKTOP_DIR`, `$LINK_DIR`) being set and +# accessible. If these variables are unset or incorrect, the function may not perform as intended. +# - Administrative privileges may be required for certain operations, such as updating the desktop database +# or setting permissions, depending on the system's configuration. +# - Care is taken to avoid overwriting existing files or directories without confirmation, but this function +# does perform significant file system operations that could potentially conflict with existing user data +# or system settings. +# +# Usage: +# This function is intended to be called as part of the Phoenix Code installation process, after the +# application's binary and other necessary files have been prepared in a temporary location. It finalizes +# the installation by placing all components in their appropriate locations within the user's system. +# +copyFilesToDestination(){ echo "Setting up the installation directory at $INSTALL_DIR..." mkdir -p "$INSTALL_DIR" mkdir -p "$DESKTOP_DIR" + echo -e "${YELLOW}Installation directory set up at: $INSTALL_DIR${RESET}" - mv "$TMP_DIR/$APPIMAGE_NAME" "$INSTALL_DIR/$APPIMAGE_NAME" || { - echo -e "${RED}Failed to move the AppImage to the installation directory. Please check the permissions and try again.${RESET}" + echo "Moving the necessary files to the installation directory..." + echo -e "Phoenix Code files moved to: $INSTALL_DIR" + mv "$TMP_DIR"/phoenix-code/* "$INSTALL_DIR/" || { + echo -e "${RED}Failed to move the files to the installation directory. Please check the permissions and try again.${RESET}" exit 1 } + # Move the icon to the installation directory mv "$TMP_DIR/icon.png" "$INSTALL_DIR/" - chmod +x "$INSTALL_DIR/$APPIMAGE_NAME" || { - echo -e "${RED}Failed to set executable permissions on the AppImage.${RESET}" + echo "Setting the correct permissions for the executable..." + chmod +x "$INSTALL_DIR/phoenix-code" || { + echo -e "${RED}Failed to set executable permissions. Please check the file path and permissions.${RESET}" exit 1 } - echo -e "AppImage installed at: $INSTALL_DIR/$APPIMAGE_NAME" + echo -e "Executable permissions set for: $INSTALL_DIR/$BINARY_NAME" - mkdir -p "$LINK_DIR" + mkdir -p "$LINK_DIR" # Ensure the directory exists + # Call the function to create and copy the invocation script create_invocation_script "$INSTALL_DIR" "$SCRIPT_NAME" "$LINK_DIR" - - local mime_types_string - mime_types_string=$(IFS=";"; echo "${MIME_TYPES[*]}") + # Convert MIME types array to semicolon-separated string for the desktop entry + MIME_TYPES_STRING=$(IFS=";"; echo "${MIME_TYPES[*]}") echo "Creating desktop entry..." cat > "$DESKTOP_ENTRY" < /dev/null; then - update-desktop-database "$DESKTOP_DIR" + update-desktop-database "$DESKTOP_DIR" + echo -e "Desktop database updated in: $DESKTOP_DIR" fi - if [ "${XDG_CURRENT_DESKTOP:-}" = "KDE" ]; then + + # Update the KDE desktop database if KDE is in use + if [ "$XDG_CURRENT_DESKTOP" = "KDE" ]; then if command -v kbuildsycoca5 &> /dev/null; then - kbuildsycoca5 + kbuildsycoca5 fi fi - if [[ "${XDG_CURRENT_DESKTOP:-}" =~ LXQt ]]; then + + if [[ "$XDG_CURRENT_DESKTOP" =~ LXQt ]]; then if command -v xdg-desktop-menu &> /dev/null; then - xdg-desktop-menu forceupdate - echo -e "${YELLOW}Please log out and log back in to see Phoenix Code in the panel.${RESET}" + xdg-desktop-menu forceupdate + echo -e "${YELLOW}Please log out and log back in to see Phoenix Code in the panel.${RESET}" else - echo -e "${RED}Failed to update LXQt menu. Please log out and log back in to see Phoenix Code in the panel.${RESET}" + echo -e "${RED}Failed to update LXQt menu. Please log out and log back in to see Phoenix Code in the panel.${RESET}" fi fi - + # Set Phoenix Code as the default application for the MIME types set_default_application echo -e "${GREEN}Installation completed successfully. Phoenix Code is now installed.${RESET}" - print_keyring_hint_if_locked + +} + +downloadLatestReleaseInfo() { + local release_info_file="$TMP_DIR/latest_release.json" + + if [ -f "$release_info_file" ]; then + # Only extract and echo the version number, without any additional messages + grep -Po '"tag_name":\s*"prod-app-v\K[\d.]+(?=")' "$release_info_file" + return + fi + + # Direct informational messages to stderr to avoid them being captured or displayed unexpectedly + >&2 echo -e "${GREEN}Fetching the latest release information from $GITHUB_REPO...${RESET}" + wget -qO "$release_info_file" "$API_URL" || { + >&2 echo -e "${RED}Failed to fetch the latest release information. Please check your internet connection and try again.${RESET}" + exit 1 + } + + # Only extract and echo the version number after successful download + grep -Po '"tag_name":\s*"prod-app-v\K[\d.]+(?=")' "$release_info_file" } -downloadAndInstall() { +# Function to check if the architecture is 64-bit x86 +check_architecture() { + ARCHITECTURE=$(uname -m) + if [ "$ARCHITECTURE" != "x86_64" ]; then + local latestFileUrl + latestFileUrl=$(grep -oP 'https://[^"]*latest\.json' "$TMP_DIR/latest_release.json") + WGET_OPTS=$(configure_wget_options) + wget $WGET_OPTS "$TMP_DIR/latest.json" "$latestFileUrl" || { + echo -e "${RED}Failed to download the latestFile. Please check your internet connection and try again.${RESET}" + } + echo -e "${RED}This script can only be run on 64-bit x86 architecture.${RESET}" + exit 1 + fi +} +# Download Latest Release Information Function +# +# Purpose: +# Retrieves the latest release information for Phoenix Code from its GitHub repository. This function +# is designed to fetch the JSON data of the latest release using GitHub's API, allowing the script to +# determine the most recent version of Phoenix Code and any associated assets like binaries or icons. +# +# Behavior: +# 1. Checks if the release information file (`latest_release.json`) already exists in the temporary +# directory (`$TMP_DIR`). If it does, the function assumes the latest release information has already +# been fetched and proceeds to extract the version number from this file without re-downloading. +# 2. If the release information file does not exist, the function uses `wget` to download the JSON data +# from the GitHub API URL specified by `$API_URL`, which should point to the latest release endpoint +# of the Phoenix Code GitHub repository. +# 3. The JSON data is saved to `latest_release.json` in the temporary directory. +# 4. Extracts the version number from the downloaded JSON data using `grep` with a Perl-compatible regular +# expression (PCRE) that looks for the `tag_name` field. This field is expected to contain the version +# number prefixed by `prod-app-v` (e.g., `prod-app-v1.2.3`), and the function extracts the version number +# portion. +# 5. Echoes the extracted version number for use by the calling context. +# +# Considerations: +# - This function relies on external utilities `wget` and `grep` with PCRE support, which must be available +# in the execution environment. +# - The GitHub API URL (`$API_URL`) must be correctly set to the latest release endpoint of the Phoenix Code +# repository for this function to work as intended. +# - The function assumes a specific version naming convention (`prod-app-vX.Y.Z`). If this convention changes, +# the `grep` pattern used to extract the version number may need to be updated. +# - Network connectivity is required to fetch the release information from GitHub. If the script is run in an +# environment without internet access, or if GitHub is unreachable, this function will fail to retrieve the +# latest release information. +# - The function does not perform error handling for the `wget` command. If the download fails, the script may +# exit or behave unexpectedly depending on the `set -e` setting. +# +# Usage: +# This function is intended to be called during the installation or upgrade process to determine the latest +# version of Phoenix Code. The extracted version number can be used to compare with the currently installed +# version and decide whether an upgrade is necessary. +# +downloadAndInstall(){ echo "Using temporary directory $TMP_DIR for processing" downloadLatestReleaseInfo > /dev/null check_architecture + CURRENT_GLIBC_VERSION=$(ldd --version | grep "ldd" | awk '{print $NF}') + echo "Current GLIBC version: $CURRENT_GLIBC_VERSION" + + BEST_MATCH_URL="" + BEST_MATCH_VERSION=0 + echo "Searching for a compatible binary..." + + while read -r BINARY_URL; do + BINARY_GLIBC_VERSION=$(echo "$BINARY_URL" | grep -oP 'GLIBC-\K[\d\.]+(?=\.tar\.gz)') + if awk -v bin_ver="$BINARY_GLIBC_VERSION" -v cur_ver="$CURRENT_GLIBC_VERSION" -v best_ver="$BEST_MATCH_VERSION" 'BEGIN { bin_ver += 0; cur_ver += 0; best_ver += 0; exit !(bin_ver <= cur_ver && bin_ver > best_ver) }'; then + BEST_MATCH_URL="$BINARY_URL" + BEST_MATCH_VERSION="$BINARY_GLIBC_VERSION" + echo "Found a new best match: $BEST_MATCH_URL with GLIBC version $BEST_MATCH_VERSION" + fi + done < <(grep -oP '"browser_download_url"\s*:\s*"\K[^"]*_linux_bin-GLIBC-[\d\.]+\.tar\.gz(?=")' "$TMP_DIR/latest_release.json") - local appimage_url - appimage_url=$(getLinuxAppImageUrl) - if [ -z "$appimage_url" ]; then - echo -e "${RED}Could not find a linux-x86_64 AppImage URL in the release info.${RESET}" + if [ -z "$BEST_MATCH_URL" ]; then + echo -e "${RED}No compatible binary found for the current GLIBC version ($CURRENT_GLIBC_VERSION). Exiting installation.${RESET}" exit 1 fi - echo -e "${YELLOW}Downloading the AppImage from $appimage_url...${RESET}" - local wget_opts - wget_opts=$(configure_wget_options) - # shellcheck disable=SC2086 - wget $wget_opts "$TMP_DIR/$APPIMAGE_NAME" "$appimage_url" || { - echo -e "${RED}Failed to download the AppImage. Please check your internet connection and try again.${RESET}" + echo -e "${YELLOW}Downloading the compatible binary from $BEST_MATCH_URL...${RESET}" + # Set options based on wget version + WGET_OPTS=$(configure_wget_options) + + wget $WGET_OPTS "$TMP_DIR/phoenix-code.tar.gz" "$BEST_MATCH_URL" || { + echo -e "${RED}Failed to download the binary. Please check your internet connection and try again.${RESET}" exit 1 } - echo "Downloading the icon..." - # shellcheck disable=SC2086 - wget $wget_opts "$TMP_DIR/icon.png" "$ICON_URL" || { - echo -e "${RED}Failed to download the icon.${RESET}" + # Download the icon + echo -e "Downloading the icon..." + wget $WGET_OPTS "$TMP_DIR/icon.png" "$ICON_URL" || { + echo -e "${RED}Failed to download the icon${RESET}" + exit 1 + } + echo "Extracting the binary to $TMP_DIR..." + tar -xzf "$TMP_DIR/phoenix-code.tar.gz" -C "$TMP_DIR" || { + echo -e "${RED}Failed to extract the binary. The downloaded file might be corrupt.${RESET}" exit 1 } - chmod +x "$TMP_DIR/$APPIMAGE_NAME" - - ensure_runtime_dependencies "$TMP_DIR/$APPIMAGE_NAME" + # Verify binary execution and install dependencies if necessary + if ! verify_and_install_dependencies; then + echo -e "${RED}Unable to successfully verify application launch. Exiting installation.${RESET}" + exit 1 + fi } +# Function to check wget version and configure options +configure_wget_options() { + local wget_version=$(wget --version | head -n1 | awk '{print $3}') + local major_version=$(echo "$wget_version" | cut -d. -f1) -# Temporary cleanup for files from older installer versions. -uninstallBetaAppImage() { - rm -f "$LINK_DIR"/phoenix_icon.png + if [[ "$major_version" -ge 2 ]]; then + echo "-c --tries=10 --timeout=30 --waitretry=5 --progress=bar --retry-connrefused -O" + else + echo "-c -N --tries=10 --timeout=30 --waitretry=5 --retry-connrefused --show-progress -qO" + fi } - +# Install Function +# +# Purpose: +# Facilitates the entire installation process of Phoenix Code on the user's system. This includes +# checking for previous installations, downloading and installing the latest version, setting up +# necessary directories, and ensuring that all dependencies are met. It also handles user prompts +# for repair or reinstallation options if Phoenix Code is already installed. +# +# Behavior: +# 1. Checks if Phoenix Code is already installed by looking for specific files or directories +# associated with the installation (e.g., the invocation script in `$LINK_DIR` or the main +# directory in `$INSTALL_DIR`). If found, prompts the user with an option to repair (reinstall). +# 2. If the user opts for repair, the function proceeds to uninstall the existing installation and +# then reinstalls the application, ensuring the user ends up with the latest version. +# 3. For a fresh installation or repair, the function calls `downloadAndInstall` to fetch the latest +# release and install it. This step involves downloading the binary, setting permissions, and +# moving files to their correct locations. +# 4. After installing the binary, `copyFilesToDestination` is invoked to set up the desktop entry, +# create an invocation script, and perform other necessary post-installation configurations. +# +# Considerations: +# - The function assumes that the user has sufficient permissions to write to the target directories +# and install Phoenix Code. Administrative privileges may be required for certain operations. +# - If the repair option is chosen, all existing Phoenix Code files and configurations in the installation +# directory will be replaced. Users should ensure that any custom configurations or data are backed up +# before proceeding. +# - Network connectivity is required to download the latest version of Phoenix Code from the repository. +# - The function does not explicitly handle all potential error scenarios, such as download failures or +# permission issues. It relies on the `set -e` option to halt execution on any unhandled errors. +# +# Usage: +# This function is intended to be invoked when the script is run without specific options (e.g., not an +# uninstall or upgrade). It can be directly called from the command line when the user wishes to install +# Phoenix Code, or it may be triggered by default when the script is executed without arguments. +# install() { + # Check if the application is already installed if [ -f "$LINK_DIR/$SCRIPT_NAME" ] || [ -d "$INSTALL_DIR" ]; then echo -e "${YELLOW}Phoenix Code appears to be already installed.${RESET}" + # Checking if the shell has a controlling terminal if its non interactive reinstall phoenix with latest version if [ ! -t 0 ]; then echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" uninstall downloadAndInstall copyFilesToDestination else + # Simplified prompt for reinstall without detailed explanation read -r -p "Would you like to reinstall it? (y/N): " response case "$response" in [Yy]* ) - echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" - uninstall - downloadAndInstall - copyFilesToDestination - ;; + echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" + uninstall + downloadAndInstall + copyFilesToDestination + ;; * ) - echo -e "${RED}Reinstall aborted by the user.${RESET}" - exit 0 - ;; + echo -e "${RED}Reinstall aborted by the user.${RESET}" + exit 0 + ;; esac fi else @@ -522,23 +844,65 @@ install() { fi } +# Temporary code to clean up earlier beta installations +function uninstallBetaAppImage() { + rm -f "$LINK_DIR"/phoenix_icon.png +} +# Upgrade Function +# +# Purpose: +# Manages the upgrade process for Phoenix Code, ensuring that the user's installation is updated +# to the latest version available. This function checks the currently installed version against +# the latest version available in the GitHub repository. If a newer version is found, it proceeds +# to download and install the update, effectively replacing the old version with the new one. +# +# Behavior: +# 1. Verifies that Phoenix Code is already installed by checking for the existence of the installation +# directory and the invocation script. If not found, it exits with an error message indicating that +# Phoenix Code must be installed before it can be upgraded. +# 2. Determines the currently installed version of Phoenix Code by invoking the binary with a version +# check command (e.g., `phoenix-code --version`) and captures the output. +# 3. Fetches the latest release information from the GitHub repository using the `downloadLatestReleaseInfo` +# function, which retrieves the version number of the latest release. +# 4. Compares the currently installed version with the latest version. If the installed version is older, +# it initiates the upgrade process by calling `downloadAndInstall`, which handles the download and +# installation of the new version. +# 5. If the currently installed version is already up to date, it informs the user that no upgrade is necessary. +# +# Considerations: +# - The function relies on the correct implementation of version checking in the Phoenix Code binary. +# The binary must support a command-line option to output its version, and the output format must be +# consistent for correct parsing and comparison. +# - Network connectivity is required to fetch the latest release information from the GitHub repository. +# The upgrade process will fail if the script cannot connect to GitHub. +# - The user must have sufficient permissions to overwrite the existing installation files and to +# execute network requests (e.g., downloading the latest release). +# - The upgrade process replaces the existing installation files. Users should ensure that any +# necessary backups or custom configurations are saved before proceeding with the upgrade. +# +# Usage: +# This function is intended to be called when the user explicitly wants to upgrade their installation +# of Phoenix Code to the latest version. It can be triggered by passing an `--upgrade` option to the +# script, as defined in the script's main case statement handling command-line arguments. +# upgrade() { echo -e "${YELLOW}Checking for upgrades to Phoenix Code...${RESET}" + # Ensure Phoenix Code is installed if [ ! -f "$LINK_DIR/$SCRIPT_NAME" ] && [ ! -d "$INSTALL_DIR" ]; then echo -e "${RED}Phoenix Code is not installed. Please install it first.${RESET}" exit 1 fi + # Get the current installed version + CURRENT_VERSION=$("$INSTALL_DIR/phoenix-code" --version) + echo "Current installed version: $CURRENT_VERSION" - local current_version - current_version=$("$INSTALL_DIR/$APPIMAGE_NAME" --version 2>/dev/null || true) - echo "Current installed version: ${current_version:-unknown}" + LATEST_VERSION=$(downloadLatestReleaseInfo) + # Now LATEST_VERSION should only contain the version number without extra messages + echo "Latest available version: $LATEST_VERSION" - local latest_version - latest_version=$(downloadLatestReleaseInfo) - echo "Latest available version: $latest_version" - - if [ -n "$latest_version" ] && [ "$(printf '%s\n' "$latest_version" "$current_version" | sort -V | tail -n1)" = "$latest_version" ] && [ "$latest_version" != "$current_version" ]; then + # Compare versions and upgrade if the latest version is greater + if [ "$(printf '%s\n' "$LATEST_VERSION" "$CURRENT_VERSION" | sort -V | tail -n1)" = "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "$CURRENT_VERSION" ]; then echo -e "${YELLOW}A newer version of Phoenix Code is available. Proceeding with the upgrade...${RESET}" downloadAndInstall uninstall @@ -547,12 +911,48 @@ upgrade() { else echo "Your Phoenix Code installation is up-to-date." fi -} +} +# Uninstall Function +# +# Purpose: +# Handles the removal of Phoenix Code from the user's system. This function is responsible for +# cleaning up all files, directories, and links created during the installation process, effectively +# reverting any changes made by the installer script. It ensures that Phoenix Code and its components +# are uninstalled cleanly, leaving no residual files or configurations. +# +# Behavior: +# 1. Checks for and removes the invocation script located in the user's binary directory (`$LINK_DIR`). +# This step prevents the Phoenix Code command from being accessible after uninstallation. +# 2. Deletes the desktop entry file (`$DESKTOP_ENTRY`) to remove Phoenix Code from application menus +# and launchers. This step is crucial for desktop environments to recognize that the application has been uninstalled. +# 3. Updates the desktop database, if necessary, to reflect the removal of the Phoenix Code desktop entry. +# This may involve commands like `update-desktop-database` or `kbuildsycoca5`, depending on the desktop environment. +# 4. Removes the Phoenix Code installation directory (`$INSTALL_DIR`) and all its contents, including +# the main binary, configuration files, and any other related files placed during installation. +# 5. Prints a confirmation message indicating that the uninstallation process has been completed successfully. +# +# Considerations: +# - The function assumes that the paths stored in `$LINK_DIR`, `$DESKTOP_ENTRY`, and `$INSTALL_DIR` accurately +# reflect the locations used during the installation. If these variables are incorrect, the uninstallation +# process may not remove all components. +# - Administrative privileges may be required for some operations, especially if Phoenix Code was installed +# in system-wide directories. +# - Users should be advised to back up any important data or configurations related to Phoenix Code before +# initiating the uninstallation process, as this function will remove all associated files without recovery options. +# - The function does not provide a rollback mechanism. Once the uninstallation is initiated, the process cannot +# be reversed through this script. +# +# Usage: +# This function is intended to be invoked when the user wishes to completely remove Phoenix Code from their system. +# It can be triggered by passing an `--uninstall` option to the script, as defined in the script's main case statement +# handling command-line arguments. Users should be prompted to confirm their intention to uninstall before this +# function is executed to prevent accidental data loss. +# uninstall() { echo -e "${YELLOW}Starting uninstallation of Phoenix Code...${RESET}" uninstallBetaAppImage - + # Remove the invocation script from ~/.local/bin if [ -f "$LINK_DIR/$SCRIPT_NAME" ]; then echo -e "${YELLOW}Removing invocation script from $LINK_DIR...${RESET}" rm "$LINK_DIR/$SCRIPT_NAME" @@ -560,33 +960,40 @@ uninstall() { echo -e "${RED}Invocation script not found in $LINK_DIR. Skipping...${RESET}" fi + # Delete the desktop entry if [ -f "$DESKTOP_ENTRY" ]; then echo -e "${YELLOW}Removing desktop entry...${RESET}" rm "$DESKTOP_ENTRY" + + # Update the desktop database for GNOME, Unity, XFCE, etc. + echo -e "${YELLOW}Updating desktop database...${RESET}" if command -v update-desktop-database &> /dev/null; then - update-desktop-database "$DESKTOP_DIR" + update-desktop-database "$DESKTOP_DIR" fi - if [ "${XDG_CURRENT_DESKTOP:-}" = "KDE" ]; then - if command -v kbuildsycoca5 &> /dev/null; then - kbuildsycoca5 - fi + + # Update the KDE desktop database if KDE is in use + if [ "$XDG_CURRENT_DESKTOP" = "KDE" ]; then + if command -v kbuildsycoca5 &> /dev/null; then + kbuildsycoca5 + fi fi - if [[ "${XDG_CURRENT_DESKTOP:-}" =~ LXQt ]]; then + + if [[ "$XDG_CURRENT_DESKTOP" =~ LXQt ]]; then if command -v xdg-desktop-menu &> /dev/null; then - xdg-desktop-menu forceupdate + xdg-desktop-menu forceupdate fi fi else - echo -e "${RED}Desktop entry not found. Skipping...${RESET}" + echo -e "${RED}Desktop entry not found. Skipping...${RESET}" fi + # Remove the installation directory and its contents if [ -d "$INSTALL_DIR" ]; then echo -e "${YELLOW}Removing installation directory and its contents...${RESET}" rm -rf "$INSTALL_DIR" else echo -e "${RED}Installation directory not found. Skipping...${RESET}" fi - echo -e "${GREEN}Uninstallation of Phoenix Code completed.${RESET}" } @@ -602,6 +1009,7 @@ show_help() { echo "Without any options, the script will install Phoenix Code." } +# Check for GUI session by looking for DISPLAY or WAYLAND_DISPLAY variables if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then echo "This script should only be run from terminals in GUI sessions." exit 1 @@ -609,20 +1017,22 @@ fi case "${1-}" in -h|--help) - show_help + show_help # Function to show help ;; --uninstall) - uninstall + uninstall # Function to uninstall ;; --upgrade) - upgrade + upgrade # Function to upgrade ;; "") - install + # This case handles when $1 is unset (acts as a default action) + install # Function to install ;; *) + # This case handles unexpected arguments echo "Invalid option: $1" >&2 - show_help + show_help # Assuming you have a function to show usage information exit 1 ;; esac diff --git a/docs/tauri/update-latest-experimental-build.json b/docs/tauri/update-latest-experimental-build.json index c1c67770..d02b893d 100644 --- a/docs/tauri/update-latest-experimental-build.json +++ b/docs/tauri/update-latest-experimental-build.json @@ -29,7 +29,7 @@ }, "linux-x86_64": { "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIG1pbmlzaWduIHNlY3JldCBrZXkKUlVUVTVEdXJNVyttWm1LT0Rla1BIK1l2akRaclI3TFhGMHZYTEVSbWxmSGt1UFRCeGRPY1BjVkJ3Nk9DMlgvYjZWSXlWSVhjVGR2S1dYa2RsYVF2SnZLY2lra2lObXZ3cWdRPQp0cnVzdGVkIGNvbW1lbnQ6IDE3Njk5Mzc4MDYJZmlsZTpwaG9lbml4LWNvZGUtZXhwZXJpbWVudGFsLWJ1aWxkXzUuMC41LkFwcEltYWdlCjhVR3BVVkFSV010dXZjZkExeDFnOTcrbjRWMW4wVlhmOFYzbmhWMGxUd1VaR0lhc2tmMTRpNTRHYzkyeVNNNnAydG9YSkZvQTJ1U3F1RU96akVsaEN3PT0K", - "url": "https://github.com/phcode-dev/phoenix-desktop/releases/download/prod-app-v5.1.3/phoenix-code_5.1.3.AppImage" + "url": "https://github.com/abose/phoenix-desktop/releases/download/cc/phoenix-code_5.1.16.AppImage" } } }