# shellcheck shell=bash #------------------------------------------ # lib/common.sh — shared helpers # # Sourced by setup.sh and by every module. Modules source it directly, so a # module can be run standalone for testing: # DE=none TWM=niri bash modules/twm/niri.sh #------------------------------------------ # Guard against double-sourcing (setup.sh and a module can both pull it in) if [[ -n "${_SB_COMMON_LOADED:-}" ]]; then return 0 fi _SB_COMMON_LOADED=1 #------------------------------------------ # Colors (printf-safe, no tput) #------------------------------------------ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' PURPLE='\033[0;35m' BLUE='\033[0;34m' GRAY='\033[0;37m' BOLD='\033[1m' RESET='\033[0m' # Disable colors if output is not a terminal if [ ! -t 1 ]; then RED='' GREEN='' YELLOW='' CYAN='' PURPLE='' BLUE='' GRAY='' BOLD='' RESET='' fi color_yellow() { printf "%b%s%b\n" "$YELLOW" "$*" "$RESET"; } color_cyan() { printf "%b%s%b\n" "$CYAN" "$*" "$RESET"; } color_red() { printf "%b%s%b\n" "$RED" "$*" "$RESET"; } color_green() { printf "%b%s%b\n" "$GREEN" "$*" "$RESET"; } color_gray() { printf "%b%s%b\n" "$GRAY" "$*" "$RESET"; } #------------------------------------------ # Log levels #------------------------------------------ info() { color_cyan " $*"; } ok() { color_green " $*"; } warn() { color_yellow " $*"; } err() { color_red " $*" >&2; } step() { printf "\n"; color_yellow "$*"; } rule() { printf "%s\n" "----------------------------------------------------------------"; } #------------------------------------------ # Debug pausing #------------------------------------------ is_debug() { [[ "${DEBUG_MODE:-false}" == "true" ]]; } pause_if_debug() { if is_debug; then printf "\n" color_yellow "----------------------------------------------------------------" read -rp "Section complete. Press [Enter] to continue, or [q] to abort: " debug_choice if [[ "${debug_choice,,}" == "q" ]]; then color_red "Script aborted by user." exit 1 fi color_yellow "----------------------------------------------------------------" printf "\n" fi } #------------------------------------------ # Package helpers (dnf5) #------------------------------------------ install_packages() { # -y : no prompts # weak deps on : keep default behaviour, stated explicitly so it is greppable sudo dnf install -y --setopt=install_weak_deps=True "$@" } # Same, but a missing/renamed package warns instead of aborting the whole run. # Use for anything that is a nice-to-have rather than load-bearing. install_optional() { sudo dnf install -y --setopt=install_weak_deps=True "$@" \ || warn "Optional packages not installed: $*" } pkg_installed() { local p for p in "$@"; do rpm -q "$p" &>/dev/null || return 1 done return 0 } remove_packages() { local packages_to_remove=() for pkg in "$@"; do if pkg_installed "$pkg"; then packages_to_remove+=("$pkg") else color_gray " Package '$pkg' not installed. Skipping removal." fi done if [ ${#packages_to_remove[@]} -gt 0 ]; then info "Removing packages: ${packages_to_remove[*]}" sudo dnf remove -y "${packages_to_remove[@]}" fi } # dnf comps group. Fail loudly with the real group list rather than letting # dnf's generic "no match" scroll past. install_group() { local group="$1" if ! dnf group info "$group" >/dev/null 2>&1; then err "No such dnf group: @$group" err "Available groups on this system:" dnf group list --hidden 2>/dev/null | sed 's/^/ /' >&2 || true return 1 fi sudo dnf group install -y "$group" } # Try each candidate group/environment in turn; first one that exists wins. install_group_any() { local g for g in "$@"; do if dnf group info "$g" >/dev/null 2>&1; then info "Using dnf group: @$g" sudo dnf group install -y "$g" return 0 fi done err "None of these dnf groups exist: $*" dnf group list --hidden 2>/dev/null | sed 's/^/ /' >&2 || true return 1 } #------------------------------------------ # Third-party repos #------------------------------------------ add_terra_repo() { if pkg_installed terra-release; then ok "Terra repository already installed." return 0 fi info "Adding Terra repository (Fyra Labs)..." # Terra's own instructions use --nogpgcheck for the bootstrap package; # once terra-release is in place the repo itself is signature-checked. sudo dnf install -y --nogpgcheck \ --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' \ terra-release } add_rpmfusion_repo() { local rel rel=$(rpm -E %fedora) if pkg_installed rpmfusion-free-release rpmfusion-nonfree-release; then ok "RPM Fusion already installed." return 0 fi info "Enabling RPM Fusion (free + nonfree) for Fedora ${rel}..." sudo dnf install -y \ "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${rel}.noarch.rpm" \ "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${rel}.noarch.rpm" } #------------------------------------------ # Fonts #------------------------------------------ # Nerd Fonts are not in the Fedora repos, so pull the release zip from GitHub # into the user's font dir. Idempotent: skipped if the family is already there. install_nerd_font() { local family="${1:-JetBrainsMono}" local dest="${HOME}/.local/share/fonts/NerdFonts-${family}" local url="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/${family}.zip" if [[ -d "$dest" ]] && compgen -G "$dest/*.ttf" >/dev/null; then ok "Nerd Font ${family} already installed." return 0 fi info "Installing Nerd Font ${family}..." local tmp tmp=$(mktemp -d) if ! curl -fsSL -o "$tmp/${family}.zip" "$url"; then warn "Could not download ${family} Nerd Font. Skipping." rm -rf "$tmp" return 0 fi if ! command -v unzip >/dev/null 2>&1; then install_packages unzip fi mkdir -p "$dest" unzip -qo "$tmp/${family}.zip" -d "$dest" rm -rf "$tmp" fc-cache -f "$dest" >/dev/null 2>&1 || true ok "Nerd Font ${family} installed to $dest" }