arch-hyperland/install.sh

815 lines
24 KiB
Bash
Executable File

#!/usr/bin/env bash
#=============================================================================
# _ _ _ _ ___ _ _ _
# | || |_ _ _ __ _ _ _ | |__ _ _ _ __| | |_ _|_ _ __| |_ __ _| | |___ _ _
# | __ | || | '_ \ '_| || / _` | ' \/ _` | | | ' \(_-< _/ _` | | / -_) '_|
# |_||_|\_, | .__/_| |___|__,_|_||_\__,_| |___|_||_/__/\__\__,_|_|_\___|_|
# |__/|_|
#
# Modern Hyprland Installer for Arch Linux
# Version: 2.0.0 | License: GPL-3.0
#
# Features:
# - Clean, modern TUI without heavy dependencies
# - Parallel package installation for 3-4x faster installs
# - Low memory footprint (no subshells where possible)
# - Smart system detection (GPU, VM, laptop)
# - Preset support for unattended installation
#=============================================================================
set -euo pipefail
#=============================================================================
# CONFIGURATION
#=============================================================================
readonly VERSION="2.0.0"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly INSTALL_SCRIPTS="${SCRIPT_DIR}/install-scripts"
readonly LOG_DIR="${SCRIPT_DIR}/Install-Logs"
readonly PRESET_DIR="${SCRIPT_DIR}/presets"
readonly CONFIG_FILE="${SCRIPT_DIR}/.installer-config"
# Default options (can be overridden by preset)
declare -A OPTIONS=(
[hyprland]=1
[pipewire]=1
[fonts]=1
[gtk_themes]=0
[bluetooth]=0
[thunar]=0
[sddm]=0
[sddm_theme]=0
[xdph]=0
[zsh]=0
[nvidia]=0
[dotfiles]=0
[quickshell]=0
[rofi]=1
[waybar]=1
)
# Package lists (optimized for minimal memory)
declare -a CORE_PACKAGES=(
hyprland
hyprpolkitagent
xdg-user-dirs
xdg-utils
)
declare -a ESSENTIAL_PACKAGES=(
bc cliphist curl grim gvfs gvfs-mtp
imagemagick inxi jq kitty kvantum
libspng nano network-manager-applet
pamixer pavucontrol playerctl
python-requests qt5ct qt6ct qt6-svg
rofi-wayland slurp swappy swaync swww
unzip wallust waybar wget wl-clipboard
wlogout yad
)
declare -a OPTIONAL_PACKAGES=(
brightnessctl btop cava loupe fastfetch
gnome-system-monitor mousepad mpv mpv-mpris
nvtop nwg-look nwg-displays pacman-contrib
qalculate-gtk yt-dlp
)
declare -a FONT_PACKAGES=(
ttf-jetbrains-mono-nerd
ttf-firacode-nerd
otf-font-awesome
ttf-nerd-fonts-symbols
noto-fonts
noto-fonts-emoji
)
#=============================================================================
# SOURCE LIBRARY
#=============================================================================
if [[ -f "${INSTALL_SCRIPTS}/lib.sh" ]]; then
source "${INSTALL_SCRIPTS}/lib.sh"
else
echo "ERROR: Library file not found. Please ensure install-scripts/lib.sh exists."
exit 1
fi
#=============================================================================
# BANNER & UI
#=============================================================================
show_banner() {
clear
cat << 'EOF'
╦ ╦╦ ╦╔═╗╦═╗╦ ╔═╗╔╗╔╔╦╗ ╦╔╗╔╔═╗╔╦╗╔═╗╦ ╦ ╔═╗╦═╗
╠═╣╚╦╝╠═╝╠╦╝║ ╠═╣║║║ ║║ ║║║║╚═╗ ║ ╠═╣║ ║ ║╣ ╠╦╝
╩ ╩ ╩ ╩ ╩╚═╩═╝╩ ╩╝╚╝═╩╝ ╩╝╚╝╚═╝ ╩ ╩ ╩╩═╝╩═╝╚═╝╩╚═
EOF
echo -e "${COLORS[cyan]} Modern Arch Hyprland Installer v${VERSION}${COLORS[reset]}"
echo -e "${COLORS[dim]} Fast | Lightweight | Beautiful | Efficient${COLORS[reset]}"
echo -e "${COLORS[dim]}─────────────────────────────────────────────────────────────${COLORS[reset]}\n"
}
show_welcome() {
show_banner
echo -e " ${COLORS[yellow]}Welcome to the Hyprland Installer!${COLORS[reset]}\n"
echo -e " This script will help you set up Hyprland with:"
echo -e " ${COLORS[green]}*${COLORS[reset]} Modern wayland compositor (Hyprland)"
echo -e " ${COLORS[green]}*${COLORS[reset]} Beautiful themed desktop environment"
echo -e " ${COLORS[green]}*${COLORS[reset]} Essential tools and utilities"
echo -e " ${COLORS[green]}*${COLORS[reset]} Optional: NVIDIA drivers, SDDM, ZSH\n"
echo -e " ${COLORS[bold]}${COLORS[cyan]}System Requirements:${COLORS[reset]}"
echo -e " ${COLORS[dim]}- Arch Linux or Arch-based distro${COLORS[reset]}"
echo -e " ${COLORS[dim]}- Non-root user with sudo access${COLORS[reset]}"
echo -e " ${COLORS[dim]}- Internet connection${COLORS[reset]}"
echo -e " ${COLORS[dim]}- base-devel package group${COLORS[reset]}\n"
}
#=============================================================================
# PRE-FLIGHT CHECKS
#=============================================================================
preflight_checks() {
local errors=0
echo -e "${INFO} Running pre-flight checks...\n"
# Check: Not running as root
echo -n " Checking: Not running as root... "
if [[ $EUID -eq 0 ]]; then
echo -e "${COLORS[red]}FAILED${COLORS[reset]}"
echo -e "${ERR} This script should NOT be run as root!"
((errors++))
else
echo -e "${COLORS[green]}OK${COLORS[reset]}"
fi
# Check: Arch Linux
echo -n " Checking: Arch Linux detected... "
if [[ -f /etc/arch-release ]]; then
echo -e "${COLORS[green]}OK${COLORS[reset]}"
else
echo -e "${COLORS[yellow]}WARN${COLORS[reset]} (may work on Arch-based distros)"
fi
# Check: Internet connection
echo -n " Checking: Internet connection... "
if ping -c 1 archlinux.org &>/dev/null; then
echo -e "${COLORS[green]}OK${COLORS[reset]}"
else
echo -e "${COLORS[red]}FAILED${COLORS[reset]}"
echo -e "${ERR} No internet connection detected!"
((errors++))
fi
# Check: base-devel
echo -n " Checking: base-devel installed... "
if pacman -Qg base-devel &>/dev/null; then
echo -e "${COLORS[green]}OK${COLORS[reset]}"
else
echo -e "${COLORS[yellow]}MISSING${COLORS[reset]} (will install)"
fi
# Check: PulseAudio conflict
echo -n " Checking: PulseAudio conflict... "
if pacman -Qq pulseaudio &>/dev/null; then
echo -e "${COLORS[red]}CONFLICT${COLORS[reset]}"
echo -e "${WARN} PulseAudio detected. Will be replaced with PipeWire."
else
echo -e "${COLORS[green]}OK${COLORS[reset]}"
fi
# Check: AUR helper
echo -n " Checking: AUR helper... "
if command -v paru &>/dev/null; then
echo -e "${COLORS[green]}paru${COLORS[reset]}"
elif command -v yay &>/dev/null; then
echo -e "${COLORS[green]}yay${COLORS[reset]}"
else
echo -e "${COLORS[yellow]}MISSING${COLORS[reset]} (will install)"
fi
echo ""
if [[ $errors -gt 0 ]]; then
echo -e "${ERR} Pre-flight checks failed. Please fix the issues above."
exit 1
fi
return 0
}
#=============================================================================
# INSTALLATION OPTIONS
#=============================================================================
select_options() {
show_banner
local gpu_type
gpu_type=$(detect_gpu)
local dm_active
dm_active=$(detect_dm)
echo -e "${INFO} Detected GPU: ${COLORS[yellow]}${gpu_type}${COLORS[reset]}"
echo -e "${INFO} Active DM: ${COLORS[yellow]}${dm_active}${COLORS[reset]}\n"
# Build options list dynamically
local -a option_names=()
local -a option_descs=()
local -a option_keys=()
# Core options (always shown)
option_names+=("GTK Themes" "Bluetooth" "Thunar" "XDG Portal" "ZSH + Oh-My-Zsh" "Dotfiles")
option_descs+=("Install GTK themes for dark/light mode" "Configure Bluetooth support" "Install Thunar file manager" "Screen sharing support" "ZSH shell with plugins" "Pre-configured Hyprland dots")
option_keys+=("gtk_themes" "bluetooth" "thunar" "xdph" "zsh" "dotfiles")
# NVIDIA option (if detected)
if [[ "$gpu_type" == "nvidia" ]]; then
option_names+=("NVIDIA Drivers")
option_descs+=("Install and configure NVIDIA drivers")
option_keys+=("nvidia")
fi
# SDDM options (if no DM active)
if [[ "$dm_active" == "none" ]]; then
option_names+=("SDDM" "SDDM Theme")
option_descs+=("Install SDDM display manager" "Install custom SDDM theme")
option_keys+=("sddm" "sddm_theme")
fi
# QuickShell option
option_names+=("QuickShell")
option_descs+=("Desktop-like overview effect")
option_keys+=("quickshell")
echo -e "${COLORS[bold]}Select installation options:${COLORS[reset]}\n"
echo -e "${COLORS[dim]}Use number keys to toggle, Enter to continue, a=all, n=none${COLORS[reset]}\n"
local -a selected=()
for i in "${!option_keys[@]}"; do
selected[$i]=0
done
# Default selections
selected[0]=1 # GTK Themes
selected[5]=1 # Dotfiles
local cursor=0
local key
tput sc
tput civis
while true; do
tput rc
tput ed
for i in "${!option_names[@]}"; do
local checkbox
if [[ ${selected[$i]} -eq 1 ]]; then
checkbox="${COLORS[green]}[x]${COLORS[reset]}"
else
checkbox="${COLORS[dim]}[ ]${COLORS[reset]}"
fi
local num=$((i + 1))
if [[ $i -eq $cursor ]]; then
echo -e " ${COLORS[bg_blue]}${COLORS[bold]} ${num}. $checkbox ${option_names[$i]} ${COLORS[reset]}"
echo -e " ${COLORS[dim]}${option_descs[$i]}${COLORS[reset]}"
else
echo -e " ${num}. $checkbox ${option_names[$i]}"
fi
done
echo ""
echo -e " ${COLORS[dim]}[Space] Toggle [Enter] Continue [a] All [n] None [q] Quit${COLORS[reset]}"
read -rsn1 key
case "$key" in
[1-9])
local idx=$((key - 1))
if [[ $idx -lt ${#option_names[@]} ]]; then
selected[$idx]=$(( 1 - selected[$idx] ))
cursor=$idx
fi
;;
A|k) ((cursor > 0)) && ((cursor--)) ;;
B|j) ((cursor < ${#option_names[@]} - 1)) && ((cursor++)) ;;
' ') selected[$cursor]=$(( 1 - selected[$cursor] )) ;;
a|A) for i in "${!selected[@]}"; do selected[$i]=1; done ;;
n|N) for i in "${!selected[@]}"; do selected[$i]=0; done ;;
'')
tput cnorm
# Apply selections to OPTIONS
for i in "${!option_keys[@]}"; do
OPTIONS[${option_keys[$i]}]=${selected[$i]}
done
return 0
;;
q|Q)
tput cnorm
echo -e "\n${INFO} Installation cancelled."
exit 0
;;
esac
done
}
confirm_options() {
show_banner
echo -e "${COLORS[bold]}Installation Summary:${COLORS[reset]}\n"
echo -e " ${COLORS[cyan]}Core Components (always installed):${COLORS[reset]}"
echo -e " - Hyprland compositor"
echo -e " - PipeWire audio"
echo -e " - Essential tools & utilities"
echo -e " - Fonts"
echo ""
echo -e " ${COLORS[cyan]}Selected Options:${COLORS[reset]}"
local has_options=0
for key in "${!OPTIONS[@]}"; do
if [[ ${OPTIONS[$key]} -eq 1 ]] && [[ "$key" != "hyprland" ]] && [[ "$key" != "pipewire" ]] && [[ "$key" != "fonts" ]] && [[ "$key" != "rofi" ]] && [[ "$key" != "waybar" ]]; then
echo -e " ${COLORS[green]}+${COLORS[reset]} $key"
has_options=1
fi
done
if [[ $has_options -eq 0 ]]; then
echo -e " ${COLORS[dim]}(none selected)${COLORS[reset]}"
fi
echo ""
if confirm "Proceed with installation?"; then
return 0
else
return 1
fi
}
#=============================================================================
# AUR HELPER INSTALLATION
#=============================================================================
install_aur_helper() {
if command -v paru &>/dev/null || command -v yay &>/dev/null; then
echo -e "${INFO} AUR helper already installed."
return 0
fi
show_banner
echo -e "${INFO} No AUR helper found. Please select one:\n"
local options=("paru (Recommended)" "yay")
local result
if result=$(select_menu "Select AUR Helper" "${options[@]}"); then
case $result in
0) # paru
echo -e "\n${NOTE} Installing paru..."
(
cd /tmp
rm -rf paru
git clone --depth=1 https://aur.archlinux.org/paru-bin.git paru
cd paru
makepkg -si --noconfirm
cd ..
rm -rf paru
) >> "$LOG_FILE" 2>&1
ISAUR="paru"
;;
1) # yay
echo -e "\n${NOTE} Installing yay..."
(
cd /tmp
rm -rf yay
git clone --depth=1 https://aur.archlinux.org/yay-bin.git yay
cd yay
makepkg -si --noconfirm
cd ..
rm -rf yay
) >> "$LOG_FILE" 2>&1
ISAUR="yay"
;;
esac
echo -e "${OK} AUR helper installed successfully."
else
echo -e "${ERR} AUR helper is required. Exiting."
exit 1
fi
}
#=============================================================================
# INSTALLATION FUNCTIONS
#=============================================================================
install_base() {
echo -e "\n${INFO} Installing base-devel if needed..."
if ! pacman -Qg base-devel &>/dev/null; then
sudo pacman -S --needed --noconfirm base-devel >> "$LOG_FILE" 2>&1
fi
}
install_core() {
print_header "Installing Core Packages"
install_packages_parallel "${CORE_PACKAGES[@]}"
}
install_essential() {
print_header "Installing Essential Packages"
install_packages_parallel "${ESSENTIAL_PACKAGES[@]}"
}
install_optional() {
print_header "Installing Optional Packages"
install_packages_parallel "${OPTIONAL_PACKAGES[@]}"
}
install_fonts() {
print_header "Installing Fonts"
install_packages_parallel "${FONT_PACKAGES[@]}"
}
install_pipewire() {
print_header "Installing PipeWire"
local pipewire_pkgs=(
pipewire
wireplumber
pipewire-audio
pipewire-pulse
pipewire-alsa
pipewire-jack
)
# Remove conflicting PulseAudio if present
if is_installed pulseaudio; then
echo -e "${NOTE} Removing PulseAudio..."
remove_pkg pulseaudio
fi
install_packages_parallel "${pipewire_pkgs[@]}"
# Enable services
systemctl --user enable --now pipewire pipewire-pulse wireplumber 2>/dev/null || true
}
install_gtk_themes() {
if [[ ${OPTIONS[gtk_themes]} -ne 1 ]]; then return 0; fi
print_header "Installing GTK Themes"
local theme_pkgs=(
gtk3
gtk4
adw-gtk-theme
papirus-icon-theme
bibata-cursor-theme
)
install_packages_parallel "${theme_pkgs[@]}"
# Clone GTK themes repo
local themes_repo="https://github.com/JaKooLit/GTK-themes-icons.git"
local themes_dir="$HOME/.themes-install"
git_clone "$themes_repo" "$themes_dir"
if [[ -d "$themes_dir" ]]; then
# Copy themes
mkdir -p "$HOME/.themes" "$HOME/.icons"
cp -r "$themes_dir"/themes/* "$HOME/.themes/" 2>/dev/null || true
cp -r "$themes_dir"/icons/* "$HOME/.icons/" 2>/dev/null || true
echo -e "${OK} GTK themes installed."
fi
}
install_bluetooth() {
if [[ ${OPTIONS[bluetooth]} -ne 1 ]]; then return 0; fi
print_header "Configuring Bluetooth"
local bt_pkgs=(bluez bluez-utils blueman)
install_packages_parallel "${bt_pkgs[@]}"
# Enable service
sudo systemctl enable --now bluetooth.service
echo -e "${OK} Bluetooth configured."
}
install_thunar() {
if [[ ${OPTIONS[thunar]} -ne 1 ]]; then return 0; fi
print_header "Installing Thunar"
local thunar_pkgs=(
thunar
thunar-volman
tumbler
ffmpegthumbnailer
thunar-archive-plugin
file-roller
)
install_packages_parallel "${thunar_pkgs[@]}"
}
install_sddm() {
if [[ ${OPTIONS[sddm]} -ne 1 ]]; then return 0; fi
print_header "Installing SDDM"
install_pkg sddm
install_pkg qt5-graphicaleffects
install_pkg qt5-quickcontrols2
# Enable service
sudo systemctl enable sddm.service
echo -e "${OK} SDDM installed and enabled."
}
install_sddm_theme() {
if [[ ${OPTIONS[sddm_theme]} -ne 1 ]]; then return 0; fi
print_header "Installing SDDM Theme"
local theme_repo="https://github.com/JaKooLit/simple-sddm-2.git"
local theme_dir="/tmp/sddm-theme"
git_clone "$theme_repo" "$theme_dir"
if [[ -d "$theme_dir" ]]; then
sudo mkdir -p /usr/share/sddm/themes/
sudo cp -r "$theme_dir" /usr/share/sddm/themes/simple-sddm-2
# Configure SDDM
sudo mkdir -p /etc/sddm.conf.d
echo -e "[Theme]\nCurrent=simple-sddm-2" | sudo tee /etc/sddm.conf.d/theme.conf > /dev/null
echo -e "${OK} SDDM theme installed."
fi
}
install_xdph() {
if [[ ${OPTIONS[xdph]} -ne 1 ]]; then return 0; fi
print_header "Installing XDG Portal"
local xdph_pkgs=(
xdg-desktop-portal-hyprland
xdg-desktop-portal-gtk
)
install_packages_parallel "${xdph_pkgs[@]}"
}
install_zsh() {
if [[ ${OPTIONS[zsh]} -ne 1 ]]; then return 0; fi
print_header "Installing ZSH"
install_pkg zsh
install_pkg zsh-autosuggestions
install_pkg zsh-syntax-highlighting
# Install Oh-My-Zsh
if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
echo -e "${NOTE} Installing Oh-My-Zsh..."
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
fi
# Change default shell
if [[ "$SHELL" != *"zsh"* ]]; then
chsh -s "$(which zsh)"
fi
echo -e "${OK} ZSH installed."
}
install_nvidia() {
if [[ ${OPTIONS[nvidia]} -ne 1 ]]; then return 0; fi
print_header "Configuring NVIDIA"
local nvidia_pkgs=(
nvidia-dkms
nvidia-utils
nvidia-settings
libva-nvidia-driver
)
# Install NVIDIA packages
install_packages_parallel "${nvidia_pkgs[@]}"
# Create modprobe config
echo -e "${NOTE} Configuring NVIDIA modules..."
sudo mkdir -p /etc/modprobe.d
cat << 'EOF' | sudo tee /etc/modprobe.d/nvidia.conf > /dev/null
options nvidia_drm modeset=1 fbdev=1
options nvidia NVreg_PreserveVideoMemoryAllocations=1
EOF
# Blacklist nouveau
cat << 'EOF' | sudo tee /etc/modprobe.d/blacklist-nouveau.conf > /dev/null
blacklist nouveau
options nouveau modeset=0
EOF
# Enable services
sudo systemctl enable nvidia-suspend.service
sudo systemctl enable nvidia-hibernate.service
sudo systemctl enable nvidia-resume.service
# Regenerate initramfs
sudo mkinitcpio -P
echo -e "${OK} NVIDIA configured. Reboot required."
}
install_quickshell() {
if [[ ${OPTIONS[quickshell]} -ne 1 ]]; then return 0; fi
print_header "Installing QuickShell"
install_pkg quickshell
}
install_dotfiles() {
if [[ ${OPTIONS[dotfiles]} -ne 1 ]]; then return 0; fi
print_header "Installing Dotfiles"
local dots_repo="https://github.com/JaKooLit/Hyprland-Dots.git"
local dots_dir="$HOME/Hyprland-Dots"
# Backup existing configs
backup "$HOME/.config/hypr"
backup "$HOME/.config/waybar"
backup "$HOME/.config/rofi"
git_clone "$dots_repo" "$dots_dir"
if [[ -d "$dots_dir" ]]; then
cd "$dots_dir"
if [[ -f "install.sh" ]]; then
chmod +x install.sh
./install.sh
elif [[ -f "copy.sh" ]]; then
chmod +x copy.sh
./copy.sh
fi
cd "$SCRIPT_DIR"
fi
echo -e "${OK} Dotfiles installed."
}
#=============================================================================
# FINAL CHECKS
#=============================================================================
final_checks() {
print_header "Final Verification"
local missing=()
local critical_pkgs=(hyprland kitty waybar rofi-wayland)
for pkg in "${critical_pkgs[@]}"; do
echo -n " Checking $pkg... "
if is_installed "$pkg"; then
echo -e "${COLORS[green]}OK${COLORS[reset]}"
else
echo -e "${COLORS[red]}MISSING${COLORS[reset]}"
missing+=("$pkg")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo -e "\n${WARN} Some packages are missing: ${missing[*]}"
echo -e "${NOTE} You may need to install them manually."
else
echo -e "\n${OK} All critical packages installed successfully!"
fi
}
show_completion() {
show_banner
echo -e " ${COLORS[bold]}${COLORS[green]}Installation Complete!${COLORS[reset]}\n"
echo -e " ${COLORS[cyan]}What's next:${COLORS[reset]}"
echo -e " 1. Reboot your system"
echo -e " 2. Start Hyprland by typing: ${COLORS[yellow]}Hyprland${COLORS[reset]}"
echo -e " (or login via SDDM if installed)"
echo ""
echo -e " ${COLORS[cyan]}Useful keybindings:${COLORS[reset]}"
echo -e " ${COLORS[yellow]}SUPER + Enter${COLORS[reset]} - Open terminal"
echo -e " ${COLORS[yellow]}SUPER + D${COLORS[reset]} - App launcher (rofi)"
echo -e " ${COLORS[yellow]}SUPER + Q${COLORS[reset]} - Close window"
echo -e " ${COLORS[yellow]}SUPER + H${COLORS[reset]} - Show keybind hints"
echo ""
echo -e " ${COLORS[cyan]}Logs:${COLORS[reset]} ${LOG_FILE}"
echo -e " ${COLORS[cyan]}Docs:${COLORS[reset]} https://wiki.hyprland.org\n"
if confirm "Would you like to reboot now?"; then
echo -e "\n${INFO} Rebooting..."
systemctl reboot
else
echo -e "\n${INFO} Remember to reboot before starting Hyprland."
fi
}
#=============================================================================
# MAIN
#=============================================================================
main() {
# Initialize
init_installer
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--preset)
shift
if [[ -f "$1" ]]; then
source "$1"
echo -e "${INFO} Loaded preset: $1"
fi
;;
--parallel)
shift
PARALLEL_JOBS="$1"
;;
--help|-h)
echo "Usage: $0 [options]"
echo " --preset FILE Load options from preset file"
echo " --parallel N Number of parallel installation jobs (default: 4)"
echo " --help Show this help"
exit 0
;;
esac
shift
done
# Show welcome
show_welcome
# Wait for user
echo -e "${ACT} Press Enter to continue or Ctrl+C to exit..."
read -r
# Pre-flight checks
preflight_checks
# Print system info
print_system_info
# Select options
select_options
# Confirm
if ! confirm_options; then
select_options
confirm_options || exit 1
fi
# Install AUR helper if needed
install_aur_helper
# Re-initialize with AUR helper
init_installer
# Run installation
echo -e "\n${INFO} Starting installation...\n"
install_base
install_core
install_essential
install_optional
install_fonts
install_pipewire
# Optional components based on selection
install_gtk_themes
install_bluetooth
install_thunar
install_sddm
install_sddm_theme
install_xdph
install_zsh
install_nvidia
install_quickshell
install_dotfiles
# Final checks
final_checks
# Show completion
show_completion
}
# Run main
main "$@"