From 36d9815f8a931b882afe192b06b1e6d5cb8a34dc Mon Sep 17 00:00:00 2001 From: Charly Abraham Date: Tue, 12 May 2026 15:20:23 +0530 Subject: [PATCH 1/2] feat: migrate Linux installer to AppImage with Electron build Replace the GLIBC-versioned tar.gz + bundled GTK/WebKit2GTK installer with a single AppImage flow driven by the Tauri-format update JSON. Probe-driven dependency install: - After download, probe the AppImage with --no-sandbox --version. If it exits cleanly, system libraries are sufficient and apt/dnf/pacman is skipped entirely. - gnome-keyring daemon presence checked independently (binary check; --version doesn't exercise keytar). - AppArmor profile presence checked independently (Ubuntu 24.04+ / Debian 13+ where unprivileged user namespaces are restricted). - Each probe gates its own install step, so sudo is only requested for pieces actually missing. Cross-distro robustness: - apt-cache probe picks libfuse2 (Ubuntu 20.04-22.04, Debian 11-12, Mint 20-21, Neon 22) vs libfuse2t64 (Noble+, Trixie+, Kali, Mint 22+). - KDE sessions skip gnome-keyring to avoid the kwallet/Secret Service D-Bus race. - RHEL 10's missing libXScrnSaver is intentionally not requested (Wayland-only; Electron no-ops gracefully). - Arch gets libxss + libnotify explicitly (not pulled by gtk3). Wrapper script (~/.local/bin/phcode) handles runtime failure modes: - Falls back to --appimage-extract-and-run on FUSE errors. - Falls back to --no-sandbox on userns/AppArmor errors. Other UX improvements: - SIGINT/SIGTERM trap so Ctrl+C cleans up the tmpdir and exits 130. - apt-get install uses --force-confdef/--force-confold + DEBIAN_FRONTEND so an unrelated pending upgrade can't trigger config-file prompts. - Replaced the `yes | sudo apt install` pipeline that silently aborted the script under set -o pipefail when yes died of SIGPIPE. - AppArmor profile preserved across reinstall and --upgrade (only removed by explicit --uninstall) so routine version bumps don't need sudo just to recreate the same profile content. - Post-install hint when the keyring is locked (autologin sessions) explaining the one-time unlock prompt users will see. Removed dead code: check_os_version, download_and_install_gtk, create_launch_script_with_gtk, the GLIBC-version search loop in downloadAndInstall. --- docs/linux/installer.sh | 1205 ++++++++++++++------------------------- 1 file changed, 433 insertions(+), 772 deletions(-) diff --git a/docs/linux/installer.sh b/docs/linux/installer.sh index 87f8b54e..417efa43 100755 --- a/docs/linux/installer.sh +++ b/docs/linux/installer.sh @@ -1,25 +1,36 @@ #!/bin/bash -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" +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. +# +# Two things are checked independently of the --version probe: +# - gnome-keyring daemon presence (libsecret needs the daemon for keytar +# credential storage; --version exits before keytar is required). +# - AppArmor unprivileged-userns profile on Ubuntu 24.04+ / Debian 13+ +# (required for Chromium's sandbox; --version exits before the zygote). +# +# Sudo is only requested if at least one of the three checks fails, and only +# the missing piece gets installed. 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" -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" # Name of the script to invoke the binary +SCRIPT_NAME="phcode" BINARY_NAME="phoenix-code" -# 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 - +APPIMAGE_NAME="phoenix-code.AppImage" +APPARMOR_PROFILE_PATH="/etc/apparmor.d/phoenix-code" declare -a MIME_TYPES=( "text/html" @@ -48,460 +59,368 @@ 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 +} -# 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. - -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 +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 - return 1 } -# 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. -# +# 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+ without our AppArmor profile, +# that init can SIGTRAP. Pass --no-sandbox so the probe ALSO works on systems +# where the AppArmor profile isn't installed yet — this isolates the probe to +# "are the system shared libraries present and loadable?" and decouples it +# from the AppArmor concern (which is handled separately). +# +# 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 +} + +# Returns 0 if this system's kernel restricts unprivileged user namespaces +# (Ubuntu 24.04+ / Debian 13+ / Kali rolling default), which blocks Chromium's +# sandbox inside Electron AppImages unless an AppArmor profile grants userns. +apparmor_userns_restricted() { + [ -d /etc/apparmor.d ] || return 1 + command -v apparmor_parser >/dev/null 2>&1 || return 1 + [ "$(sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo 0)" = "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|dlopen' "\$ERR"; then + rm -f "\$ERR" + exec "\$APPIMAGE" --appimage-extract-and-run "\$@" + fi + if grep -qiE 'userns|apparmor|sandbox|setuid|namespace' "\$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." +# 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 +} - # 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." +# Installs an AppArmor profile that allows the AppImage to use unprivileged +# user namespaces. Required on Ubuntu 24.04+ / Debian 13+ where the kernel knob +# kernel.apparmor_restrict_unprivileged_userns=1 blocks Chromium's sandbox in +# every Electron AppImage. No-op elsewhere, and a no-op if the profile is +# already present (idempotent across re-runs and --upgrade). +install_apparmor_profile_if_needed() { + apparmor_userns_restricted || return 0 + [ -f "$APPARMOR_PROFILE_PATH" ] && return 0 + + sudo tee "$APPARMOR_PROFILE_PATH" >/dev/null <, +include +profile phoenix-code "$INSTALL_DIR/$APPIMAGE_NAME" flags=(unconfined) { + userns, + include if exists +} +EOF + if ! sudo apparmor_parser -r "$APPARMOR_PROFILE_PATH" 2>/dev/null; then + echo -e "${YELLOW}AppArmor profile reload failed; the wrapper will fall back to --no-sandbox if needed.${RESET}" 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|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 +# 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." - 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 + echo "Detected a Fedora/Red Hat-based distribution." + # shellcheck disable=SC2068 + sudo dnf install -y ${pkgs[@]} ;; arch|manjaro|cachyos) - echo "Detected an Arch Linux based distribution." - echo "Updating package database..." + echo "Detected an Arch-based distribution." sudo pacman -Sy - yes | sudo pacman -S webkit2gtk gtk3 libxml2 + # shellcheck disable=SC2068 + sudo pacman -S --noconfirm --needed ${pkgs[@]} ;; + esac +} + +# 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. +# 3. AppArmor unprivileged-userns profile — Chromium sandbox prereq on +# Ubuntu 24.04+ / Debian 13+. Independent of the other two. +# +# Sudo is requested ONLY if at least one of the three probes fails, and only +# the failing pieces are installed. +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 "Unsupported distribution. Please manually install the required dependencies." + echo -e "${RED}Unsupported distribution: $distro. Please install libfuse2/fuse2, libsecret, and (on non-KDE) gnome-keyring manually.${RESET}" 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. - - 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 - } - 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 - } + local keyring; keyring=$(gnome_keyring_pkg_if_needed) + + local libs_ok=1 keyring_ok=1 apparmor_ok=1 + verify_appimage_launches "$appimage" || libs_ok=0 + if [ -n "$keyring" ] && ! gnome_keyring_present; then keyring_ok=0; fi + case "$distro" in + ubuntu|debian|linuxmint|kali|neon) + if apparmor_userns_restricted && [ ! -f "$APPARMOR_PROFILE_PATH" ]; then + apparmor_ok=0 + fi + ;; + esac - 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 - } - 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 - } + if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ] && [ "$apparmor_ok" = 1 ]; then + echo -e "${GREEN}All runtime dependencies already present.${RESET}" + return 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." + local need_pkgs=0 + [ "$libs_ok" = 0 ] || [ "$keyring_ok" = 0 ] && need_pkgs=1 + if [ "$need_pkgs" = 1 ] && [ "$apparmor_ok" = 0 ]; then + echo "Installing runtime packages and AppArmor sandbox profile..." + elif [ "$libs_ok" = 0 ]; then + echo "Installing missing runtime libraries..." + elif [ "$keyring_ok" = 0 ]; then + echo "Installing system keychain daemon..." + else + echo "Installing AppArmor sandbox profile (keeps Chromium's sandbox enabled on Ubuntu 24.04+)..." + fi + echo "This step requires administrative access." + if ! sudo -n true 2>/dev/null; then + echo "Please enter your password to proceed." + 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." + if [ "$need_pkgs" = 1 ]; then + 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 + fi - 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 -e "${GREEN}WebKit2GTK already installed.${RESET}" + if [ "$apparmor_ok" = 0 ]; then + install_apparmor_profile_if_needed fi } - -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 - 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 + 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 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 + local desktop_file="$DESKTOP_ENTRY_NAME" - # 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" @@ -510,7 +429,6 @@ 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" @@ -520,82 +438,36 @@ set_default_application() { fi 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 - fi - - xdg-mime default "$desktop_file" "$mime_type" + 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 - - 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}" - -} - -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" + print_keyring_hint_if_locked } -# 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(){ +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") - 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}" - # 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}" + 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 } - # 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}" + 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 } - # 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 + chmod +x "$TMP_DIR/$APPIMAGE_NAME" + + ensure_runtime_dependencies "$TMP_DIR/$APPIMAGE_NAME" } -# 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) - 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 +# Temporary cleanup for files from older installer versions. +uninstallBetaAppImage() { + rm -f "$LINK_DIR"/phoenix_icon.png } -# 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 + uninstall keep_apparmor 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 keep_apparmor + 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 @@ -844,115 +576,46 @@ 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" - 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}" - # 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 + 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 echo -e "${YELLOW}A newer version of Phoenix Code is available. Proceeding with the upgrade...${RESET}" downloadAndInstall - uninstall + uninstall keep_apparmor copyFilesToDestination echo -e "${GREEN}Upgrade completed successfully. Phoenix Code has been updated to the latest version.${RESET}" 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() { + # mode: + # "full" (default) — remove every artifact, including the + # system-level AppArmor profile under /etc/apparmor.d/. + # Used by the --uninstall CLI entry-point. + # "keep_apparmor" — remove user-scope artifacts only; leave the AppArmor + # profile in place. Used by the internal reinstall and + # --upgrade flows so a routine version bump doesn't need + # sudo just to recreate the same profile content. + local mode="${1:-full}" 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" @@ -960,40 +623,41 @@ 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 [[ "${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 + + if [ "$mode" != "keep_apparmor" ] && [ -f "$APPARMOR_PROFILE_PATH" ]; then + echo -e "${YELLOW}Removing AppArmor profile...${RESET}" + sudo rm -f "$APPARMOR_PROFILE_PATH" || true + if command -v apparmor_parser >/dev/null 2>&1; then + sudo apparmor_parser -R "$APPARMOR_PROFILE_PATH" 2>/dev/null || true + fi + fi + echo -e "${GREEN}Uninstallation of Phoenix Code completed.${RESET}" } @@ -1009,7 +673,6 @@ 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 @@ -1017,22 +680,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 From 3edb7b75aece556b6049d382e97801d85ba6e88d Mon Sep 17 00:00:00 2001 From: Charly Abraham Date: Wed, 13 May 2026 00:13:47 +0530 Subject: [PATCH 2/2] refactor: drop AppArmor profile install, tighten wrapper fallback regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phoenix Code's Electron renderer is already locked down (contextIsolation + nodeIntegration:false + local-only phtauri:// protocol). The kernel-side AppArmor profile only adds defense-in-depth against renderer V8 RCE → raw-syscall escape, which is a narrow class. The IPC-bridge attack surface (electronFSAPI, keytar via electronAPI) is not gated by the kernel sandbox either way. Removing the profile install matches what every other Electron AppImage in the wild does (Cursor, Obsidian, Discord, VSCode-AppImage), removes ~70 LOC, and stops requesting sudo to write under /etc/. Wrapper-script fallbacks stay (verified load-bearing by reading Chromium and AppImage type-2 runtime source): - Sandbox: Chromium LOG(FATAL)s when AppArmor blocks the userns clone on Ubuntu 24.04+ / Debian 13+; it does NOT auto-fall-back. Our --no-sandbox re-exec is the actual recovery and now the only one. - FUSE: the AppImage runtime self-recovers only when libfuse.so.2 can't dlopen; when /dev/fuse is missing or fusermount is denied (Docker/Podman containers, older WSL2, SELinux-restricted kernels), it prints "Cannot mount AppImage, please check your FUSE setup" and exits. Our --appimage-extract-and-run re-exec is the recovery. Wrapper regex tightened: - Drop `dlopen` from the FUSE branch (too broad, could match unrelated library-load failures and route them into a useless retry). - Add `Check failed` to the sandbox branch to catch Chromium's actual NamespaceSandbox / SUID-helper FATAL phrasing. uninstall() reverted to a single behavior (the keep_apparmor mode arg is no longer needed since there's no profile to preserve). --- docs/linux/installer.sh | 135 ++++++++++------------------------------ 1 file changed, 32 insertions(+), 103 deletions(-) diff --git a/docs/linux/installer.sh b/docs/linux/installer.sh index 417efa43..8c442ddc 100755 --- a/docs/linux/installer.sh +++ b/docs/linux/installer.sh @@ -9,15 +9,20 @@ set -euo pipefail # is attempted — the system already has every shared library Phoenix Code # needs at startup, regardless of which package name provides it. # -# Two things are checked independently of the --version probe: -# - gnome-keyring daemon presence (libsecret needs the daemon for keytar -# credential storage; --version exits before keytar is required). -# - AppArmor unprivileged-userns profile on Ubuntu 24.04+ / Debian 13+ -# (required for Chromium's sandbox; --version exits before the zygote). +# 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. # -# Sudo is only requested if at least one of the three checks fails, and only -# the missing piece gets installed. Also wires up a desktop entry + `phcode` -# wrapper script that handles AppImage launch fallbacks. +# 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" @@ -30,7 +35,6 @@ DESKTOP_ENTRY="$DESKTOP_DIR/$DESKTOP_ENTRY_NAME" SCRIPT_NAME="phcode" BINARY_NAME="phoenix-code" APPIMAGE_NAME="phoenix-code.AppImage" -APPARMOR_PROFILE_PATH="/etc/apparmor.d/phoenix-code" declare -a MIME_TYPES=( "text/html" @@ -106,11 +110,10 @@ check_architecture() { # 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+ without our AppArmor profile, -# that init can SIGTRAP. Pass --no-sandbox so the probe ALSO works on systems -# where the AppArmor profile isn't installed yet — this isolates the probe to -# "are the system shared libraries present and loadable?" and decouples it -# from the AppArmor concern (which is handled separately). +# 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 @@ -126,15 +129,6 @@ gnome_keyring_present() { command -v gnome-keyring-daemon >/dev/null 2>&1 } -# Returns 0 if this system's kernel restricts unprivileged user namespaces -# (Ubuntu 24.04+ / Debian 13+ / Kali rolling default), which blocks Chromium's -# sandbox inside Electron AppImages unless an AppArmor profile grants userns. -apparmor_userns_restricted() { - [ -d /etc/apparmor.d ] || return 1 - command -v apparmor_parser >/dev/null 2>&1 || return 1 - [ "$(sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo 0)" = "1" ] -} - downloadLatestReleaseInfo() { local release_info_file="$TMP_DIR/latest_release.json" if [ ! -f "$release_info_file" ]; then @@ -179,11 +173,11 @@ create_invocation_script() { APPIMAGE="$install_dir/$APPIMAGE_NAME" ERR=\$(mktemp) if ! "\$APPIMAGE" "\$@" 2>"\$ERR"; then - if grep -qiE 'fuse|libfuse|dlopen' "\$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' "\$ERR"; then + if grep -qiE 'userns|apparmor|sandbox|setuid|namespace|Check failed' "\$ERR"; then rm -f "\$ERR" exec "\$APPIMAGE" --no-sandbox "\$@" fi @@ -212,32 +206,6 @@ gnome_keyring_pkg_if_needed() { fi } -# Installs an AppArmor profile that allows the AppImage to use unprivileged -# user namespaces. Required on Ubuntu 24.04+ / Debian 13+ where the kernel knob -# kernel.apparmor_restrict_unprivileged_userns=1 blocks Chromium's sandbox in -# every Electron AppImage. No-op elsewhere, and a no-op if the profile is -# already present (idempotent across re-runs and --upgrade). -install_apparmor_profile_if_needed() { - apparmor_userns_restricted || return 0 - [ -f "$APPARMOR_PROFILE_PATH" ] && return 0 - - sudo tee "$APPARMOR_PROFILE_PATH" >/dev/null <, -include -profile phoenix-code "$INSTALL_DIR/$APPIMAGE_NAME" flags=(unconfined) { - userns, - include if exists -} -EOF - if ! sudo apparmor_parser -r "$APPARMOR_PROFILE_PATH" 2>/dev/null; then - echo -e "${YELLOW}AppArmor profile reload failed; the wrapper will fall back to --no-sandbox if needed.${RESET}" - 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 @@ -304,11 +272,8 @@ install_packages_for_distro() { # 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. -# 3. AppArmor unprivileged-userns profile — Chromium sandbox prereq on -# Ubuntu 24.04+ / Debian 13+. Independent of the other two. # -# Sudo is requested ONLY if at least one of the three probes fails, and only -# the failing pieces are installed. +# Sudo is requested ONLY if at least one of the probes fails. ensure_runtime_dependencies() { local appimage="$1" if [ ! -f /etc/os-release ]; then @@ -326,49 +291,30 @@ ensure_runtime_dependencies() { esac local keyring; keyring=$(gnome_keyring_pkg_if_needed) - local libs_ok=1 keyring_ok=1 apparmor_ok=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 - case "$distro" in - ubuntu|debian|linuxmint|kali|neon) - if apparmor_userns_restricted && [ ! -f "$APPARMOR_PROFILE_PATH" ]; then - apparmor_ok=0 - fi - ;; - esac - if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ] && [ "$apparmor_ok" = 1 ]; then + if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ]; then echo -e "${GREEN}All runtime dependencies already present.${RESET}" return 0 fi - local need_pkgs=0 - [ "$libs_ok" = 0 ] || [ "$keyring_ok" = 0 ] && need_pkgs=1 - if [ "$need_pkgs" = 1 ] && [ "$apparmor_ok" = 0 ]; then - echo "Installing runtime packages and AppArmor sandbox profile..." - elif [ "$libs_ok" = 0 ]; then + if [ "$libs_ok" = 0 ]; then echo "Installing missing runtime libraries..." - elif [ "$keyring_ok" = 0 ]; then - echo "Installing system keychain daemon..." else - echo "Installing AppArmor sandbox profile (keeps Chromium's sandbox enabled on Ubuntu 24.04+)..." + 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 - if [ "$need_pkgs" = 1 ]; then - 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 - fi - - if [ "$apparmor_ok" = 0 ]; then - install_apparmor_profile_if_needed + 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 } @@ -552,7 +498,7 @@ install() { echo -e "${YELLOW}Phoenix Code appears to be already installed.${RESET}" if [ ! -t 0 ]; then echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" - uninstall keep_apparmor + uninstall downloadAndInstall copyFilesToDestination else @@ -560,7 +506,7 @@ install() { case "$response" in [Yy]* ) echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}" - uninstall keep_apparmor + uninstall downloadAndInstall copyFilesToDestination ;; @@ -595,7 +541,7 @@ upgrade() { 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 keep_apparmor + uninstall copyFilesToDestination echo -e "${GREEN}Upgrade completed successfully. Phoenix Code has been updated to the latest version.${RESET}" else @@ -604,15 +550,6 @@ upgrade() { } uninstall() { - # mode: - # "full" (default) — remove every artifact, including the - # system-level AppArmor profile under /etc/apparmor.d/. - # Used by the --uninstall CLI entry-point. - # "keep_apparmor" — remove user-scope artifacts only; leave the AppArmor - # profile in place. Used by the internal reinstall and - # --upgrade flows so a routine version bump doesn't need - # sudo just to recreate the same profile content. - local mode="${1:-full}" echo -e "${YELLOW}Starting uninstallation of Phoenix Code...${RESET}" uninstallBetaAppImage @@ -650,14 +587,6 @@ uninstall() { echo -e "${RED}Installation directory not found. Skipping...${RESET}" fi - if [ "$mode" != "keep_apparmor" ] && [ -f "$APPARMOR_PROFILE_PATH" ]; then - echo -e "${YELLOW}Removing AppArmor profile...${RESET}" - sudo rm -f "$APPARMOR_PROFILE_PATH" || true - if command -v apparmor_parser >/dev/null 2>&1; then - sudo apparmor_parser -R "$APPARMOR_PROFILE_PATH" 2>/dev/null || true - fi - fi - echo -e "${GREEN}Uninstallation of Phoenix Code completed.${RESET}" }