Compare commits

..
1 Commits
6 changed files with 66 additions and 984 deletions
-37
View File
@@ -34,8 +34,6 @@ lib/common.sh colours, logging, dnf helpers, rpmfusion/terra, nerd f
lib/session.sh display-manager detection/switching, session entries lib/session.sh display-manager detection/switching, session entries
modules/de/{gnome,plasma,xfce}.sh modules/de/{gnome,plasma,xfce}.sh
modules/twm/{niri,hyprland,mango,chadwm}.sh modules/twm/{niri,hyprland,mango,chadwm}.sh
modules/user/dotfiles.sh bare-repo dotfiles checkout
modules/user/nvidia.sh GPU-detected NVIDIA driver install
``` ```
Modules are **separate processes**, not sourced into `setup.sh`. That means a Modules are **separate processes**, not sourced into `setup.sh`. That means a
@@ -77,41 +75,6 @@ Each module pulls in `lib/` by relative path, so it works both ways.
`chadwm` is the only X11 entry in the menu. The other three are Wayland `chadwm` is the only X11 entry in the menu. The other three are Wayland
compositors — do not expect the same config story from it. compositors — do not expect the same config story from it.
## GPU drivers (NVIDIA)
`modules/user/nvidia.sh` runs on **detected hardware**, no question asked: the
card in the machine decides what is installed. Opt out with `--nvidia=no`
(e.g. to stay on nouveau).
Branch selection comes from the chip family in the name `lspci` prints, because
the numeric device-id ranges overlap between families and a wrong guess installs
a module the card cannot use:
| Chip family in the name | Driver branch | Packages |
|---|---|---|
| `GB`/`AD`/`GA`/`TU` (Turing and newer) | current | `akmod-nvidia` + `xorg-x11-drv-nvidia-cuda` |
| `GV`/`GP`/`GM` (Volta, Pascal, Maxwell) | 580xx | `akmod-nvidia-580xx` + `xorg-x11-drv-nvidia-580xx{,-cuda}` |
| `GK` (Kepler) | 470xx | `akmod-nvidia-470xx` + `xorg-x11-drv-nvidia-470xx{,-cuda}` |
| `GF` (Fermi) | 390xx | `akmod-nvidia-390xx` + `xorg-x11-drv-nvidia-390xx{,-cuda}` |
| pre-Fermi (`G92`, `GT200`, ...) | none | refused: 340xx is EOL and needs a longterm kernel |
If the name cannot be parsed, the module asks for the branch instead of guessing.
**Secure Boot.** With enforcement on, the akmods key is generated and imported,
and the module then *stops before installing the driver* — installing an
unsigned module while Secure Boot enforces is the documented way to get a black
screen, because the driver package blacklists nouveau as it lands. Enrol the key
in MokManager at the next boot, then run the setup again. With Secure Boot off
or in **Setup mode**, no signing happens at all: shim skips MokManager when
enforcement is off, so an import request would just sit in the efivars.
After the transaction the module waits (bounded, 5 min) for the akmod to build
so you cannot reboot into a driverless boot, runs `dnf mark user` on the akmod
(so `dnf autoremove` cannot take it), and offers VA-API decode + vulkan as an
opt-in extra. Nothing here sets `nvidia-drm.modeset=1`: RPM Fusion's kernel
package already enables KMS, and the flag now conflicts with Fedora's simpledrm
early-boot patch.
## Display manager policy ## Display manager policy
* A **DE module** leaves the DE's own greeter alone (GNOME→gdm, Plasma→sddm, * A **DE module** leaves the DE's own greeter alone (GNOME→gdm, Plasma→sddm,
+25 -103
View File
@@ -101,109 +101,43 @@ ui_quiet_run() {
before=$(wc -l < "$log" 2>/dev/null || echo 0) before=$(wc -l < "$log" 2>/dev/null || echo 0)
# ---- status BLOCK (TTY-safe: a background-coloured SPACE, never a glyph) -- # Child writes to the log; we draw a LIVE line: spinner frame + the package
# busy: blue block that breathes (bg-colour ramp) + current dnf action # dnf is currently working on. gum spin was tried here first but rendered
# ok : solid green block + package count # as a frozen block on the real VM ("looks like it hangs") — this variant
# fail: solid RED block (spec change from the X, per YuKu) # animates independently of gum and shows real progress, not just motion.
# Colour-depth aware: 256 -> ramp, 16 -> blue/bright-blue, none -> ASCII.
local depth=0 nc
nc=$(tput colors 2>/dev/null || echo 0)
case "$nc" in ''|*[!0-9]*) nc=0 ;; esac
if (( nc >= 256 )); then depth=256
elif (( nc >= 8 )); then depth=16
fi
# The bare Linux VT is 16-color VGA: 48;5;N codes are ignored there, and
# its blue/bright-blue backgrounds render nearly identically (VM-proven:
# "solid blue, no breathing" on the console). Cap to the 16-color path.
case "${TERM:-}" in linux*|console*) depth=16 ;; esac
# Per-depth frame interval: the console colour-pulse runs slower (a fast
# blue/grey flip reads as flicker — YuKu asked for half-to-third speed);
# the 256-colour breathe ramp keeps its 50ms.
local tick=0.05
(( depth == 16 )) && tick=0.15
local -a bg=()
case "$depth" in
# Deep-amplitude breathe: dark navy (17) to BRIGHT blue (45). Adjacent
# shades must differ enough to survive flicker fusion — a subtle ramp
# at 20fps reads as a solid block (VM-proven).
256) bg=( "48;5;17" "48;5;19" "48;5;21" "48;5;27" "48;5;33" "48;5;39" "48;5;45" "48;5;39" "48;5;33" "48;5;27" "48;5;21" "48;5;19" ) ;;
# Colour pulse with CONSTANT layout: blue <-> dark grey (bright black).
# Never remove the block on a frame — a missing block shifted the
# whole line by one char on the console (VM-proven).
16) bg=( "44" "44" "100" "100" ) ;;
esac
local -a ch=( "|" "/" "-" "\\" )
"$@" >>"$log" 2>&1 & "$@" >>"$log" 2>&1 &
local pid=$! local pid=$!
# ASCII frames ONLY: braille/em-dash are multibyte UTF-8 and rendered as
# garbage letters when the session locale is not UTF-8 (VM-proven: the
# spinner appeared as changing random chars, not as a spinner).
local frames=( '|' '/' '-' '\\' )
local f=0 cur local f=0 cur
while kill -0 "$pid" 2>/dev/null; do while kill -0 "$pid" 2>/dev/null; do
# Match dnf ('Installing: pkg') AND flatpak ('Installing... id', a
# Unicode ellipsis) progress lines; sanitize to printable ASCII so a
# non-UTF8 console never sees garbage bytes in the live line.
cur=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null \ cur=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null \
| grep -aE '^Installing|^Upgrading|^Downgrading|^Removing|^Verifying|^Preparing|^Cleanup' \ | grep -E '^(Installing|Upgrading|Downgrading|Removing|Verifying|Preparing|Cleanup):' \
| tail -n 1 \ | tail -n 1 || true)
| tr -cd '\11\12\40-\176' || true)
# Compose ONCE, then clamp to the terminal width: a live line longer
# than the screen wraps on every redraw and floods the screen with
# duplicate partial lines (VM-proven on the 80-col TTY).
local cols line
cols=$(tput cols 2>/dev/null)
[[ "$cols" =~ ^[0-9]+$ ]] || cols=80
if [[ -n "$cur" ]]; then if [[ -n "$cur" ]]; then
line="$msg: $cur" printf '\r %s %s: %s' "${frames[$f]}" "$msg" "$cur" >&2
else else
line="$msg" printf '\r %s %s' "${frames[$f]}" "$msg" >&2
fi fi
if (( ${#line} > cols - 2 )); then f=$(( (f + 1) % ${#frames[@]} ))
line="${line:0:cols-4}.." sleep 0.15
fi
if (( depth >= 16 )); then
local code="${bg[$(( f % ${#bg[@]} ))]}"
printf '\r\033[%sm \033[0m %s' "$code" "$line" >&2
else
printf '\r %s %s' "${ch[$(( f % ${#ch[@]} ))]}" "$line" >&2
fi
f=$(( f + 1 ))
sleep "$tick"
done done
local rc=0 local rc=0
wait "$pid" || rc=$? wait "$pid" || rc=$?
# ANSI erase-line: wipes the live line without leaving trailing spaces # wipe the progress line before printing the result
# (a 100-space wipe left a visible gap when the summary was shorter) printf '\r%*s\r' 100 '' >&2
printf '\r\033[K' >&2
if (( rc == 0 )); then if (( rc == 0 )); then
n=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null | grep -ac '^Installing' || true) n=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null | grep -c '^Installing:' || true)
local count_txt="" local plural="s"
if (( n > 0 )); then (( n == 1 )) && plural=""
local plural="s" ok "$msg (${n} package${plural})"
(( n == 1 )) && plural=""
count_txt=" (${n} package${plural})"
fi
# n == 0 means nothing new was installed (idempotent re-run /
# deps already present): print the message without a count.
if (( depth == 256 )); then
printf '\033[48;5;34m \033[0m %s%s\n' "$msg" "$count_txt" >&2
elif (( depth == 16 )); then
printf '\033[42m \033[0m %s%s\n' "$msg" "$count_txt" >&2
else
ok "$msg${count_txt}"
fi
return 0 return 0
else else
if (( depth == 256 )); then color_red " Failed: $msg"
printf '\033[48;5;196m \033[0m %s\n' "$msg" >&2
elif (( depth == 16 )); then
printf '\033[41m \033[0m %s\n' "$msg" >&2
else
color_red " Failed: $msg"
fi
color_red " (dnf reported a transaction failure)"
color_red " Last lines from $log:" color_red " Last lines from $log:"
sed -n "$((before+1)),\$p" "$log" 2>/dev/null | tail -n 20 | sed 's/^/ /' >&2 sed -n "$((before+1)),\$p" "$log" 2>/dev/null | tail -n 20 | sed 's/^/ /' >&2
return "$rc" return "$rc"
@@ -291,17 +225,9 @@ install_group_any() {
#------------------------------------------ #------------------------------------------
# Third-party repos # Third-party repos
#------------------------------------------ #------------------------------------------
# True when a repo id is CONFIGURED, whatever installed it. This is the
# reliable source of truth: a machine can have the repo without the
# -release RPM (hand-added .repo file) — and bootstrapping over an existing
# repo id via --repofrompath collides and fails (laptop-proven).
repo_enabled() {
dnf repolist --all 2>/dev/null | awk '{print $1}' | grep -qx "$1"
}
add_terra_repo() { add_terra_repo() {
if pkg_installed terra-release || repo_enabled terra; then if pkg_installed terra-release; then
ok "Terra repository already present." ok "Terra repository already installed."
return 0 return 0
fi fi
info "Adding Terra repository (Fyra Labs)..." info "Adding Terra repository (Fyra Labs)..."
@@ -315,9 +241,8 @@ add_terra_repo() {
add_rpmfusion_repo() { add_rpmfusion_repo() {
local rel local rel
rel=$(rpm -E %fedora) rel=$(rpm -E %fedora)
if pkg_installed rpmfusion-free-release rpmfusion-nonfree-release \ if pkg_installed rpmfusion-free-release rpmfusion-nonfree-release; then
|| { repo_enabled rpmfusion-free && repo_enabled rpmfusion-nonfree; }; then ok "RPM Fusion already installed."
ok "RPM Fusion already present."
return 0 return 0
fi fi
info "Enabling RPM Fusion (free + nonfree) for Fedora ${rel}..." info "Enabling RPM Fusion (free + nonfree) for Fedora ${rel}..."
@@ -382,9 +307,6 @@ disable_selinux() {
sudo cp "$cfg" "${cfg}.bkp.$(date +%F)" sudo cp "$cfg" "${cfg}.bkp.$(date +%F)"
sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' "$cfg" sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' "$cfg"
else else
# belt-and-suspenders: on real Fedora /etc/selinux always exists, but
# do not die if it somehow does not
sudo mkdir -p "$(dirname "$cfg")"
printf 'SELINUX=disabled\nSELINUXTYPE=targeted\n' | sudo tee "$cfg" >/dev/null printf 'SELINUX=disabled\nSELINUXTYPE=targeted\n' | sudo tee "$cfg" >/dev/null
fi fi
ok "SELINUX=disabled written to $cfg (backup saved). Reboot to apply." ok "SELINUX=disabled written to $cfg (backup saved). Reboot to apply."
-118
View File
@@ -1,118 +0,0 @@
#!/usr/bin/env bash
# ui-block.sh - TTY-safe status "block" indicator (no special fonts)
#
# busy : blue block that "breathes" (background colour pulses)
# ok : solid green block
# fail : solid red block (changed from red X per YuKu's spec)
#
# Why this works on a bare TTY: the "block" is a BACKGROUND-COLOURED SPACE,
# not a U+2588 glyph. No fonts, no UTF-8, no locale dependency.
#
# Draws on /dev/tty, never stdout: ui_choose/ui_confirm capture stdout with $(),
# so a status line on stdout would corrupt every captured answer.
_UB_FD=""
_UB_PID=""
_UB_MSG=""
_ub_tty() {
if [[ -z "$_UB_FD" ]]; then
# probe first: a bare `exec {fd}>/dev/tty` failure still prints its error
{ : >/dev/tty; } 2>/dev/null || { _UB_FD=""; return 1; }
exec {_UB_FD}>/dev/tty 2>/dev/null || { _UB_FD=""; return 1; }
fi
[[ -n "$_UB_FD" ]]
}
_ub_depth() {
local n
n=$(tput colors 2>/dev/null || echo 0)
case "$n" in ''|*[!0-9]*) n=0 ;; esac
if [[ "$n" -ge 256 ]]; then echo 256
elif [[ "$n" -ge 8 ]]; then echo 16
else echo 0; fi
# bare Linux VT: cap regardless of what tput reports
case "${TERM:-}" in linux*|console*) echo 16 ;; esac
}
# paint one background-coloured block (a SPACE) + message
_ub_paint() {
printf '\r\033[%sm \033[0m %s' "$1" "$2" >&"$_UB_FD"
}
_ub_busy_loop() {
local depth pal=() i tick=0.05
depth=$(_ub_depth)
[[ "$depth" -eq 16 ]] && tick=0.15 # console pulse: slow, not flicker
case "$depth" in
256) pal=(17 19 21 27 33 39 45 39 33 27 21 19) ;; # deep breathe: dark navy to bright blue
16) pal=(44 44 100 100) ;; # blue <-> dark grey, constant layout
*) pal=("|" "/" "-" "\\") ;; # last-resort spinner
esac
while :; do
for i in "${pal[@]}"; do
if [[ "$depth" -eq 256 ]]; then
_ub_paint "48;5;$i" "$_UB_MSG" # 256-colour cube background
elif [[ "$depth" -eq 16 ]]; then
_ub_paint "$i" "$_UB_MSG" # blue <-> dark grey pulse
else
printf '\r[%s] %s' "$i" "$_UB_MSG" >&"$_UB_FD"
fi
sleep "$tick" || return 0
done
done
}
_ub_stop() {
if [[ -n "$_UB_PID" ]]; then
kill "$_UB_PID" 2>/dev/null
wait "$_UB_PID" 2>/dev/null
_UB_PID=""
fi
}
ui_block_start() {
_UB_MSG="${1:-Working}"
_ub_tty || return 0 # no tty: silent no-op
_ub_stop
_ub_busy_loop &
_UB_PID=$!
}
ui_block_ok() {
local msg="${1:-Done}" d
_ub_tty || return 0
_ub_stop
d=$(_ub_depth)
case "$d" in
256) _ub_paint "48;5;34" "$msg" ;;
16) _ub_paint "42" "$msg" ;;
*) printf '\r[OK] %s' "$msg" >&"$_UB_FD" ;;
esac
printf '\033[0m\n' >&"$_UB_FD"
}
ui_block_fail() {
local msg="${1:-Failed}" d
_ub_tty || return 0
_ub_stop
d=$(_ub_depth)
case "$d" in
256) printf '\r\033[48;5;196m \033[0m %s' "$msg" >&"$_UB_FD" ;;
16) printf '\r\033[41m \033[0m %s' "$msg" >&"$_UB_FD" ;;
*) printf '\r[!] %s' "$msg" >&"$_UB_FD" ;;
esac
printf '\n' >&"$_UB_FD"
}
ui_block_close() {
_ub_stop
if [[ -n "$_UB_FD" ]]; then
exec {_UB_FD}>&- 2>/dev/null
_UB_FD=""
fi
return 0 # an EXIT trap's status becomes the script's exit code — never fail here
}
# ALWAYS install a cleanup trap in the sourcing script:
# trap 'ui_block_close' EXIT INT TERM
+1 -27
View File
@@ -17,34 +17,8 @@ if [[ ! -d "$HOME/.config" ]]; then
mkdir -p "$HOME/.config" mkdir -p "$HOME/.config"
fi fi
# --- Install the cfg command, ALWAYS (even if the repo exists already) ---
# The alias lives in ~/.bash_aliases (user-owned, trackable in fedora-dots),
# and ~/.bashrc sources that file. Without this, `cfg` exists only on the
# machine where it was typed by hand (VM-proven: "cfg pull: command not found").
ALIASES_FILE="$HOME/.bash_aliases"
BASHRC="$HOME/.bashrc"
if ! grep -q "alias cfg=" "$ALIASES_FILE" 2>/dev/null; then
info "Installing cfg alias into ~/.bash_aliases..."
cat >> "$ALIASES_FILE" <<'EOF'
# cfg: manage the bare dotfiles repo (~/.cfg.git, work tree $HOME)
alias cfg='git --git-dir=$HOME/.cfg.git --work-tree=$HOME'
EOF
fi
if ! grep -q "bash_aliases" "$BASHRC" 2>/dev/null; then
info "Making ~/.bashrc load ~/.bash_aliases..."
touch "$BASHRC"
cat >> "$BASHRC" <<'EOF'
# user aliases
if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases; fi
EOF
fi
if [[ -d "$CFG_DIR" ]]; then if [[ -d "$CFG_DIR" ]]; then
ok "Dotfiles already set up ($CFG_DIR exists)." ok "Dotfiles already set up ($CFG_DIR exists). Skipping."
ok "cfg alias ensured. Open a new shell or run: source ~/.bashrc"
exit 0 exit 0
fi fi
-354
View File
@@ -1,354 +0,0 @@
#!/bin/bash
#
# modules/user/nvidia.sh — install the NVIDIA driver branch that matches the
# GPU that is actually in the machine.
#
# Runs on its own:
# bash modules/user/nvidia.sh
# LSPCI_BIN=/tmp/fake-lspci bash modules/user/nvidia.sh
#
# Behaviour:
# * no NVIDIA GPU -> prints one line and exits 0
# * GPU found, SB off/setup -> installs, no signing, no MOK enrolment
# * GPU found, SB enabled -> generates + imports the akmods key, then STOPS
# and asks for a reboot. The driver is installed
# on the next run, once the key is enrolled.
# (Installing before enrolment is the documented
# way to get a black screen: RPM Fusion blacklists
# nouveau the moment the driver package lands.)
# * already installed -> skips the transaction, keeps `dnf mark user`
#
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../../lib/common.sh
source "$HERE/../../lib/common.sh"
# shellcheck source=../../lib/ui.sh
source "$HERE/../../lib/ui.sh"
if is_debug; then set +e; else set -e; fi
# Overridable so the detection logic can be tested on a host with no NVIDIA
# hardware (same pattern as OS_RELEASE_FILE / FSTAB_FILE in lib/common.sh).
LSPCI_BIN="${LSPCI_BIN:-lspci}"
MOKUTIL_BIN="${MOKUTIL_BIN:-mokutil}"
EFIVARS_DIR="${EFIVARS_DIR:-/sys/firmware/efi/efivars}"
AKMODS_CERT="${AKMODS_CERT:-/etc/pki/akmods/certs/public_key.der}"
AKMOD_COMPILE_TIMEOUT="${AKMOD_COMPILE_TIMEOUT:-300}"
step "NVIDIA driver"
rule
#--------------------------------------------------
# Detection
#--------------------------------------------------
# Prints: "<branch>|<model>|<pciid>" or nothing at all when there is no card.
#
# Branch is derived from the chip family in the name lspci prints (hwdata always
# has it): GB/AD/GA/TU -> current, GV/GP/GM -> 580xx, GK -> 470xx, GF -> 390xx.
# The numeric device id is NOT used for the decision: the id ranges overlap
# between families, and a wrong guess installs a module the card cannot use.
detect_nvidia_gpu() {
local line model arch id
line=$("$LSPCI_BIN" -nn 2>/dev/null \
| grep -iE '(VGA compatible|3D|Display) controller' \
| grep -iE 'nvidia|\[10de:' \
| head -n1) || true
[[ -z "$line" ]] && return 1
id=$(printf '%s' "$line" \
| grep -oE '\[10de:[0-9a-fA-F]{4}\]' | head -n1 | tr -d '[]') || true
# "01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102
# [GeForce RTX 3080 Ti] [10de:2208] (rev a1)" -> "GA102 [GeForce RTX 3080 Ti]"
model=$(printf '%s' "$line" \
| sed -n 's/.*NVIDIA Corporation \(.*\)\[10de:[0-9a-fA-F]\{4\}\].*/\1/p' \
| sed -e 's/[[:space:]]*$//') || true
[[ -z "$model" ]] && model="unknown NVIDIA device"
arch=$(printf '%s' "$model" | grep -oE '(GB|AD|GA|TU|GV|GP|GM|GK|GF)[0-9]{3}[A-Z]?' \
| head -n1 | grep -oE '^(GB|AD|GA|TU|GV|GP|GM|GK|GF)') || true
local branch="unknown"
case "$arch" in
GB|AD|GA|TU) branch="current" ;; # Turing and newer: open kernel module
GV|GP|GM) branch="580xx" ;; # Volta / Pascal / Maxwell (last branch)
GK) branch="470xx" ;; # Kepler, Xorg only
GF) branch="390xx" ;; # Fermi, Xorg only, EOL upstream
"")
# Pre-Fermi naming (G80/G92/GT200/GT215...) has no architecture
# prefix; those cards need the EOL 340xx branch plus a longterm
# kernel, which this script deliberately does not automate.
if printf '%s' "$model" | grep -qE '(^|[^A-Za-z])(G[0-9]{2,3}|GT[0-9]{3}|NV[0-9]{2})'; then
branch="340xx"
else
branch="unknown"
fi
;;
esac
printf '%s|%s|%s\n' "$branch" "$model" "${id:-10de:unknown}"
}
gpu_info=""
if ! command -v "$LSPCI_BIN" >/dev/null 2>&1; then
info "pciutils missing (no lspci). Installing..."
install_optional pciutils
fi
if command -v "$LSPCI_BIN" >/dev/null 2>&1; then
gpu_info=$(detect_nvidia_gpu) || true
fi
if [[ -z "$gpu_info" ]]; then
info "No NVIDIA GPU detected. Nothing to do."
exit 0
fi
BRANCH="${gpu_info%%|*}"
rest="${gpu_info#*|}"
MODEL="${rest%%|*}"
PCIID="${rest#*|}"
ok "GPU: $MODEL ($PCIID)"
# Multiple cards of different generations are not something one dnf transaction
# can serve correctly: the packages are mutually exclusive.
nv_count=$("$LSPCI_BIN" -nn 2>/dev/null \
| grep -icE '(VGA compatible|3D|Display) controller.*(nvidia|\[10de:)') || true
if [[ "${nv_count:-0}" -gt 1 ]]; then
warn "Found $nv_count NVIDIA display devices. Only the first one determines"
warn "the driver branch; mixed generations on one machine need manual work."
fi
# A 3D controller (rather than a VGA/Display controller) is the Optimus layout.
if printf '%s' "$("$LSPCI_BIN" -nn 2>/dev/null | grep -i '10de:' | head -n1)" \
| grep -q '3D controller'; then
info "Hybrid (Optimus) layout: Fedora handles the iGPU/dGPU split automatically."
fi
#--------------------------------------------------
# Branch -> packages
#--------------------------------------------------
if [[ "$BRANCH" == "unknown" ]]; then
warn "Could not identify the GPU generation from '$MODEL'."
choice=$(ui_choose "NVIDIA driver branch" "Which driver branch does this card need?" \
"current (Turing / Ampere / Ada / Blackwell)" \
"current (Turing / Ampere / Ada / Blackwell)" \
"580xx (Maxwell / Pascal / Volta)" \
"470xx (Kepler)" \
"390xx (Fermi)")
case "$choice" in
580xx*) BRANCH="580xx" ;;
470xx*) BRANCH="470xx" ;;
390xx*) BRANCH="390xx" ;;
*) BRANCH="current" ;;
esac
fi
case "$BRANCH" in
current)
PKGS=(akmod-nvidia xorg-x11-drv-nvidia-cuda)
MARK="akmod-nvidia"
BRANCH_LABEL="current (Turing/Ampere/Ada/Blackwell, open kernel module)"
;;
580xx)
PKGS=(akmod-nvidia-580xx xorg-x11-drv-nvidia-580xx xorg-x11-drv-nvidia-580xx-cuda)
MARK="akmod-nvidia-580xx"
BRANCH_LABEL="580xx (Maxwell/Pascal/Volta, last branch for those cards)"
;;
470xx)
PKGS=(akmod-nvidia-470xx xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-cuda)
MARK="akmod-nvidia-470xx"
BRANCH_LABEL="470xx (Kepler, Xorg only)"
;;
390xx)
PKGS=(akmod-nvidia-390xx xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-cuda)
MARK="akmod-nvidia-390xx"
BRANCH_LABEL="390xx (Fermi, EOL upstream, Xorg only)"
;;
340xx)
warn "This card predates Fermi. Driver 340xx is end-of-life and needs a"
warn "longterm kernel from a COPR, which this script does not automate."
warn "See https://rpmfusion.org/Howto/NVIDIA and install it by hand."
exit 0
;;
esac
info "Driver branch: $BRANCH_LABEL"
if [[ "$BRANCH" != "current" ]]; then
warn "This branch has no Wayland support. Plan on an X11 session."
fi
#--------------------------------------------------
# Secure Boot gate
#--------------------------------------------------
# enabled -> sign and enrol, then stop for a reboot
# disabled / setup mode -> no signing at all (an import request would never be
# processed: shim skips MokManager when enforcement is
# off, so the request just sits in the efivars)
sb_state() {
local out sb_var pk_var
if command -v "$MOKUTIL_BIN" >/dev/null 2>&1; then
out=$("$MOKUTIL_BIN" --sb-state 2>/dev/null) || true
case "$out" in
*"SecureBoot enabled"*) echo "enabled"; return 0 ;;
*"Setup mode"*) echo "setup"; return 0 ;;
*"SecureBoot disabled"*) echo "disabled"; return 0 ;;
esac
fi
# Fallback: read the efivars directly (byte 5 of the variable data).
if [[ -d "$EFIVARS_DIR" ]]; then
sb_var=$(ls "$EFIVARS_DIR"/SecureBoot-* 2>/dev/null | head -n1) || true
pk_var=$(ls "$EFIVARS_DIR"/PK-* 2>/dev/null | head -n1) || true
if [[ -n "$sb_var" ]]; then
if [[ "$(od -An -t u1 -j 4 -N 1 "$sb_var" 2>/dev/null | tr -d '[:space:]')" == "1" ]]; then
echo "enabled"; return 0
fi
# No Platform Key enrolled = Setup mode: nothing can be enforced.
[[ -z "$pk_var" ]] && { echo "setup"; return 0; }
echo "disabled"; return 0
fi
fi
echo "unknown"
}
SB_STATE=$(sb_state)
case "$SB_STATE" in
enabled)
info "Secure Boot is enabled: the module has to be signed and the key enrolled."
install_packages akmods
if [[ ! -f "$AKMODS_CERT" ]]; then
info "Generating the akmods signing key..."
sudo kmodgenca -a
else
ok "akmods signing key present."
fi
if sudo "$MOKUTIL_BIN" --test-key "$AKMODS_CERT" 2>&1 | grep -qi 'not enrolled'; then
warn "The akmods key is not enrolled yet. Importing it now."
sudo "$MOKUTIL_BIN" --import "$AKMODS_CERT"
printf "\n"
color_yellow "------------------------------------------------------------"
color_yellow " ACTION REQUIRED: enrolment is pending"
color_yellow "------------------------------------------------------------"
info "The driver is deliberately NOT installed yet: with Secure Boot"
info "enforcing, an unsigned module leaves you with a black screen."
printf "\n"
info "1. Reboot now."
info "2. At the boot screen press a key during the countdown."
info "3. Enroll MOK -> View key 0 (check the fingerprint) -> Continue"
info " -> Yes -> type the password you just set. The keyboard there"
info " is QWERTY, so use a short, symbol-free password."
info "4. Boot Fedora and run this setup again: it will install the"
info " driver now that the key is trusted."
printf "\n"
warn "If you miss the countdown the request is dropped silently and"
warn "you have to import again."
exit 0
fi
ok "akmods key already enrolled."
;;
setup)
ok "Secure Boot is off (platform is in Setup mode): no signing needed."
warn "Setup mode is a temporary state: a firmware update or 'Restore"
warn "Factory Keys' turns enforcement on, and this unsigned module then"
warn "stops loading at the next boot. Enrol the key before you need it:"
warn " sudo dnf install akmods && sudo kmodgenca -a"
warn " sudo mokutil --import /etc/pki/akmods/certs/public_key.der"
;;
disabled)
ok "Secure Boot is disabled: no signing needed."
warn "Keep it that way deliberately: a firmware update or 'Restore Factory"
warn "Keys' can enable enforcement, and the unsigned module then fails to"
warn "load at the next boot."
;;
*)
warn "Could not determine the Secure Boot state. Assuming it is off."
warn "If Secure Boot actually is on, the driver will not load: check"
warn "'mokutil --sb-state' and enrol the akmods key first."
;;
esac
#--------------------------------------------------
# Install
#--------------------------------------------------
if pkg_installed_any "${PKGS[@]}"; then
ok "NVIDIA driver packages already installed."
else
info "Installing: ${PKGS[*]}"
if ! install_packages "${PKGS[@]}"; then
err "Driver installation failed for branch $BRANCH."
err "Check that RPM Fusion nonfree is enabled: dnf repolist | grep rpmfusion"
exit 1
fi
fi
# An akmod is removable as an "unused dependency" unless it is marked user
# installed; `dnf autoremove` has taken the driver (and its compiler) away on
# people before.
if ! sudo dnf mark user "$MARK" >/dev/null 2>&1; then
warn "Could not mark $MARK as user-installed (dnf autoremove may remove it)."
fi
#--------------------------------------------------
# Wait for the module build
#--------------------------------------------------
# dnf returns before the kmod exists. Rebooting in that window gives a boot with
# no driver at all, because nouveau is already blacklisted by the driver package.
NEWEST_KERNEL=$(rpm -q --qf '%{VERSION}-%{RELEASE}.%{ARCH}\n' kernel-core 2>/dev/null \
| sort -V | tail -n1) || true
info "Waiting for the akmod build (up to ${AKMOD_COMPILE_TIMEOUT}s)..."
built=false
deadline=$(( SECONDS + AKMOD_COMPILE_TIMEOUT ))
while (( SECONDS < deadline )); do
if [[ -n "$NEWEST_KERNEL" ]] && modinfo -k "$NEWEST_KERNEL" -F version nvidia >/dev/null 2>&1; then
built=true; break
fi
if [[ -z "$NEWEST_KERNEL" ]] && modinfo -F version nvidia >/dev/null 2>&1; then
built=true; break
fi
sleep 5
done
if [[ "$built" == "true" ]]; then
if [[ -n "$NEWEST_KERNEL" ]]; then
VER=$(modinfo -k "$NEWEST_KERNEL" -F version nvidia 2>/dev/null) || true
else
VER=$(modinfo -F version nvidia 2>/dev/null) || true
fi
ok "Kernel module built: nvidia ${VER:-unknown}"
if [[ -n "$NEWEST_KERNEL" ]] && ! [[ "$NEWEST_KERNEL" == "$(uname -r)" ]]; then
warn "Built for $NEWEST_KERNEL but you are running $(uname -r): reboot."
fi
else
warn "The module is not built yet after ${AKMOD_COMPILE_TIMEOUT}s."
warn "Do NOT reboot until it is: the next boot would have no driver."
warn "Check the build log, then retry:"
warn " ls -l /var/cache/akmods/nvidia/"
warn " sudo akmods --force"
fi
#--------------------------------------------------
# Optional extras
#--------------------------------------------------
printf "\n"
extras=$(ui_confirm "Install the optional graphics extras (VA-API decode, vulkan)?" "no")
if [[ "$extras" == "yes" ]]; then
install_optional libva-nvidia-driver libva-utils vulkan
fi
#--------------------------------------------------
# What to check after the reboot
#--------------------------------------------------
printf "\n"
info "After the reboot, verify with:"
info " nvidia-smi"
info " cat /sys/module/nvidia_drm/parameters/modeset # expect Y"
info " lsmod | grep -Ei 'nouveau|nova_core' # expect nothing"
warn "A reboot is required before the driver is active."
+40 -345
View File
@@ -12,10 +12,6 @@ source "$SCRIPT_DIR/lib/common.sh"
source "$SCRIPT_DIR/lib/session.sh" source "$SCRIPT_DIR/lib/session.sh"
# shellcheck source=lib/ui.sh # shellcheck source=lib/ui.sh
source "$SCRIPT_DIR/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). # 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}" export SETUP_DNF_LOG="${SETUP_DNF_LOG:-/tmp/fedora-setup-dnf-$(id -u).log}"
@@ -32,7 +28,6 @@ TWM=""
SELINUX_OPT="" SELINUX_OPT=""
SNAPPER_OPT="" SNAPPER_OPT=""
DOTFILES_OPT="" DOTFILES_OPT=""
NVIDIA_OPT=""
usage() { usage() {
cat <<'EOF' cat <<'EOF'
@@ -49,8 +44,6 @@ Options:
--selinux=X keep | disabled (asked, default keep) --selinux=X keep | disabled (asked, default keep)
--snapper=X yes | no (automatic on btrfs; use 'no' to opt out) --snapper=X yes | no (automatic on btrfs; use 'no' to opt out)
--dotfiles=X yes | no (clone personal dotfiles, default no) --dotfiles=X yes | no (clone personal dotfiles, default no)
--nvidia=X auto | no (auto: install the matching NVIDIA driver
when an NVIDIA GPU is detected; default auto)
-y, --yes never prompt (requires --de and --twm) -y, --yes never prompt (requires --de and --twm)
-d, --debug pause after each section instead of aborting on error -d, --debug pause after each section instead of aborting on error
-h, --help show this help -h, --help show this help
@@ -75,7 +68,6 @@ for arg in "$@"; do
--selinux=*) SELINUX_OPT="${arg#*=}" ;; --selinux=*) SELINUX_OPT="${arg#*=}" ;;
--snapper=*) SNAPPER_OPT="${arg#*=}" ;; --snapper=*) SNAPPER_OPT="${arg#*=}" ;;
--dotfiles=*) DOTFILES_OPT="${arg#*=}" ;; --dotfiles=*) DOTFILES_OPT="${arg#*=}" ;;
--nvidia=*) NVIDIA_OPT="${arg#*=}" ;;
--twm=*) TWM="${arg#*=}" ;; --twm=*) TWM="${arg#*=}" ;;
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
*) *)
@@ -196,9 +188,6 @@ add_rpmfusion_repo
# Terra up front: it carries mangowc (noctalia moved into Fedora proper in # Terra up front: it carries mangowc (noctalia moved into Fedora proper in
# F44). Idempotent; also kept for the standalone-mango-module case. # F44). Idempotent; also kept for the standalone-mango-module case.
add_terra_repo add_terra_repo
# Fedora's third-party repo set (ships the google-chrome repo, disabled by
# default — the Browsers menu option enables it via config-manager setopt).
install_optional fedora-workstation-repositories
pause_if_debug pause_if_debug
if ! command -v flatpak >/dev/null 2>&1; then if ! command -v flatpak >/dev/null 2>&1; then
@@ -262,15 +251,6 @@ else
*) err "Unknown --dotfiles value: '$DOTFILES_OPT' (yes|no)"; exit 2 ;; *) err "Unknown --dotfiles value: '$DOTFILES_OPT' (yes|no)"; exit 2 ;;
esac esac
fi fi
# GPU drivers are driven by DETECTED hardware, not by a prompt: an NVIDIA card
# in the machine means install the branch that fits it. 'yes' is accepted as an
# alias of 'auto' so muscle memory does not hit an error.
case "${NVIDIA_OPT:-}" in
auto|yes|"") NVIDIA_OPT="auto" ;;
no) NVIDIA_OPT="no" ;;
*) err "Unknown --nvidia value: '$NVIDIA_OPT' (auto|no)"; exit 2 ;;
esac
pause_if_debug pause_if_debug
#------------------------------------------ #------------------------------------------
@@ -378,14 +358,6 @@ if [[ "$TWM" != "none" ]]; then
pause_if_debug pause_if_debug
fi fi
# Hardware drivers. The module re-detects the card and exits quietly when there
# is none, so running it unconditionally is safe; --nvidia=no is the opt-out
# (nouveau or a hand-built driver).
if [[ "$NVIDIA_OPT" != "no" ]]; then
run_module "$SCRIPT_DIR/modules/user/nvidia.sh"
pause_if_debug
fi
#------------------------------------------ #------------------------------------------
# 6 Done # 6 Done
#------------------------------------------ #------------------------------------------
@@ -395,65 +367,22 @@ fi
#------------------------------------------ #------------------------------------------
# Add a category here and it is installed on every run, no questions asked. # Add a category here and it is installed on every run, no questions asked.
# Keep each category one line so diffs stay readable. # Keep each category one line so diffs stay readable.
# A line that is a dnf comps GROUP is written "@group" (e.g. "@multimedia");
# the loop below routes it through install_group, which reports a bad group
# name with the list of groups this system actually has.
ALWAYS_PACKAGES=( ALWAYS_PACKAGES=(
# Terminals # Terminals
"ghostty ghostty-bash-completion zsh zsh-syntax-highlighting" "ghostty ghostty-bash-completion"
# 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 catfish galculator NetworkManager network-manager-applet"
"pipewire pipewire-alsa pipewire-pulse wireplumber pavucontrol playerctl gparted"
"system-config-printer system-config-printer-applet system-config-printer-libs ripgrep meld btop htop"
"gcc gcc-c++ make python3-devel libX11-devel libXi-devel"
# File Managers
"thunar thunar-archive-plugin thunar-volman baobab gvfs-smb squashfs-tools cifs-utils papirus-icon-theme"
# Internet
"wget"
# Compression tools
"zip gzip 7zip unace unrar xarchiver"
# Office and Editors
"libreoffice"
# Multimedia
"mpv gstreamer1-plugin-openh264 mozilla-openh264"
# Multimedia codecs: dnf comps group 'multimedia' (the group Fedora's own
# docs recommend for playing movies/music). It is a GROUP, not a package,
# so it gets its own line — and the loop below sends it to install_group.
"@multimedia"
)
step "6 Always-installed packages" step "6 Always-installed packages"
rule rule
always_failed=""
for category in "${ALWAYS_PACKAGES[@]}"; do for category in "${ALWAYS_PACKAGES[@]}"; do
# A "@group" line is a dnf comps group. install_group checks the group
# exists first and prints the real group list when it does not, instead
# of dnf's unhelpful "No match for argument: @multimedia".
if [[ "$category" == @* ]]; then
if ! install_group "${category#@}"; then
color_red " Failed: group ${category}"
always_failed="yes"
pause_if_debug
fi
continue
fi
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! install_packages $category; then install_packages $category
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 done
if [[ -z "$always_failed" ]]; then ok "Always-packages installed."
ok "Always-packages installed."
else
warn "Always-packages finished with failures (see red lines above)."
fi
pause_if_debug pause_if_debug
#------------------------------------------ #------------------------------------------
# 7 Advanced: optional tool selection (menu-driven) # 7 Advanced: optional tool selection (menu-driven)
#------------------------------------------ #------------------------------------------
@@ -463,9 +392,6 @@ pause_if_debug
ADV_CATEGORIES=( ADV_CATEGORIES=(
"Virtualisation" "Virtualisation"
"Browsers" "Browsers"
"Editors"
"Graphics and Multimedia"
"Development Tools"
) )
# shellcheck disable=SC2034 # arrays are read via nameref from ADV_CATEGORIES # shellcheck disable=SC2034 # arrays are read via nameref from ADV_CATEGORIES
ADV_Virtualisation=( ADV_Virtualisation=(
@@ -479,22 +405,6 @@ ADV_Browsers=(
"google-chrome:Google Chrome (adds repo):google-chrome-stable" "google-chrome:Google Chrome (adds repo):google-chrome-stable"
"brave:Brave (adds repo):brave-browser" "brave:Brave (adds repo):brave-browser"
) )
# shellcheck disable=SC2034 # arrays are read via nameref from ADV_CATEGORIES
ADV_Editors=(
"vscodium:VSCodium (flatpak):flatpak:com.vscodium.codium"
)
# shellcheck disable=SC2034
ADV_Graphics_and_Multimedia=(
"gimp:GIMP:gimp"
"inkscape:Inkscape:inkscape"
"obs:OBS Studio:obs-studio"
"lollypop:Lollypop (music):lollypop"
"spotify:Spotify (flatpak):flatpak:com.spotify.Client"
)
# shellcheck disable=SC2034
ADV_Development_Tools=(
"devtools:Development Tools (dnf group + build tools):@development-tools wget cmake ninja-build pkgconf-pkg-config"
)
run_advanced_menu() { run_advanced_menu() {
local done_any=false local done_any=false
@@ -528,43 +438,38 @@ run_advanced_menu() {
sel=$(ui_choose_multi "$cat" "Install which? (multi-select, none = back):" "${labels[@]}") sel=$(ui_choose_multi "$cat" "Install which? (multi-select, none = back):" "${labels[@]}")
[[ -z "$sel" ]] && continue [[ -z "$sel" ]] && continue
# Resolve each picked label to its OWN package list, then install # Map chosen labels back to their package lists. Word-split each
# PER ITEM: one failing package used to kill the whole multi-select # package list into SEPARATE args: quoting it whole makes dnf look for
# transaction (VM: brave's no-match took chromium down with it). # one package named "qemu-kvm libvirt ..." (seen on the VM).
# Repo wiring inside install_category_pkgs runs per item; failures local -a to_install=()
# warn in red and the remaining picks still install.
# Read the picks into an ARRAY first, install in a for-loop.
# A `while read ... <<< "$sel"` herestring REPLACES stdin for its whole
# body — inside it stdin is no longer a TTY, ui_quiet_run saw "piped"
# and menu installs ran in plain mode (VM-proven: the advanced menu
# was the one place dnf's full output still scrolled).
local -a picked_items=()
while IFS= read -r picked; do while IFS= read -r picked; do
[[ -n "$picked" ]] && picked_items+=("$picked") [[ -z "$picked" ]] && continue
done <<< "$sel"
local picked lbl it
for picked in "${picked_items[@]}"; do
for entry in "${entries[@]}"; do for entry in "${entries[@]}"; do
rest="${entry#*:}" rest="${entry#*:}"
lbl="${rest%%:*}" lbl="${rest%%:*}"
[[ "$lbl" == "$picked" ]] || continue if [[ "$lbl" == "$picked" ]]; then
rest="${rest#*:}" # drop id and label -> pkgs rest="${entry#*:}"; rest="${rest#*:}" # drop id and label
# word-split: quoting $rest whole makes dnf look for one # shellcheck disable=SC2206
# package named "qemu-kvm libvirt ..." (seen on the VM) to_install+=(${rest})
# 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 fi
done done
done done <<< "$sel"
if (( ${#to_install[@]} > 0 )); then
step "Installing from $cat: ${to_install[*]}"
# install_category_pkgs wires any needed third-party repo first.
# Failure must NOT kill the script: warn in red and drop back to
# the menu (set -e is bypassed for this one call).
if install_category_pkgs "$cat" "${to_install[@]}"; then
done_any=true
else
color_red " Installation failed: ${to_install[*]}"
color_red " (dnf reported no match / transaction failure — see output above)"
warn " Returning to the menu. The system is unchanged."
pause_if_debug
fi
unset to_install; local -a to_install=() # fresh slate for next pick
fi
done done
# not `&& ok` — a false done_any would return 1 and kill the script under set -e # not `&& ok` — a false done_any would return 1 and kill the script under set -e
if [[ "$done_any" == "true" ]]; then if [[ "$done_any" == "true" ]]; then
@@ -576,35 +481,6 @@ run_advanced_menu() {
# app that needs one; packages alone stay generic. # app that needs one; packages alone stay generic.
install_category_pkgs() { install_category_pkgs() {
local cat="$1"; shift local cat="$1"; shift
# flatpak:<id> entries install from Flathub; the rest go to dnf. Split so
# a flatpak in a mixed pick cannot fail the dnf transaction (and vice
# versa — same per-item resilience as the rest of the menu).
local -a fp=() dp=()
local item
for item in "$@"; do
if [[ "$item" == flatpak:* ]]; then
fp+=("${item#flatpak:}")
else
dp+=("$item")
fi
done
if (( ${#dp[@]} > 0 )); then
# existing per-category wiring + dnf install (unchanged)
install_category_pkgs_dnf "$cat" "${dp[@]}" || return 1
fi
if (( ${#fp[@]} > 0 )); then
# quiet like dnf: live block line, full output in the log, red + tail
# on failure (ui_quiet_run matches flatpak's 'Installing...' lines)
ui_quiet_run "Installing flatpak: ${fp[*]}" \
flatpak install -y flathub "${fp[@]}" || return 1
fi
return 0
}
install_category_pkgs_dnf() {
local cat="$1"; shift
case "$cat" in case "$cat" in
Virtualisation) Virtualisation)
# qemu:///system without password prompts: libvirt group + enabled # qemu:///system without password prompts: libvirt group + enabled
@@ -629,26 +505,16 @@ install_category_pkgs_dnf() {
done done
if $needs_chrome && ! pkg_installed google-chrome-stable; then if $needs_chrome && ! pkg_installed google-chrome-stable; then
info "Adding Google Chrome repo..." info "Adding Google Chrome repo..."
install_optional fedora-workstation-repositories sudo dnf install -y fedora-workstation-repositories 2>/dev/null || true
if ! sudo dnf config-manager setopt google-chrome.enabled=1 >/dev/null 2>&1; then sudo dnf config-manager setopt google-chrome.enabled=1 2>/dev/null || \
# repo enable failed: install straight from Google's rpm sudo dnf install -y "https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm"
ui_quiet_run "Installing Google Chrome (direct rpm)" \
sudo dnf install -y "https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm"
fi
fi fi
if $needs_brave && ! pkg_installed brave-browser; then if $needs_brave && ! pkg_installed brave-browser; then
# dnf5 (Fedora 41+) has no 'config-manager add' subcommand — info "Adding Brave repo..."
# the VM showed 'Unknown argument "add"', so the repo never sudo dnf install -y dnf-plugins-core
# landed and brave-browser had no match. Correct form per sudo dnf config-manager add --from-repofile=https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo || \
# Brave's own F41+ docs: addrepo. The .repo carries the gpgkey, true
# so no separate rpm --import is needed. sudo rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc 2>/dev/null || true
if [[ -f /etc/yum.repos.d/brave-browser.repo ]]; then
ok "Brave repo already present."
else
ui_quiet_run "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 fi
;; ;;
esac esac
@@ -665,169 +531,7 @@ if [[ "$ASSUME_YES" != "true" ]]; then
fi fi
pause_if_debug pause_if_debug
#------------------------------------------ step "8 Summary"
# 8 Post-install tailoring
#------------------------------------------
# Ported from the old starburst minimal-fedora script, adapted:
# - polkit agent handled elsewhere (polkit-kde user unit, VM-proven)
# - resilient: each part warns red and continues (one missing package
# must not abort a finished install)
step "8 Post-install tailoring"
rule
# --- 8.1 PeaZip: toolkit variant follows the session (Qt6 for Qt-ish
# environments, GTK3 for the rest) ---
info "Tailoring toolkits for DE=$DE / TWM=$TWM..."
# The UI flavors all ship /usr/bin/peazip and file-conflict with each
# other — installing gtk3 over an existing qt5 dies mid-transaction
# (laptop-proven). Behaviors:
# no flavor -> install the environment-appropriate one
# right flavor -> nothing to do
# OTHER flavor -> dnf SWAP to the wanted one (atomic: on failure the
# existing flavor stays, which is the safe outcome)
#
# Flavor choice: PeaZip's GTK3 build is ALPHA (Lazarus GTK3 widgetset) —
# upstream ships it "for test purpose only", and it renders broken on
# non-GTK Wayland compositors (YuKu-proven on niri: broken UI until a
# manual swap to qt6). GTK3 therefore only for genuine GTK stacks
# (GNOME, XFCE); Qt6 everywhere else — including niri/MangoWC/Hyprland
# and chadwm (Qt6 handles both Wayland and X11 cleanly).
if [[ "$DE" == "gnome" || "$DE" == "xfce" ]]; then
peazip_wanted="peazip-gtk3"
else
peazip_wanted="peazip-qt6"
fi
peazip_existing=""
local_v=""
for local_v in peazip-qt5 peazip-qt6 peazip-gtk3 peazip-gtk2; do
pkg_installed "$local_v" && peazip_existing="$local_v"
done
if [[ -z "$peazip_existing" ]]; then
# shellcheck disable=SC2086
if ! install_packages peazip-common "$peazip_wanted"; then
color_red " Failed: peazip-common $peazip_wanted"
color_red " (continuing — PeaZip is a convenience, not a dependency)"
fi
elif [[ "$peazip_existing" == "$peazip_wanted" ]]; then
ok "PeaZip already present ($peazip_existing)."
else
info "PeaZip $peazip_existing does not match this environment — swapping to $peazip_wanted..."
if ui_quiet_run "Swapping PeaZip: $peazip_existing -> $peazip_wanted" \
sudo dnf swap -y "$peazip_existing" "$peazip_wanted"; then
ok "PeaZip swapped to $peazip_wanted."
else
warn "Swap failed — keeping the existing $peazip_existing (fully functional)."
fi
fi
pause_if_debug
# --- 8.2 Starship prompt + Tokyo Night preset ---
if ! command -v starship >/dev/null 2>&1; then
info "Installing Starship shell prompt..."
# starship's official installer; -y skips its confirmation
if ! curl -sS https://starship.rs/install.sh | sh -s -- -y; then
color_red " Failed: starship installer"
color_red " (continuing)"
fi
fi
# Preset only when the user has no config yet — never clobber existing
# configuration (which may arrive via the dotfiles checkout).
if command -v starship >/dev/null 2>&1 && [[ ! -f "$HOME/.config/starship.toml" ]]; then
info "Applying starship tokyo-night preset..."
mkdir -p "$HOME/.config"
starship preset tokyo-night -o "$HOME/.config/starship.toml" \
|| warn "Could not apply the preset (starship still installed)."
fi
# bashrc wiring — ONLY when dotfiles were not cloned: on machines that
# use fedora-dots, .bashrc is owned by the dots and must not be edited
# here. Also requires starship to actually be present (a missing binary
# would make every shell print an error from the eval line).
if [[ "$DOTFILES" != "true" ]] \
&& command -v starship >/dev/null 2>&1 \
&& ! grep -q "starship init" "$HOME/.bashrc" 2>/dev/null; then
info "Wiring starship into ~/.bashrc..."
cat >> "$HOME/.bashrc" <<'EOF'
# starship
export STARSHIP_CONFIG="$HOME/.config/starship.toml"
eval "$(starship init bash)"
EOF
fi
pause_if_debug
# --- 8.3 RobotoMono Nerd Font (the modules install JetBrainsMono) ---
install_nerd_font RobotoMono
pause_if_debug
# --- 8.4 Services ---
info "Enabling services..."
sudo systemctl enable --now fstrim.timer
sudo systemctl enable --now chronyd || true
info "Ensuring NetworkManager manages all devices..."
NM_CONF="/etc/NetworkManager/conf.d/10-globally-managed-devices.conf"
if [[ ! -f "$NM_CONF" ]]; then
sudo mkdir -p "$(dirname "$NM_CONF")"
printf '[ifupdown]\nmanaged=true\n\n[keyfile]\nunmanaged-devices=\n' \
| sudo tee "$NM_CONF" >/dev/null
sudo systemctl enable --now NetworkManager
sudo systemctl restart NetworkManager
warn "NetworkManager restarted; giving it 15s to settle..."
sleep 15
else
ok "NetworkManager already globally-managed (config present)."
fi
# pipewire user units: preset-enabled on install; enable explicitly for
# robustness (fails harmlessly in odd session contexts, hence || true)
systemctl --user enable pipewire pipewire-pulse wireplumber >/dev/null 2>&1 || true
pause_if_debug
# --- 8.5 SDDM theme (SilentSDDM) — only when SDDM is the greeter ---
# Requires SDDM >= 0.21 (Fedora 44 ships 0.24+). Skipped on GNOME (gdm) and
# any setup where sddm is not installed.
if pkg_installed sddm; then
info "Installing SDDM theme: SilentSDDM..."
GIT_DIR="$HOME/git"
REPO_DIR="$GIT_DIR/SilentSDDM"
REPO_URL="https://github.com/uiriansan/SilentSDDM"
mkdir -p "$GIT_DIR"
if [[ -d "$REPO_DIR/.git" ]]; then
ok "SilentSDDM checkout exists. Updating..."
git -C "$REPO_DIR" pull --rebase --quiet || warn "git pull failed; using existing checkout."
else
info "Cloning SilentSDDM..."
git clone -b main --depth 1 --quiet "$REPO_URL" "$REPO_DIR" \
|| { color_red " Failed: git clone SilentSDDM (continuing)"; }
fi
if [[ -f "$REPO_DIR/install.sh" ]]; then
# yes | install.sh: the installer prompts; SIGPIPE from 'yes' would
# read as a crash under pipefail, so suspend it for this one command.
# pipefail state is inherited by the ui_quiet_run subshell at fork.
# The theme's own dnf dependency flood goes to the log like every
# other transaction (laptop-proven it was the last raw wall left).
set +o pipefail
if ui_quiet_run "Installing SilentSDDM theme" \
bash -c 'cd "$1" && yes | ./install.sh' _ "$REPO_DIR"; then
ok "SilentSDDM theme installed."
else
color_red " Failed: SilentSDDM installer (continuing; SDDM keeps its default theme)"
fi
set -o pipefail
else
color_red " SilentSDDM install.sh not found; skipping theme."
fi
else
info "SDDM not installed (gdm/none setup) - skipping SDDM theme."
fi
pause_if_debug
step "9 Summary"
rule rule
# Post-install wiring that must see the FINAL package state. The libvirt # Post-install wiring that must see the FINAL package state. The libvirt
@@ -853,15 +557,6 @@ ok "TWM: $TWM"
ok "SELinux: $([[ "$SELINUX_DISABLED" == "true" ]] && echo disabled || echo keeping)" ok "SELinux: $([[ "$SELINUX_DISABLED" == "true" ]] && echo disabled || echo keeping)"
ok "Btrfs snapshots: $([[ "$BTRFS_SNAPPER" == "true" ]] && echo yes || echo skipped)" ok "Btrfs snapshots: $([[ "$BTRFS_SNAPPER" == "true" ]] && echo yes || echo skipped)"
ok "Dotfiles: $([[ "$DOTFILES" == "true" ]] && echo clone || echo no)" ok "Dotfiles: $([[ "$DOTFILES" == "true" ]] && echo clone || echo no)"
NV_VER=$(modinfo -F version nvidia 2>/dev/null) || true
if [[ -n "${NV_VER:-}" ]]; then
ok "NVIDIA: module nvidia ${NV_VER} built (reboot if it was installed in this run)"
elif pkg_installed_any akmod-nvidia akmod-nvidia-580xx akmod-nvidia-470xx akmod-nvidia-390xx; then
ok "NVIDIA: driver packages installed; the kmod builds at the next boot"
else
ok "NVIDIA: none (no supported card, --nvidia=no, or no card present)"
fi
DM=$(detect_display_manager) DM=$(detect_display_manager)
ok "Display manager: ${DM:-none}" ok "Display manager: ${DM:-none}"