YuKu's expanded always-section died at section 6: p7zip was RETIRED from Fedora 44 (replaced by the official '7zip' package) -> the compression line's transaction failed -> set -e killed the whole install after everything before it had already succeeded. Two changes: - p7zip -> 7zip in the Compression line (unace/unrar verified fine: they live in RPM Fusion, which base enables). - The always-loop now warns in red per failing category line and continues — same resilience contract as the advanced menu. One missing package can no longer abort a 30-minute install at the finish line. Harness: healthy run installs everything incl. 7zip/mpv/wget; a deliberately broken compression line produces the red Failed block, 'finished with failures' summary, exit 0, and later categories (mpv, wget...) still install.
615 lines
21 KiB
Bash
Executable File
615 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# setup.sh — unified Fedora configuration
|
|
# Target: bare/minimal Fedora 44 and newer
|
|
#
|
|
set -uo pipefail
|
|
|
|
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"
|
|
# shellcheck source=lib/ui.sh
|
|
source "$SCRIPT_DIR/lib/ui.sh"
|
|
# shellcheck source=lib/ui-block.sh
|
|
source "$SCRIPT_DIR/lib/ui-block.sh"
|
|
# never leave a block pulse loop running if the script is interrupted
|
|
trap 'ui_block_close' EXIT INT TERM
|
|
|
|
# Full dnf output from quiet transactions lands here (shown on failure).
|
|
export SETUP_DNF_LOG="${SETUP_DNF_LOG:-/tmp/fedora-setup-dnf-$(id -u).log}"
|
|
: >> "$SETUP_DNF_LOG"
|
|
|
|
#------------------------------------------
|
|
# Defaults + argument parsing
|
|
# DEBUG_MODE must be initialised before anything reads it under set -u.
|
|
#------------------------------------------
|
|
DEBUG_MODE=false
|
|
ASSUME_YES=false
|
|
DE=""
|
|
TWM=""
|
|
SELINUX_OPT=""
|
|
SNAPPER_OPT=""
|
|
DOTFILES_OPT=""
|
|
|
|
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
|
|
--selinux=X keep | disabled (asked, default keep)
|
|
--snapper=X yes | no (automatic on btrfs; use 'no' to opt out)
|
|
--dotfiles=X yes | no (clone personal dotfiles, default no)
|
|
-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
|
|
}
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
-d|--debug) DEBUG_MODE=true ;;
|
|
-y|--yes) ASSUME_YES=true ;;
|
|
--de=*) DE="${arg#*=}" ;;
|
|
--selinux=*) SELINUX_OPT="${arg#*=}" ;;
|
|
--snapper=*) SNAPPER_OPT="${arg#*=}" ;;
|
|
--dotfiles=*) DOTFILES_OPT="${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
|
|
|
|
printf "\n"
|
|
color_yellow "----------------------------------------------------------------"
|
|
color_yellow "Fedora unified configuration"
|
|
color_yellow "----------------------------------------------------------------"
|
|
|
|
if [[ ${EUID} -eq 0 ]]; then
|
|
err "Do not run this as root. Run it as your user; it calls sudo itself."
|
|
exit 1
|
|
fi
|
|
|
|
# Ask for the sudo password ONCE, up front, on a clean screen. Otherwise the
|
|
# first sudo call mid-run prints its prompt straight into the live progress
|
|
# line ("Installing (optional): gum[sudo] password for yuri:" — VM-proven).
|
|
# The timestamp refreshes on every subsequent sudo use, so one prompt covers
|
|
# the whole run.
|
|
info "Requesting sudo credentials (asked once for the whole run)..."
|
|
sudo -v
|
|
|
|
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+
|
|
#------------------------------------------
|
|
step "OS detection"
|
|
rule
|
|
|
|
# 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:-unknown}"; OS_ID="${OS_ID,,}"
|
|
OS_PRETTY="${PRETTY_NAME:-unknown}"
|
|
|
|
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
|
|
#------------------------------------------
|
|
step "1 Base settings"
|
|
rule
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
info "curl missing. Installing..."
|
|
install_packages curl
|
|
fi
|
|
|
|
# gum drives the styled menus/spinners when a TTY is present; the script's
|
|
# plain-text fallback means this is an enhancement, not a dependency.
|
|
if ! pkg_installed gum; then
|
|
install_optional gum
|
|
fi
|
|
|
|
# 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
|
|
|
|
info "Tuning DNF..."
|
|
sudo dnf config-manager setopt max_parallel_downloads=20
|
|
sudo dnf config-manager setopt defaultyes=True
|
|
|
|
info "Refreshing metadata..."
|
|
sudo dnf makecache --refresh
|
|
|
|
add_rpmfusion_repo
|
|
# Terra up front: it carries mangowc (noctalia moved into Fedora proper in
|
|
# F44). Idempotent; also kept for the standalone-mango-module case.
|
|
add_terra_repo
|
|
pause_if_debug
|
|
|
|
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
|
|
|
|
# Update the system (spinner + one-line summary; full output in $SETUP_DNF_LOG)
|
|
ui_quiet_run "Updating the system" sudo dnf update -y
|
|
pause_if_debug
|
|
|
|
#------------------------------------------
|
|
# 2 Menu helpers
|
|
#------------------------------------------
|
|
# $1 = prompt, $2 = default answer, rest = valid answers
|
|
#------------------------------------------
|
|
# 3 System settings
|
|
#------------------------------------------
|
|
SELINUX_DISABLED=false
|
|
BTRFS_SNAPPER=false
|
|
|
|
if [[ -z "$SELINUX_OPT" ]]; then
|
|
step "3 System settings"
|
|
rule
|
|
printf "\n"
|
|
printf "\n"
|
|
choice=$(ui_confirm "Disable SELinux permanently? (not recommended)" "no")
|
|
[[ "$choice" == "yes" ]] && SELINUX_DISABLED=true
|
|
printf "\n"
|
|
if findmnt -n -o FSTYPE / 2>/dev/null | grep -q btrfs; then
|
|
ok "Root filesystem is btrfs: snapshots + zstd compression will be set up."
|
|
BTRFS_SNAPPER=true
|
|
else
|
|
info "Root filesystem is not btrfs; skipping snapshot setup."
|
|
fi
|
|
fi
|
|
|
|
case "${SELINUX_OPT:-}" in
|
|
disabled) SELINUX_DISABLED=true ;;
|
|
keep|"") : ;;
|
|
*) err "Unknown --selinux value: '$SELINUX_OPT' (keep|disabled)"; exit 2 ;;
|
|
esac
|
|
case "${SNAPPER_OPT:-}" in
|
|
yes) BTRFS_SNAPPER=true ;;
|
|
no) BTRFS_SNAPPER=false ;; # explicit flag beats the interactive default
|
|
""|keep) : ;;
|
|
*) err "Unknown --snapper value: '$SNAPPER_OPT' (yes|no)"; exit 2 ;;
|
|
esac
|
|
|
|
DOTFILES=false
|
|
if [[ -z "$DOTFILES_OPT" ]]; then
|
|
printf "\n"
|
|
choice=$(ui_confirm "Clone personal dotfiles (fedora-dots) into \$HOME?" "no")
|
|
[[ "$choice" == "yes" ]] && DOTFILES=true
|
|
else
|
|
case "$DOTFILES_OPT" in
|
|
yes) DOTFILES=true ;;
|
|
no) : ;;
|
|
*) err "Unknown --dotfiles value: '$DOTFILES_OPT' (yes|no)"; exit 2 ;;
|
|
esac
|
|
fi
|
|
pause_if_debug
|
|
|
|
#------------------------------------------
|
|
# 4 Desktop environment selection
|
|
#------------------------------------------
|
|
if [[ -z "$DE" ]]; then
|
|
step "4 Desktop environment"
|
|
rule
|
|
color_gray "Bare Fedora install: picking a DE is what gives you a graphical login."
|
|
choice=$(ui_choose "Desktop environment" "Select desktop environment:" \
|
|
"None (TWM only, SDDM via TWM module)" \
|
|
"GNOME (gdm)" "Plasma (sddm)" "XFCE (sddm)" "None (TWM only, SDDM via TWM module)")
|
|
case "$choice" in
|
|
GNOME*) DE="gnome" ;;
|
|
Plasma*) DE="plasma" ;;
|
|
XFCE*) DE="xfce" ;;
|
|
*) 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
|
|
|
|
#------------------------------------------
|
|
# 5 Tiling window manager selection
|
|
#------------------------------------------
|
|
if [[ -z "$TWM" ]]; then
|
|
step "5 Tiling window manager"
|
|
rule
|
|
choice=$(ui_choose "Tiling window manager" "Select a tiling window manager:" \
|
|
"None" \
|
|
"Niri (Wayland, scrollable)" "Hyprland (Wayland, dynamic)" \
|
|
"MangoWC (Wayland, Terra repo)" "chadwm (X11, source build)" "None")
|
|
case "$choice" in
|
|
Niri*) TWM="niri" ;;
|
|
Hyprland*) TWM="hyprland" ;;
|
|
MangoWC*) TWM="mango" ;;
|
|
chadwm*) TWM="chadwm" ;;
|
|
*) TWM="none" ;;
|
|
esac
|
|
fi
|
|
|
|
case "$TWM" in
|
|
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
|
|
|
|
#------------------------------------------
|
|
# 5 Dispatch
|
|
#------------------------------------------
|
|
export DE TWM
|
|
|
|
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 [[ "$SELINUX_DISABLED" == "true" ]]; then
|
|
disable_selinux
|
|
pause_if_debug
|
|
fi
|
|
|
|
if [[ "$BTRFS_SNAPPER" == "true" ]]; then
|
|
ensure_btrfs_compression
|
|
setup_snapper
|
|
pause_if_debug
|
|
fi
|
|
|
|
if [[ "$DOTFILES" == "true" ]]; then
|
|
run_module "$SCRIPT_DIR/modules/user/dotfiles.sh"
|
|
pause_if_debug
|
|
fi
|
|
|
|
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
|
|
#------------------------------------------
|
|
|
|
#------------------------------------------
|
|
# 6 Always-installed packages
|
|
#------------------------------------------
|
|
# Add a category here and it is installed on every run, no questions asked.
|
|
# Keep each category one line so diffs stay readable.
|
|
ALWAYS_PACKAGES=(
|
|
# Terminals
|
|
"ghostty ghostty-bash-completion zsh zsh-syntax-highlighting"
|
|
# Utilities
|
|
"fastfetch bash-completion hwinfo lshw duf chrony man-db tree xdg-user-dirs rsync bat hddtemp powertop inxi acpi plocate nm-connection-editor"
|
|
"python3-pylint qt6-qtsvg qt6-qtvirtualkeyboard qt6-qtmultimedia arandr catfish galculator NetworkManager network-manager-applet"
|
|
"numlockx pipewire pipewire-alsa pipewire-pulse wireplumber pavucontrol playerctl gparted"
|
|
"system-config-printer system-config-printer-applet system-config-printer-libs ripgrep meld btop htop"
|
|
# File Managers
|
|
"thunar thunar-archive-plugin thunar-volman baobab gvfs-smb squashfs-tools"
|
|
# Internet
|
|
"wget"
|
|
# Compression tools
|
|
# (p7zip was RETIRED in Fedora 44; '7zip' is the official replacement)
|
|
"zip gzip 7zip unace unrar xarchiver"
|
|
# Office and Editors
|
|
"libreoffice"
|
|
# Multimedia
|
|
"mpv"
|
|
)
|
|
|
|
step "6 Always-installed packages"
|
|
rule
|
|
|
|
always_failed=""
|
|
for category in "${ALWAYS_PACKAGES[@]}"; do
|
|
# shellcheck disable=SC2086
|
|
if ! install_packages $category; then
|
|
color_red " Failed: $category"
|
|
color_red " (a package in this line has no match — the rest of the install continues)"
|
|
always_failed="yes"
|
|
pause_if_debug
|
|
fi
|
|
done
|
|
if [[ -z "$always_failed" ]]; then
|
|
ok "Always-packages installed."
|
|
else
|
|
warn "Always-packages finished with failures (see red lines above)."
|
|
fi
|
|
pause_if_debug
|
|
|
|
#------------------------------------------
|
|
# 7 Advanced: optional tool selection (menu-driven)
|
|
#------------------------------------------
|
|
# Add categories/apps by editing these parallel arrays:
|
|
# ADV_CATEGORIES : display order
|
|
# ADV_<NAME> : "pkg1 pkg2 ..." for each category (dots/minus -> underscore)
|
|
ADV_CATEGORIES=(
|
|
"Virtualisation"
|
|
"Browsers"
|
|
)
|
|
# shellcheck disable=SC2034 # arrays are read via nameref from ADV_CATEGORIES
|
|
ADV_Virtualisation=(
|
|
"qemu-kvm:QEMU/KVM (full virt stack):qemu-kvm libvirt virt-install virt-manager virt-viewer edk2-ovmf swtpm guestfs-tools libguestfs-tools"
|
|
"virtualbox:VirtualBox (Oracle):VirtualBox kernel-devel kernel-headers dkms akmod-VirtualBox"
|
|
)
|
|
# shellcheck disable=SC2034 # arrays are read via nameref from ADV_CATEGORIES
|
|
ADV_Browsers=(
|
|
"firefox:Firefox:firefox"
|
|
"chromium:Chromium:chromium"
|
|
"google-chrome:Google Chrome (adds repo):google-chrome-stable"
|
|
"brave:Brave (adds repo):brave-browser"
|
|
)
|
|
|
|
run_advanced_menu() {
|
|
local done_any=false
|
|
while true; do
|
|
local cat
|
|
cat=$(ui_choose "Optional tools" "Select a category (last item exits):" \
|
|
"Done with optional tools" \
|
|
"${ADV_CATEGORIES[@]}" "Done with optional tools")
|
|
[[ "$cat" == "Done with optional tools" ]] && break
|
|
|
|
# apps for this category — nameref, because ${!var} on an array name
|
|
# would only give element 0 (that bug showed in testing: Browsers
|
|
# listed exactly one entry)
|
|
local var="ADV_${cat//[ .-]/_}"
|
|
local -n entries_ref="$var"
|
|
local -a entries
|
|
# shellcheck disable=SC2206
|
|
entries=("${entries_ref[@]}")
|
|
|
|
# Labels for the picker (gum multi-select or numbered fallback).
|
|
# Entry format: id:Label:packages...
|
|
local entry rest lbl
|
|
local -a labels=()
|
|
for entry in "${entries[@]}"; do
|
|
rest="${entry#*:}"
|
|
labels+=("${rest%%:*}")
|
|
done
|
|
|
|
# ui_choose_multi prints the chosen labels (one per line).
|
|
local sel picked
|
|
sel=$(ui_choose_multi "$cat" "Install which? (multi-select, none = back):" "${labels[@]}")
|
|
[[ -z "$sel" ]] && continue
|
|
|
|
# Resolve each picked label to its OWN package list, then install
|
|
# PER ITEM: one failing package used to kill the whole multi-select
|
|
# transaction (VM: brave's no-match took chromium down with it).
|
|
# Repo wiring inside install_category_pkgs runs per item; failures
|
|
# warn in red and the remaining picks still install.
|
|
local picked lbl it
|
|
while IFS= read -r picked; do
|
|
[[ -z "$picked" ]] && continue
|
|
for entry in "${entries[@]}"; do
|
|
rest="${entry#*:}"
|
|
lbl="${rest%%:*}"
|
|
[[ "$lbl" == "$picked" ]] || continue
|
|
rest="${rest#*:}" # drop id and label -> pkgs
|
|
# word-split: quoting $rest whole makes dnf look for one
|
|
# package named "qemu-kvm libvirt ..." (seen on the VM)
|
|
# shellcheck disable=SC2206
|
|
local -a item_pkgs=(${rest})
|
|
step "Installing from $cat: ${item_pkgs[*]}"
|
|
if install_category_pkgs "$cat" "${item_pkgs[@]}"; then
|
|
done_any=true
|
|
else
|
|
color_red " Installation failed: $lbl"
|
|
color_red " (dnf reported no match / transaction failure — see output above)"
|
|
warn " Continuing with the remaining picks; the system is otherwise unchanged."
|
|
pause_if_debug
|
|
fi
|
|
done
|
|
done <<< "$sel"
|
|
done
|
|
# not `&& ok` — a false done_any would return 1 and kill the script under set -e
|
|
if [[ "$done_any" == "true" ]]; then
|
|
ok "Optional tools installed."
|
|
fi
|
|
}
|
|
|
|
# Per-category pre-repo wiring. Add third-party repos here when you add an
|
|
# app that needs one; packages alone stay generic.
|
|
install_category_pkgs() {
|
|
local cat="$1"; shift
|
|
case "$cat" in
|
|
Virtualisation)
|
|
# qemu:///system without password prompts: libvirt group + enabled
|
|
# daemon. Only when the picked packages actually use libvirt.
|
|
local wants_libvirt=false item
|
|
for item in "$@"; do
|
|
[[ "$item" == *libvirt* || "$item" == *virt-manager* || "$item" == *virt-install* ]] && wants_libvirt=true
|
|
done
|
|
if $wants_libvirt; then
|
|
local real_user="${SUDO_USER:-$USER}"
|
|
info "Enabling libvirtd and adding $real_user to the libvirt group..."
|
|
sudo systemctl enable --now libvirtd 2>/dev/null || true
|
|
sudo usermod -aG libvirt "$real_user" || true
|
|
warn "libvirt group membership applies after re-login."
|
|
fi
|
|
;;
|
|
Browsers)
|
|
local needs_chrome=false needs_brave=false item
|
|
for item in "$@"; do
|
|
[[ "$item" == *google-chrome* ]] && needs_chrome=true
|
|
[[ "$item" == *brave* ]] && needs_brave=true
|
|
done
|
|
if $needs_chrome && ! pkg_installed google-chrome-stable; then
|
|
info "Adding Google Chrome repo..."
|
|
sudo dnf install -y fedora-workstation-repositories 2>/dev/null || true
|
|
sudo dnf config-manager setopt google-chrome.enabled=1 2>/dev/null || \
|
|
sudo dnf install -y "https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm"
|
|
fi
|
|
if $needs_brave && ! pkg_installed brave-browser; then
|
|
# dnf5 (Fedora 41+) has no 'config-manager add' subcommand —
|
|
# the VM showed 'Unknown argument "add"', so the repo never
|
|
# landed and brave-browser had no match. Correct form per
|
|
# Brave's own F41+ docs: addrepo. The .repo carries the gpgkey,
|
|
# so no separate rpm --import is needed.
|
|
if [[ -f /etc/yum.repos.d/brave-browser.repo ]]; then
|
|
ok "Brave repo already present."
|
|
else
|
|
info "Adding Brave repo (dnf5 addrepo)..."
|
|
sudo dnf config-manager addrepo \
|
|
--from-repofile=https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
# Resilient install: report failure but return non-zero instead of
|
|
# aborting under set -e; the caller turns it into a red warning.
|
|
install_packages "$@" || return 1
|
|
return 0
|
|
}
|
|
|
|
# Interactive only: an unattended --yes run must not hang on this menu.
|
|
# Skip silently there; use --de/--twm/--dotfiles for everything unattended.
|
|
if [[ "$ASSUME_YES" != "true" ]]; then
|
|
run_advanced_menu
|
|
fi
|
|
pause_if_debug
|
|
|
|
step "8 Summary"
|
|
rule
|
|
|
|
# Post-install wiring that must see the FINAL package state. The libvirt
|
|
# group add earlier (menu/module time) can miss packages installed later in
|
|
# the same run — this is the authoritative pass (VM-proven: a fresh run left
|
|
# the user out of the group when virt-manager landed after the check).
|
|
ensure_libvirt_group() {
|
|
# shellcheck source=lib/common.sh
|
|
source "$SCRIPT_DIR/lib/common.sh" 2>/dev/null || true
|
|
if pkg_installed_any libvirt libvirt-daemon libvirt-daemon-driver-qemu virt-manager \
|
|
|| [[ -x /usr/sbin/libvirtd ]]; then
|
|
local real_user="${SUDO_USER:-$USER}"
|
|
if ! id -nG "$real_user" 2>/dev/null | grep -qw libvirt; then
|
|
info "Adding $real_user to the libvirt group (re-login to apply)..."
|
|
sudo usermod -aG libvirt "$real_user" || true
|
|
warn "Group membership applies after re-login — do it before using virt-manager."
|
|
fi
|
|
fi
|
|
}
|
|
ensure_libvirt_group
|
|
ok "DE: $DE"
|
|
ok "TWM: $TWM"
|
|
ok "SELinux: $([[ "$SELINUX_DISABLED" == "true" ]] && echo disabled || echo keeping)"
|
|
ok "Btrfs snapshots: $([[ "$BTRFS_SNAPPER" == "true" ]] && echo yes || echo skipped)"
|
|
ok "Dotfiles: $([[ "$DOTFILES" == "true" ]] && echo clone || echo no)"
|
|
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
|
|
|
|
reboot_choice=$(ui_confirm "Reboot now?" "no")
|
|
if [[ "$reboot_choice" == "yes" ]]; then
|
|
color_red "Rebooting now."
|
|
sudo reboot
|
|
else
|
|
color_yellow "Skipping reboot."
|
|
exit 0
|
|
fi
|