#!/usr/bin/env bash set -euo pipefail # --- Helper functions --- log() { echo -e "\033[1;34m[INFO]\033[0m $*"; } exists() { command -v "$1" >/dev/null 2>&1; } # --- Defined Paths --- PANAMA_PATH="$HOME/.local/share/Panama" echo -e "\n--- Installing Repositories ---" log "Installing RPM Fusion Free and Nonfree Repositories" 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 > /dev/null log "Enabling Fedora Cisco OpenH264 Repository" sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1 log "Installing RPM Fusion AppStream Metadata" sudo dnf update @core -y > /dev/null sudo dnf install -y rpmfusion-\*-appstream-data > /dev/null log "Installing Terra Repository" sudo dnf install -y --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release > /dev/null echo -e "\n--- Installing relevant packages ---" log "Updating all packages. This may take a while" sudo dnf update -y --refresh > /dev/null log "Updating core, multimedia, and sound-and-video groups" # A trailing `&& sync` here previously meant a failing groupupdate was exempt # from set -e (bash does not apply -e to the left side of a && list), so the # failure went unreported. Sync unconditionally on its own line instead. sudo dnf4 groupupdate -y 'core' 'multimedia' 'sound-and-video' \ --setop='install_weak_deps=False' \ --exclude='PackageKit-gstreamer-plugin' \ --allowerasing > /dev/null sync log "Swapping ffmpeg-free for ffmpeg" sudo dnf swap -y 'ffmpeg-free' 'ffmpeg' --allowerasing > /dev/null log "Swapping mesa-va-drivers for mesa-va-drivers-freeworld" sudo dnf swap -y mesa-va-drivers mesa-va-drivers-freeworld > /dev/null log "Upgrading Multimedia group with optional packages" sudo dnf4 group upgrade -y --with-optional Multimedia > /dev/null log "Installing GStreamer plugins (bad, good, base)" sudo dnf install -y gstreamer1-plugins-{bad-\*,good-\*,base} \ --exclude=gstreamer1-plugins-bad-free-devel > /dev/null # --- Install all initial packages --- PACKAGES_FILE="$PANAMA_PATH/setup/packages/initial-packages" if [[ -f "$PACKAGES_FILE" ]]; then INITIAL_PACKAGES=$(tr "\n" " " <"$PACKAGES_FILE") log "Installing Initial Packages" echo -e "Includes the following packages:" echo -e "$(<"$PACKAGES_FILE")" sudo dnf install -y $INITIAL_PACKAGES > /dev/null log "Initial packages installed!" else log "Package list was not in specified path: $PACKAGES_FILE" fi # --- Install Desktop Packages --- DESKTOP_FILE="$PANAMA_PATH/setup/packages/desktop-packages" if [[ -f "$DESKTOP_FILE" ]]; then DESKTOP_PACKAGES=$(tr "\n" " " <"$DESKTOP_FILE") log "Installing Desktop Packages" echo -e "Includes the following packages:" echo -e "$(<"$DESKTOP_FILE")" sudo dnf install -y $DESKTOP_PACKAGES > /dev/null log "Desktop packages installed!" else log "Package list was not in specified path: $DESKTOP_FILE" fi # --- Install Development Packages needed for Neovim --- DEV_FILE="$PANAMA_PATH/setup/packages/development-packages" if [[ -f "$DEV_FILE" ]]; then DEV_PACKAGES=$(tr "\n" " " <"$DEV_FILE") log "Installing Development Packages. Mostly for Neovim." echo -e "Includes the following packages:" echo -e "$(<"$DEV_FILE")" sudo dnf install -y $DEV_PACKAGES > /dev/null log "Development packages installed!" else log "Package list was not in specified path: $DEV_FILE" fi # --- Install the Hyprland desktop --- # Most of these live in the lionheartp/Hyprland COPR rather than Fedora proper. HYPR_FILE="$PANAMA_PATH/setup/packages/hyprland-packages" if [[ -f "$HYPR_FILE" ]]; then log "Enabling Hyprland COPR" sudo dnf copr enable -y lionheartp/Hyprland > /dev/null HYPR_PACKAGES=$(tr "\n" " " <"$HYPR_FILE") log "Installing Hyprland desktop packages" echo -e "Includes the following packages:" echo -e "$(<"$HYPR_FILE")" sudo dnf install -y --setopt=install_weak_deps=False $HYPR_PACKAGES > /dev/null log "Hyprland packages installed!" else log "Package list was not in specified path: $HYPR_FILE" fi # --- Applications no repository packages ------------------------------------- # # Everything else Panama installs comes from dnf or Flathub. These three do not # exist in either, so each is an explicit exception with a reason, and each is # skipped when already present so a re-run costs nothing. # # None of them pins a version. sunhat pinned URLs -- upscayl 2.11.5, LACT 0.5.4, # a fedora-40 RPM -- and every one of them was a 404 within a release cycle. An # installer that resolves "latest" keeps working; one that names a version rots. # # A failure here is logged and stepped over rather than aborting: this stage has # already installed the desktop by this point, and an unreachable third-party # host should not cost you that. # Bun: the JavaScript runtime and package manager. No RPM, no flatpak. if [[ -x "$HOME/.bun/bin/bun" ]]; then log "Bun already installed at \"$HOME/.bun/bin/bun\"" else log "Installing Bun via curl..." curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 || log "Bun install failed; skipping" fi # Claude Code: Anthropic's CLI. The official installer keeps itself updated # afterwards, so this runs once and then never needs to again. if command -v claude >/dev/null 2>&1; then log "Claude Code already installed at \"$(command -v claude)\"" else log "Installing Claude Code via the official installer..." curl -fsSL https://claude.ai/install.sh | bash > /dev/null 2>&1 || log "Claude Code install failed; skipping" fi # RustDesk: remote desktop. The flatpak cannot register the root-owned system # service that unattended access needs -- see panama-doctor's rustdesk check -- # so this takes the RPM. The download URL is resolved from the latest release # rather than written down, so it does not go stale. if rpm -q rustdesk >/dev/null 2>&1; then log "RustDesk already installed" else log "Resolving the latest RustDesk release..." rustdesk_url="$(curl -fsSL https://api.github.com/repos/rustdesk/rustdesk/releases/latest 2>/dev/null \ | jq -r '.assets[].browser_download_url | select(test("x86_64\\.rpm$")) | select(test("suse") | not)' \ | head -1)" if [[ -n "$rustdesk_url" ]]; then log "Installing RustDesk from $rustdesk_url" # The RPM ships rustdesk.service already enabled, which is what provides # unattended access; Panama deliberately does not start it a second time. sudo dnf install -y "$rustdesk_url" > /dev/null || log "RustDesk install failed; skipping" else log "Could not resolve a RustDesk release; skipping" fi fi # --- Install Flatpak Packages --- FLATPAK_FILE="$PANAMA_PATH/setup/packages/flatpak-packages" if [[ -f "$FLATPAK_FILE" ]]; then FLATPAK_PACKAGES=$(tr "\n" " " <"$FLATPAK_FILE") log "Adding Flathub remote" sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo > /dev/null log "Installing Flatpak Packages" echo -e "Includes the following packages:" echo -e "$(<"$FLATPAK_FILE")" sudo flatpak install -y flathub $FLATPAK_PACKAGES > /dev/null log "Flatpak packages installed!" else log "Package list was not in specified path: $FLATPAK_FILE" fi