#!/bin/bash # Post install script for Fedora # Author: Jacob Schantli # Version: 1.1 # Make a pipeline fail if any command in it fails. Without this, # `curl ... | bash` reports success even when the download fails. set -o pipefail # Steps that failed during this run (shown in the summary at the end) FAILED_STEPS=() # ----------------------------------------------------------------------------- # Global Configuration # ----------------------------------------------------------------------------- DNF_PACKAGES=( "gnome-tweaks" "steam" "lutris" "btop" "fastfetch" "distrobox" "pipx" "bash-completion" "tar" "bat" "tree" "unzip" "git" "fzf" "mpv" "fuse" "fuse-libs" "nodejs" ) FLATPAK_PACKAGES=( "com.github.tchx84.Flatseal" "net.nokyan.Resources" "com.mattjakeman.ExtensionManager" "io.github.realmazharhussain.GdmSettings" "com.ranfdev.DistroShelf" "it.mijorus.gearlever" "io.github.fastrizwaan.WineZGUI" "com.vysp3r.ProtonPlus" "io.missioncenter.MissionCenter" "io.github.peazip.PeaZip" #"app.devsuite.Ptyxis" "re.sonny.Junction" #"com.github.rafostar.Clapper" "io.github.flattool.Ignition" "io.github.swordpuffin.rewaita" "page.tesk.Refine" #"io.gitlab.adhami3310.Converter" "com.usebottles.bottles" "com.bambulab.BambuStudio" "com.collaboraoffice.Office" "io.github.kolunmi.Bazaar" "com.fastmail.Fastmail" ) GNOME_EXTENSIONS=( "dhruva@narkagni" "app-grid-wizard@mirzadeh.pro" "appindicatorsupport@rgcjonas.gmail.com" "blur-my-shell@aunetx" "caffeine@patapon.info" "dynamic-music-pill@andbal" "gnome-fuzzy-app-search@gnome-shell-extensions.Czarlie.gitlab.com" #"IP-Finder@linxgem33.com" #"lan-ip-address@mrhuber.com" #"update-extension@purejava.org" "user-theme@gnome-shell-extensions.gcampax.github.com" "gnome-wallpaper-engine@gjs.com.zip" ) # ----------------------------------------------------------------------------- # Helper Functions # ----------------------------------------------------------------------------- # Colors for output GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } command_exists() { command -v "$1" >/dev/null 2>&1 } # Check if the last command was successful check_status() { local status=$? if [ $status -eq 0 ]; then log_info "$1 successful." else log_error "$1 failed." FAILED_STEPS+=("$1") # If the second argument is "critical", exit the script if [ "$2" == "critical" ]; then log_error "Critical error encountered. Exiting." exit 1 fi fi } # Append text to a file if it doesn't already exist append_to_file_if_missing() { local file="$1" local text="$2" local description="$3" local use_sudo="$4" if grep -Fq "$text" "$file"; then log_warn "$description already exists in $file. Skipping." else if [ "$use_sudo" == "true" ]; then echo "$text" | sudo tee -a "$file" > /dev/null else echo "$text" >> "$file" fi log_info "Added $description to $file." fi } # ----------------------------------------------------------------------------- # Installation Functions # ----------------------------------------------------------------------------- optimize_dnf() { log_info "----------------------------------------------------" log_info "Modifying /etc/dnf/dnf.conf for faster downloads." log_info "----------------------------------------------------" DNF_CONF="/etc/dnf/dnf.conf" if [ -f "$DNF_CONF" ]; then append_to_file_if_missing "$DNF_CONF" "max_parallel_downloads=10" "max_parallel_downloads" "true" append_to_file_if_missing "$DNF_CONF" "defaultyes=True" "defaultyes" "true" append_to_file_if_missing "$DNF_CONF" "keepcache=True" "keepcache" "true" else log_error "$DNF_CONF not found!" fi sleep 1 } install_fonts() { log_info "----------------------------------------------------" log_info "Installing Microsoft Core Fonts and Meslo Nerd Font." log_info "----------------------------------------------------" log_info "Updating system and installing prerequisites..." sudo dnf upgrade --refresh -y check_status "System upgrade" "critical" sudo dnf install -y curl cabextract xorg-x11-font-utils fontconfig wget unzip check_status "Prerequisites installation" "critical" log_info "Installing Microsoft Core Fonts..." if rpm -q msttcore-fonts-installer > /dev/null 2>&1; then log_warn "Microsoft Core Fonts already installed." else sudo dnf install -y https://downloads.sourceforge.net/project/mscorefonts2/rpms/msttcore-fonts-installer-2.6-1.noarch.rpm check_status "Microsoft Core Fonts installation" "non-critical" fi log_info "Installing Meslo Nerd Font..." FONT_NAME="MesloLGS Nerd Font Mono" # Redirect instead of `grep -q` so fc-list isn't killed by SIGPIPE (pipefail) if fc-list :family | grep -i "$FONT_NAME" > /dev/null; then log_info "Font '$FONT_NAME' is already installed." else log_info "Installing font '$FONT_NAME'..." FONT_URL="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Meslo.zip" FONT_DIR="$HOME/.local/share/fonts" TEMP_DIR=$(mktemp -d) wget -q -O "$TEMP_DIR/Meslo.zip" "$FONT_URL" \ && unzip -q "$TEMP_DIR/Meslo.zip" -d "$TEMP_DIR" \ && mkdir -p "$FONT_DIR/$FONT_NAME" \ && mv "$TEMP_DIR"/*.ttf "$FONT_DIR/$FONT_NAME/" \ && fc-cache -f "$FONT_DIR" check_status "Meslo Nerd Font installation" "non-critical" rm -rf "$TEMP_DIR" fi } install_repos() { log_info "----------------------------------------------------" log_info "Adding RPM Fusion repositories." log_info "----------------------------------------------------" sudo dnf install -y \ "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm" \ "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm" check_status "RPM Fusion repositories installation" "non-critical" } install_software() { log_info "----------------------------------------------------" log_info "Installing most used software." log_info "----------------------------------------------------" log_info "Installing DNF packages..." sudo dnf install -y "${DNF_PACKAGES[@]}" check_status "DNF packages installation" "non-critical" # Ensure flatpak is set up if ! command -v flatpak &> /dev/null; then log_info "Flatpak not found. Installing..." sudo dnf install -y flatpak fi log_info "Adding Flathub remote..." flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo check_status "Flathub remote add" "non-critical" log_info "Installing Flatpak packages..." # One transaction is much faster (single dependency resolution, shared # runtimes downloaded once). If it fails, retry one-by-one so a single # bad app ID doesn't block the rest. if flatpak install flathub -y "${FLATPAK_PACKAGES[@]}"; then log_info "Flatpak packages installation successful." else log_warn "Batch Flatpak install failed. Retrying apps individually..." for app in "${FLATPAK_PACKAGES[@]}"; do log_info "Installing $app..." flatpak install flathub -y "$app" check_status "Flatpak $app" "non-critical" done fi log_info "----------------------------------------------------" log_info "Installing Homebrew" log_info "----------------------------------------------------" # Install Homebrew (also detect it when it isn't on PATH yet) if command_exists brew || [ -x "/home/linuxbrew/.linuxbrew/bin/brew" ]; then log_info "Homebrew already installed." else log_info "Installing Homebrew..." # Download first so a failed download isn't reported as success. # NONINTERACTIVE=1 avoids prompts during brew install. BREW_INSTALLER=$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh) \ && NONINTERACTIVE=1 /bin/bash -c "$BREW_INSTALLER" check_status "Homebrew installation" "non-critical" # Add brew to path for the current session if [ -d "/home/linuxbrew/.linuxbrew/bin" ]; then eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" elif [ -d "$HOME/.linuxbrew/bin" ]; then eval "$($HOME/.linuxbrew/bin/brew shellenv)" fi fi } install_discord() { log_info "----------------------------------------------------" log_info "Installing Discord" log_info "----------------------------------------------------" if rpm -q discord > /dev/null 2>&1; then log_info "Discord already installed." else log_info "Downloading and installing Discord..." sudo dnf install -y "https://discord.com/api/download?platform=linux&format=rpm" check_status "Discord installation" "non-critical" fi } install_1password() { log_info "----------------------------------------------------" log_info "Installing 1Password" log_info "----------------------------------------------------" if rpm -q 1password > /dev/null 2>&1; then log_info "1Password already installed." else log_info "Downloading and installing 1Password..." sudo dnf install -y https://downloads.1password.com/linux/rpm/stable/x86_64/1password-latest.rpm check_status "1Password installation" "non-critical" fi } install_PIA() { log_info "----------------------------------------------------" log_info "Installing Private Internet Access (PIA)" log_info "----------------------------------------------------" if [ -d "/opt/piavpn" ]; then log_info "PIA is already installed." else log_info "Downloading and installing PIA..." PIA_URL=$(curl -s https://www.privateinternetaccess.com/download/linux-vpn | grep -oP 'https://[^\"]+pia-linux-[0-9\.\-]+\.run' | head -n 1) if [ -n "$PIA_URL" ]; then wget -qO /tmp/pia-linux.run "$PIA_URL" chmod +x /tmp/pia-linux.run # Run the installer as the current user, letting it prompt for sudo/pkexec if needed /tmp/pia-linux.run --quiet --accept rm /tmp/pia-linux.run check_status "PIA installation" "non-critical" else log_error "Failed to fetch PIA download URL." FAILED_STEPS+=("PIA installation") fi fi } install_cachyos() { log_info "----------------------------------------------------" log_info "Installing CachyOS kernel" log_info "----------------------------------------------------" # kernel-cachyos is built for x86-64-v3 CPUs. Ask before installing it on # an older CPU, where the new (default) boot entry may not start. if ! /lib64/ld-linux-x86-64.so.2 --help 2>/dev/null | grep 'x86-64-v3 (supported' > /dev/null; then log_warn "Your CPU does not appear to support x86-64-v3, which kernel-cachyos targets." read -p "Install the CachyOS kernel anyway? [y/N]: " reply if [[ ! "$reply" =~ ^[Yy]$ ]]; then log_info "Skipping CachyOS kernel." return fi fi sudo dnf copr enable -y bieszczaders/kernel-cachyos check_status "Enabling CachyOS Copr" "non-critical" sudo dnf install -y kernel-cachyos kernel-cachyos-devel-matched check_status "CachyOS Kernel installation" "non-critical" log_info "Installing CachyOS addons" sudo dnf copr enable -y bieszczaders/kernel-cachyos-addons check_status "Enabling CachyOS Addons Copr" "non-critical" sudo dnf swap -y zram-generator-defaults cachyos-settings sudo dracut -f sudo dnf -y install scx-scheds scx-tools scx-manager ananicy-cpp check_status "CachyOS addons installation" "non-critical" } install_p03() { log_info "----------------------------------------------------" log_info "Installing P03 kernel" log_info "----------------------------------------------------" # The default kernel-p03 build needs x86-64-v3 and won't boot on older # CPUs. Those get the x86-64-v2 build (kernel-p03-gcc) instead. if /lib64/ld-linux-x86-64.so.2 --help 2>/dev/null | grep 'x86-64-v3 (supported' > /dev/null; then P03_PACKAGE="kernel-p03" else log_warn "CPU lacks x86-64-v3 support. Installing the x86-64-v2 build (kernel-p03-gcc)." P03_PACKAGE="kernel-p03-gcc" fi sudo dnf copr enable -y catpieleaf/kernel-p03 check_status "Enabling P03 Copr" "non-critical" sudo dnf install -y "$P03_PACKAGE" check_status "P03 kernel installation" "non-critical" # P03 signs itself with a locally generated key. With Secure Boot on, that # key must be enrolled or the kernel won't boot. if mokutil --sb-state 2>/dev/null | grep -i 'SecureBoot enabled' > /dev/null; then log_info "Secure Boot is enabled. Enrolling the P03 signing key..." log_info "Pick a one-time password. On the next boot, choose 'Enroll MOK' and enter it." sudo mokutil --import /etc/kernel/certs/p03-kernel/mok.der check_status "P03 Secure Boot key enrollment" "non-critical" fi if rpm -q kernel-cachyos > /dev/null 2>&1; then log_warn "The CachyOS kernel is also installed. The newest kernel usually boots by default;" log_warn "pick the other one from the boot menu if needed." fi log_warn "P03 uses the PRJC scheduler, so sched_ext schedulers (scx-scheds) likely won't work on it." } install_linuxtoys() { log_info "----------------------------------------------------" log_info "Installing LinuxToys" log_info "----------------------------------------------------" curl -fsSL https://linux.toys/install.sh | bash check_status "LinuxToys installation" "non-critical" } install_shell_env() { log_info "----------------------------------------------------" log_info "Installing Shell Environment (Starship, FZF, Zoxide, MyBash)" log_info "----------------------------------------------------" # Install Starship if command_exists starship; then log_info "Starship already installed." else log_info "Installing Starship..." # -y skips the installer's confirmation prompt curl -sSL https://starship.rs/install.sh | sudo sh -s -- -y check_status "Starship installation" "non-critical" fi # Install FZF (Fallback if not installed via DNF) if ! command_exists fzf; then log_info "Installing FZF via git..." git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install --all fi # Install Zoxide if command_exists zoxide; then log_info "Zoxide already installed." else log_info "Installing Zoxide..." curl -sSL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh check_status "Zoxide installation" "non-critical" fi # Clone MyBash log_info "Cloning MyBash configuration..." GITPATH="$HOME/.local/share/mybash" if [ -d "$GITPATH" ]; then rm -rf "$GITPATH" fi mkdir -p "$HOME/.local/share" if git clone https://gitea.schantli.ca/jschantli/mybash.git "$GITPATH"; then log_info "MyBash cloned successfully." # Link Config log_info "Linking configuration files..." OLD_BASHRC="$HOME/.bashrc" # Check if .bashrc exists in the cloned repo if [ -f "$GITPATH/.bashrc" ]; then if [ -e "$OLD_BASHRC" ] && [ ! -e "$HOME/.bashrc.bak" ]; then log_info "Backing up .bashrc to .bashrc.bak..." mv "$OLD_BASHRC" "$HOME/.bashrc.bak" fi log_info "Linking new .bashrc..." ln -svf "$GITPATH/.bashrc" "$HOME/.bashrc" # Ensure Starship is initialized in the new .bashrc if ! grep -q "starship init bash" "$HOME/.bashrc"; then log_warn ".bashrc does not contain starship init. Appending it..." echo 'eval "$(starship init bash)"' >> "$HOME/.bashrc" fi else log_error "$GITPATH/.bashrc not found! Skipping .bashrc linking." fi # Check if starship.toml exists in the cloned repo if [ -f "$GITPATH/starship.toml" ]; then log_info "Linking starship.toml..." mkdir -p "$HOME/.config" ln -svf "$GITPATH/starship.toml" "$HOME/.config/starship.toml" else log_warn "$GITPATH/starship.toml not found! Skipping Starship config linking." fi # Check if config.jsonc exists in the cloned repo if [ -f "$GITPATH/config.jsonc" ]; then log_info "Linking config.jsonc..." mkdir -p "$HOME/.config/fastfetch/" ln -svf "$GITPATH/config.jsonc" "$HOME/.config/fastfetch/config.jsonc" else log_warn "$GITPATH/config.jsonc not found! Skipping fastfetch config linking." fi check_status "Linking config files" "non-critical" else log_error "Failed to clone MyBash repository. Skipping shell configuration." fi } install_topgrade() { log_info "----------------------------------------------------" log_info "Installing Topgrade" log_info "----------------------------------------------------" if command_exists topgrade; then log_info "Topgrade already installed." return fi sudo dnf copr enable -y lilay/topgrade check_status "Enabling Topgrade" "non-critical" sudo dnf install -y topgrade check_status "Topgrade installation" "non-critical" } install_gnome_extensions() { log_info "----------------------------------------------------" log_info "Installing GNOME extensions" log_info "----------------------------------------------------" # Ensure pipx is installed and path is set if ! command -v pipx &> /dev/null; then log_info "pipx not found. Installing..." sudo dnf install -y pipx pipx ensurepath fi # Install gnome-extensions-cli log_info "Installing gnome-extensions-cli..." pipx install gnome-extensions-cli --system-site-packages check_status "gnome-extensions-cli installation" "non-critical" # Define the tool path explicitly to avoid 'pipx run' overhead/warnings EXTENSION_TOOL="$HOME/.local/bin/gnome-extensions-cli" log_info "Installing GNOME extensions..." log_info "NOTE: A popup may appear asking you to confirm installation for each extension." log_info "If the extension is already installed, no popup will appear." for extension in "${GNOME_EXTENSIONS[@]}"; do log_info "Processing $extension..." # Install (using the direct binary path) "$EXTENSION_TOOL" install "$extension" check_status "GNOME Extension $extension" "non-critical" # Attempt to enable the extension (in case it's installed but disabled) log_info "Ensuring $extension is enabled..." gnome-extensions enable "$extension" # Prompt user to press Enter to continue read -p "Press Enter to continue (if a popup appeared, confirm it first)..." done } install_antigravity() { log_info "----------------------------------------------------" log_info "Installing Antigravity App, IDE, and CLI" log_info "----------------------------------------------------" log_info "Installing Antigravity CLI..." curl -fsSL https://antigravity.google/cli/install.sh | bash check_status "Antigravity CLI installation" "non-critical" log_info "Installing Antigravity App and IDE..." sudo dnf install -y python3 nautilus-python curl tar log_info "Fetching latest download URLs from Google..." AG_URL=$(curl -s https://antigravity.google/download | grep -oP 'https://[^"]+linux-x64/Antigravity\.tar\.gz' | head -n 1) AG_IDE_URL=$(curl -s https://antigravity.google/download | grep -oP 'https://[^"]+linux-x64/Antigravity%20IDE\.tar\.gz' | head -n 1) if [ -z "$AG_URL" ] || [ -z "$AG_IDE_URL" ]; then log_error "Failed to resolve Antigravity download URLs." FAILED_STEPS+=("Antigravity installation") return fi log_info "Installing Antigravity 2.0 App..." sudo rm -rf /opt/antigravity sudo mkdir -p /opt/antigravity if ! curl -sL "$AG_URL" | sudo tar -xz -C /opt/antigravity --strip-components=1; then log_error "Failed to extract Antigravity 2.0 App." FAILED_STEPS+=("Antigravity installation") return fi sudo ln -sfn /opt/antigravity/antigravity /usr/local/bin/antigravity sudo mkdir -p /usr/share/applications /usr/share/icons/hicolor/512x512/apps # Extract icon from app.asar if [ -f "/opt/antigravity/resources/app.asar" ]; then sudo python3 - /opt/antigravity/resources/app.asar /usr/share/icons/hicolor/512x512/apps/antigravity.png <<'PY' import json, struct, sys from pathlib import Path asar = Path(sys.argv[1]) out = Path(sys.argv[2]) with asar.open('rb') as f: f.read(4) header_size = struct.unpack(' /usr/share/applications/antigravity.desktop' < /usr/share/applications/antigravity-ide.desktop' </dev/null & while true; do show_menu case $choice in 1) run_all break ;; 2) run_all skip_kernel break ;; 3) optimize_dnf ;; 4) install_fonts ;; 5) install_repos ;; 6) install_software ;; 7) install_cachyos ;; 8) install_linuxtoys ;; 9) install_shell_env ;; 10) install_gnome_extensions ;; 11) install_discord ;; 12) install_1password ;; 13) install_PIA ;; 14) install_topgrade ;; 15) install_antigravity ;; 16) install_p03 ;; 0) log_info "Exiting..." print_summary exit 0 ;; *) log_error "Invalid option. Please try again." sleep 2 ;; esac if [ "$choice" != "1" ] && [ "$choice" != "2" ]; then read -p "Press Enter to return to the main menu..." fi done print_summary