diff --git a/lib/common.sh b/lib/common.sh index 22344c1..ae1a754 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -27,6 +27,8 @@ BOLD='\033[1m' RESET='\033[0m' # Disable colors if output is not a terminal +# (PURPLE/BLUE/BOLD defined for future use; shellcheck has no way to know that) +# shellcheck disable=SC2034 if [ ! -t 1 ]; then RED='' GREEN='' YELLOW='' CYAN='' PURPLE='' BLUE='' GRAY='' BOLD='' RESET='' fi diff --git a/setup.sh b/setup.sh index 64a3276..e56817d 100755 --- a/setup.sh +++ b/setup.sh @@ -5,231 +5,231 @@ # set -uo pipefail -#------------------------------------------ -# Color helpers (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"; } +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/common.sh +source "$SCRIPT_DIR/lib/common.sh" +# shellcheck source=lib/session.sh +source "$SCRIPT_DIR/lib/session.sh" #------------------------------------------ # Defaults + argument parsing # DEBUG_MODE must be initialised before anything reads it under set -u. #------------------------------------------ -pause_if_debug() { - if [[ "${DEBUG_MODE:-false}" == "true" ]]; then - echo - 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 "----------------------------------------------------------------" - echo - fi +DEBUG_MODE=false +ASSUME_YES=false +DE="" +TWM="" + +usage() { + cat <<'EOF' +setup.sh — unified Fedora configuration (Fedora 44+) + +Usage: + ./setup.sh interactive: pick a DE and a TWM + ./setup.sh --de=none --twm=niri non-interactive, explicit choices + ./setup.sh --debug pause after each section + +Options: + --de=NAME gnome | plasma | xfce | none + --twm=NAME chadwm | hyprland | mango | niri | none + -y, --yes never prompt (requires --de and --twm) + -d, --debug pause after each section instead of aborting on error + -h, --help show this help + +Valid DE/TWM combinations: + any DE + any TWM DE provides the session, TWM is added alongside it + none + any TWM TWM-only machine, SDDM is installed as the greeter + any DE + none plain desktop, no tiling window manager + none + none refused: that is not a graphical system + +Notes: + * chadwm is an X11 window manager; the other three are Wayland compositors. + * Run as your normal user, not root. sudo is used where needed. +EOF } -install_packages() { sudo dnf install -y "$@"; } -is_package_installed() { rpm -q "$@" &>/dev/null; } - -remove_packages() { - local packages_to_remove=() - for pkg in "$@"; do - if is_package_installed "$pkg"; then - packages_to_remove+=("$pkg") - fi - done - - if [ ${#packages_to_remove[@]} -gt 0 ]; then - color_cyan "Removing packages: ${packages_to_remove[*]}" - sudo dnf remove -y "${packages_to_remove[@]}" - fi -} +for arg in "$@"; do + case "$arg" in + -d|--debug) DEBUG_MODE=true ;; + -y|--yes) ASSUME_YES=true ;; + --de=*) DE="${arg#*=}" ;; + --twm=*) TWM="${arg#*=}" ;; + -h|--help) usage; exit 0 ;; + *) + err "Unknown argument: $arg" + printf "\n" + usage + exit 2 + ;; + esac +done +export DEBUG_MODE ASSUME_YES +if is_debug; then + set +e # walk through failures instead of dying on the first one +else + set -e +fi #------------------------------------------ # Preconditions #------------------------------------------ +# clear can fail without TERM (cron, piped install, ssh without a tty) and would +# abort the whole run under set -e. +clear 2>/dev/null || true -#------------------------------------------ -# Script Version 1.0 -#------------------------------------------ -clear # Wipe the terminal history view cleanly for the main menu execution +printf "\n" +color_yellow "----------------------------------------------------------------" +color_yellow "Fedora unified configuration" +color_yellow "----------------------------------------------------------------" -if [[ "$DEBUG_MODE" == "true" ]]; then - color_red "!!! DEBUG MODE ENABLED !!!" - color_red "The script will pause after each major section." - echo +if [[ ${EUID} -eq 0 ]]; then + err "Do not run this as root. Run it as your user; it calls sudo itself." + exit 1 fi -echo -color_yellow "----------------------------------------------------------------" -color_yellow "Script version 1.0" -color_yellow "----------------------------------------------------------------" -echo +if [[ "$ASSUME_YES" == "true" && ( -z "$DE" || -z "$TWM" ) ]]; then + err "--yes requires explicit --de and --twm, so nothing is guessed on an" + err "unattended run. Example:" + err " ./setup.sh --yes --de=none --twm=niri" + exit 2 +fi + +if is_debug; then + color_red "!!! DEBUG MODE ENABLED !!!" + color_red "The script will pause after each section." +fi #------------------------------------------ # OS gate — Fedora only, 44+ #------------------------------------------ -echo -color_yellow "----------------------------------------------------------------" -color_yellow "Start OS Detection" -color_yellow "----------------------------------------------------------------" -echo +step "OS detection" +rule -source /etc/os-release +# Overridable so the script can be tested on a non-Fedora host. +OS_RELEASE_FILE="${OS_RELEASE_FILE:-/etc/os-release}" +# shellcheck source=/dev/null +source "$OS_RELEASE_FILE" -OS_ID="${ID,,}" -OS_LIKE="${ID_LIKE:-}" -OS_LIKE="${OS_LIKE,,}" -OS_VERSION="${VERSION_ID:-unknown}" -OS_PRETTY="$PRETTY_NAME" -OS="" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OS_ID="${ID:-unknown}"; OS_ID="${OS_ID,,}" +OS_PRETTY="${PRETTY_NAME:-unknown}" -case "$OS_ID" in - fedora) OS="fedora" ;; - *) - if [[ "$OS_LIKE" == *"fedora"* || "$OS_LIKE" == *"rhel"* ]]; then - OS="fedora" - fi - ;; -esac - -if [[ -n "$OS" ]]; then - color_cyan "----------------------------------------------------------------" - color_cyan "Detected OS: $OS_PRETTY" - color_cyan "Version: $OS_VERSION" - color_cyan "----------------------------------------------------------------" -else - color_red "----------------------------------------------------------------" - color_red "ERROR: Unsupported or unknown Linux distribution." - color_red "Detected: ID=$OS_ID ID_LIKE=${OS_LIKE:-empty}" - color_red "----------------------------------------------------------------" +if [[ "$OS_ID" != "fedora" ]]; then + err "Unsupported distribution: $OS_PRETTY (ID=${OS_ID})" + err "This repo targets Fedora only. RHEL clones are refused on purpose:" + err "they ship no dnf5 config-manager and no RPM Fusion, so both the base" + err "setup and every module would fail half-way." exit 1 fi +FEDORA_REL=$(rpm -E %fedora) +if [[ "$FEDORA_REL" =~ ^[0-9]+$ ]] && (( FEDORA_REL < 44 )); then + err "$OS_PRETTY reports release ${FEDORA_REL}, but this setup targets Fedora 44+." + exit 1 +fi + +ok "Detected: $OS_PRETTY (release ${FEDORA_REL})" pause_if_debug + #------------------------------------------ # 1 Base settings #------------------------------------------ -echo -color_yellow "----------------------------------------------------------------" -color_yellow "1 Basic Settings" -color_yellow "----------------------------------------------------------------" -echo +step "1 Base settings" +rule -#------------------------------------------ -# 1.1 Ensure base is ready for installation -#------------------------------------------ if ! command -v curl >/dev/null 2>&1; then info "curl missing. Installing..." install_packages curl fi -pause_if_debug +# config-manager lives in dnf5-plugins; every repo step below depends on it. +if ! dnf config-manager --help >/dev/null 2>&1; then + info "dnf config-manager missing. Installing dnf5-plugins..." + install_packages dnf5-plugins +fi -#------------------------------------------ -# 1.2 Update and Enable Repos + Flatpak -#------------------------------------------ -color_cyan "Optimizing DNF for speed..." - -# 1. Increase parallel downloads (allows downloading multiple packages at once) -# 2. Set dnf to set YES as default instead of NO +info "Tuning DNF..." sudo dnf config-manager setopt max_parallel_downloads=20 sudo dnf config-manager setopt defaultyes=True -color_green "DNF optimized. Refreshing metadata..." +info "Refreshing metadata..." sudo dnf makecache --refresh -color_cyan "Enabling RPM Fusion repositories for Fedora $(rpm -E %fedora)..." +add_rpmfusion_repo +pause_if_debug -# Define the version variable for readability -FEDORA_VER=$(rpm -E %fedora) - -color_yellow "Checking for updates and sources..." -sudo dnf check-update || true - -# Check if flatpak is installed, if not, install it -if ! command -v flatpak &> /dev/null; then - color_yellow "Flatpak not found. Installing..." - sudo dnf install -y flatpak +if ! command -v flatpak >/dev/null 2>&1; then + info "Flatpak missing. Installing..." + install_packages flatpak fi +info "Ensuring Flathub is available (system-wide)..." +sudo flatpak remote-add --if-not-exists --system flathub https://dl.flathub.org/repo/flathub.flatpakrepo -color_cyan "Enabling Flathub..." -# Using --if-not-exists is good practice to prevent errors on re-runs -sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo - -sudo dnf install -y \ - "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${FEDORA_VER}.noarch.rpm" \ - "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${FEDORA_VER}.noarch.rpm" - -color_cyan "Enabling full Flathub support..." -flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo - -# Update the system -color_gray "Updating the system" -echohttps://git.ku-it.org/terrabeam/starburst.git +info "Updating the system..." sudo dnf update -y pause_if_debug -#------------------------------------------ -# Tiling Window Manager Selection -#------------------------------------------ -echo -color_yellow "----------------------------------------------------------------" -color_yellow "Tiling WM Selection" -color_yellow "----------------------------------------------------------------" -echo -while true; do - echo - color_gray "Select a tiling window manager:" - echo - echo " 1) CHADWM" - echo " 2) Hyprland" - echo " 3) MangoWC" - echo " 4) Niri" - echo " x) None (default)" - if ! read -t 15 -rp "Enter choice [1/2/3/4/x] (default: x): " choice; then - choice="x" +#------------------------------------------ +# 2 Menu helpers +#------------------------------------------ +# $1 = prompt, $2 = default answer, rest = valid answers +ask_choice() { + local prompt="$1" default="$2"; shift 2 + local answer + if [[ "$ASSUME_YES" == "true" ]]; then + echo "$default" + return 0 fi + while true; do + if ! read -t 60 -rp "$prompt (default: $default): " answer; then + printf "\n" + answer="" + fi + answer="${answer:-$default}" + answer="${answer,,}" + local v + for v in "$@"; do + if [[ "$answer" == "$v" ]]; then + echo "$answer" + return 0 + fi + done + err "Invalid option: '$answer'. Choose from: $*" + done +} - case "${choice,,}" in - 1) TWM="chadwm"; break ;; - 2) TWM="hyprland"; break ;; - 3) TWM="mango"; break ;; - 4) TWM="niri"; break ;; - x|"") TWM="none"; break ;; - *) echo "Invalid option. Please enter 1, 2, 3, 4 or x." ;; - esac -done - -echo -color_cyan "----------------------------------------------------------------" -color_cyan "Selected Tiling WM: $TWM" -color_cyan "----------------------------------------------------------------" - -pause_if_debug #------------------------------------------ -# 5. Tiling Window Manager installation +# 3 Desktop environment selection +#------------------------------------------ +if [[ -z "$DE" ]]; then + step "3 Desktop environment" + rule + printf "\n" + color_gray " This system is treated as a bare Fedora install — picking a DE" + color_gray " here is what gives you a graphical login." + printf "\n" + echo " 1) GNOME (gdm)" + echo " 2) Plasma (sddm)" + echo " 3) XFCE (lightdm)" + echo " 4) None (TWM only — SDDM will be installed by the TWM module)" + printf "\n" + choice=$(ask_choice "Select desktop environment [1/2/3/4]" "1" 1 2 3 4) + case "$choice" in + 1) DE="gnome" ;; + 2) DE="plasma" ;; + 3) DE="xfce" ;; + 4) DE="none" ;; + esac +fi + +case "$DE" in + gnome|plasma|xfce|none) ;; + *) err "Unknown --de value: '$DE' (gnome|plasma|xfce|none)"; exit 2 ;; +esac +pause_if_debug + +#------------------------------------------ +# 4 Tiling window manager selection #------------------------------------------ if [[ -z "$TWM" ]]; then step "4 Tiling window manager" @@ -252,33 +252,74 @@ if [[ -z "$TWM" ]]; then fi case "$TWM" in - chadwm|hyprland|mango|niri) - color_yellow "Installing $TWM" - SCRIPT_NAME="$SCRIPT_DIR/${TWM}.sh" - if [[ -f "$SCRIPT_NAME" ]]; then - color_cyan "Running $SCRIPT_NAME" - source "$SCRIPT_NAME" - else - color_red "Error: $SCRIPT_NAME not found!" - exit 1 - fi - ;; - none) - color_gray "No tiling window manager selected, skipping TWM installation." - ;; + niri|hyprland|mango|chadwm|none) ;; + *) err "Unknown --twm value: '$TWM' (niri|hyprland|mango|chadwm|none)"; exit 2 ;; esac +ok "Selection: DE=${DE} TWM=${TWM}" + +# A bare install with neither is not a graphical system. Refuse rather than +# leave the user with a machine that boots to a black screen. +if [[ "$DE" == "none" && "$TWM" == "none" ]]; then + err "DE=none with TWM=none would leave no graphical session at all." + err "Pick a desktop environment, a tiling window manager, or both." + exit 2 +fi pause_if_debug -echo -color_yellow "----------------------------------------------------------------" -color_yellow "End Detection" -color_yellow "----------------------------------------------------------------" +#------------------------------------------ +# 5 Dispatch +#------------------------------------------ +export DE TWM -color_green "Fedora setup complete." -color_red "The installation has finished. A reboot is recommended." -# Reboots are dangerous to automate; we use a shorter timeout and default to No -if read -t 10 -rp "Reboot now? [y/N]: " reboot_choice; then +run_module() { + local path="$1" + if [[ ! -f "$path" ]]; then + err "Module not found: $path" + return 1 + fi + info "Running $(basename "$path")" + printf "\n" + # Separate process: a module's own shell options and exit codes stay its own. + bash "$path" +} + +step "5 Installation" +rule + +if [[ "$DE" != "none" ]]; then + run_module "$SCRIPT_DIR/modules/de/${DE}.sh" + pause_if_debug +fi + +if [[ "$TWM" != "none" ]]; then + run_module "$SCRIPT_DIR/modules/twm/${TWM}.sh" + pause_if_debug +fi + +#------------------------------------------ +# 6 Done +#------------------------------------------ +step "6 Summary" +rule +ok "DE: $DE" +ok "TWM: $TWM" +DM=$(detect_display_manager) +ok "Display manager: ${DM:-none}" + +if [[ -z "$DM" ]]; then + warn "No display manager is active. You will boot to a text console." + warn "Install one, or run a TWM module which sets up SDDM." +fi + +printf "\n" +color_green "Fedora setup complete. A reboot is recommended." +if [[ "$ASSUME_YES" == "true" ]]; then + info "Unattended run: not rebooting." + exit 0 +fi + +if read -t 15 -rp "Reboot now? [y/N]: " reboot_choice; then case "${reboot_choice,,}" in y|yes) color_red "Rebooting now."