Compare commits
41
Commits
c6e41cbf1e
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
482bd797d0 | ||
|
|
f08abda392 | ||
|
|
6733cde613 | ||
|
|
64fff4f200 | ||
|
|
17d09d985e | ||
|
|
fec520be9d | ||
|
|
065a8cd0ae | ||
|
|
e6698cdb44 | ||
|
|
88bd9d6a20 | ||
|
|
d1031277bf | ||
|
|
ee10829452 | ||
|
|
1586e10df1 | ||
|
|
4e0dad3483 | ||
|
|
b79735d82e | ||
|
|
8fb78651c9 | ||
|
|
c9a127a07e | ||
|
|
86358466e1 | ||
|
|
35c1939f28 | ||
|
|
a8796467df | ||
|
|
3590c1b477 | ||
|
|
d4f04ac486 | ||
|
|
a5e4facf42 | ||
|
|
835b6b0040 | ||
|
|
cb60610942 | ||
|
|
ea50f64ef2 | ||
|
|
82b35e4bdb | ||
|
|
85ba9ddb51 | ||
|
|
0630acf020 | ||
|
|
4a53155c7e | ||
|
|
71780291a3 | ||
|
|
3d21a1c427 | ||
|
|
35906d4df0 | ||
|
|
9c0b716758 | ||
|
|
49ea36e089 | ||
|
|
8595e3d721 | ||
|
|
1869743d4a | ||
|
|
3e23ce1210 | ||
|
|
4cf680b854 | ||
|
|
0050f3a4a8 | ||
|
|
a54f14018e | ||
|
|
9dd1ecada8 |
@@ -34,6 +34,8 @@ 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
|
||||||
@@ -75,6 +77,41 @@ 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,
|
||||||
|
|||||||
+163
-10
@@ -71,16 +71,157 @@ pause_if_debug() {
|
|||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
# Package helpers (dnf5)
|
# Package helpers (dnf5)
|
||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
|
# True when gum is installed AND we are interactive (stdin tty + /dev/tty
|
||||||
|
# writable). stdout must NOT be checked: it is a pipe inside $(...).
|
||||||
|
# Unattended runs never get the TUI (see ui_quiet_run).
|
||||||
|
have_gum() {
|
||||||
|
command -v gum >/dev/null 2>&1 || return 1
|
||||||
|
[[ "${ASSUME_YES:-false}" == "true" ]] && return 1
|
||||||
|
[ -t 0 ] || return 1
|
||||||
|
[ -w /dev/tty ] || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Quiet dnf transaction: spinner while it runs, one ok-line after, full output
|
||||||
|
# appended to $SETUP_DNF_LOG and printed (tail) only on failure. Unattended or
|
||||||
|
# no-gum runs execute plainly with normal output (logs matter there).
|
||||||
|
ui_quiet_run() {
|
||||||
|
local msg="$1"
|
||||||
|
shift
|
||||||
|
local log="${SETUP_DNF_LOG:-/tmp/fedora-setup-dnf.log}"
|
||||||
|
local before n
|
||||||
|
|
||||||
|
# Live-quiet mode needs only an interactive terminal (stdin tty + writable
|
||||||
|
# /dev/tty), NOT gum — the progress line is pure bash. Unattended runs and
|
||||||
|
# pipes keep plain full output (logs/cron contract).
|
||||||
|
if [[ "${ASSUME_YES:-false}" == "true" ]] || [ ! -t 0 ] || [ ! -w /dev/tty ]; then
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
before=$(wc -l < "$log" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
# ---- status BLOCK (TTY-safe: a background-coloured SPACE, never a glyph) --
|
||||||
|
# busy: blue block that breathes (bg-colour ramp) + current dnf action
|
||||||
|
# ok : solid green block + package count
|
||||||
|
# fail: solid RED block (spec change from the X, per YuKu)
|
||||||
|
# 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 &
|
||||||
|
local pid=$!
|
||||||
|
local f=0 cur
|
||||||
|
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 \
|
||||||
|
| grep -aE '^Installing|^Upgrading|^Downgrading|^Removing|^Verifying|^Preparing|^Cleanup' \
|
||||||
|
| tail -n 1 \
|
||||||
|
| 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
|
||||||
|
line="$msg: $cur"
|
||||||
|
else
|
||||||
|
line="$msg"
|
||||||
|
fi
|
||||||
|
if (( ${#line} > cols - 2 )); then
|
||||||
|
line="${line:0:cols-4}.."
|
||||||
|
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
|
||||||
|
|
||||||
|
local rc=0
|
||||||
|
wait "$pid" || rc=$?
|
||||||
|
# ANSI erase-line: wipes the live line without leaving trailing spaces
|
||||||
|
# (a 100-space wipe left a visible gap when the summary was shorter)
|
||||||
|
printf '\r\033[K' >&2
|
||||||
|
|
||||||
|
if (( rc == 0 )); then
|
||||||
|
n=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null | grep -ac '^Installing' || true)
|
||||||
|
local count_txt=""
|
||||||
|
if (( n > 0 )); then
|
||||||
|
local plural="s"
|
||||||
|
(( 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
|
||||||
|
else
|
||||||
|
if (( depth == 256 )); then
|
||||||
|
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:"
|
||||||
|
sed -n "$((before+1)),\$p" "$log" 2>/dev/null | tail -n 20 | sed 's/^/ /' >&2
|
||||||
|
return "$rc"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
install_packages() {
|
install_packages() {
|
||||||
# -y : no prompts
|
# -y : no prompts
|
||||||
# weak deps on : keep default behaviour, stated explicitly so it is greppable
|
# weak deps on : keep default behaviour, stated explicitly so it is greppable
|
||||||
sudo dnf install -y --setopt=install_weak_deps=True "$@"
|
ui_quiet_run "Installing: $*" \
|
||||||
|
sudo dnf install -y --setopt=install_weak_deps=True "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Same, but a missing/renamed package warns instead of aborting the whole run.
|
# 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.
|
# Use for anything that is a nice-to-have rather than load-bearing.
|
||||||
install_optional() {
|
install_optional() {
|
||||||
sudo dnf install -y --setopt=install_weak_deps=True "$@" \
|
ui_quiet_run "Installing (optional): $*" \
|
||||||
|
sudo dnf install -y --setopt=install_weak_deps=True "$@" \
|
||||||
|| warn "Optional packages not installed: $*"
|
|| warn "Optional packages not installed: $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +255,8 @@ remove_packages() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ ${#packages_to_remove[@]} -gt 0 ]; then
|
if [ ${#packages_to_remove[@]} -gt 0 ]; then
|
||||||
info "Removing packages: ${packages_to_remove[*]}"
|
ui_quiet_run "Removing: ${packages_to_remove[*]}" \
|
||||||
sudo dnf remove -y "${packages_to_remove[@]}"
|
sudo dnf remove -y "${packages_to_remove[@]}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +270,7 @@ install_group() {
|
|||||||
dnf group list --hidden 2>/dev/null | sed 's/^/ /' >&2 || true
|
dnf group list --hidden 2>/dev/null | sed 's/^/ /' >&2 || true
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
sudo dnf group install -y "$group"
|
ui_quiet_run "Installing group: @$group" sudo dnf group install -y "$group"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Try each candidate group/environment in turn; first one that exists wins.
|
# Try each candidate group/environment in turn; first one that exists wins.
|
||||||
@@ -138,7 +279,7 @@ install_group_any() {
|
|||||||
for g in "$@"; do
|
for g in "$@"; do
|
||||||
if dnf group info "$g" >/dev/null 2>&1; then
|
if dnf group info "$g" >/dev/null 2>&1; then
|
||||||
info "Using dnf group: @$g"
|
info "Using dnf group: @$g"
|
||||||
sudo dnf group install -y "$g"
|
ui_quiet_run "Installing group: @$g" sudo dnf group install -y "$g"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -150,9 +291,17 @@ 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; then
|
if pkg_installed terra-release || repo_enabled terra; then
|
||||||
ok "Terra repository already installed."
|
ok "Terra repository already present."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
info "Adding Terra repository (Fyra Labs)..."
|
info "Adding Terra repository (Fyra Labs)..."
|
||||||
@@ -166,8 +315,9 @@ 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; then
|
if pkg_installed rpmfusion-free-release rpmfusion-nonfree-release \
|
||||||
ok "RPM Fusion already installed."
|
|| { repo_enabled rpmfusion-free && repo_enabled rpmfusion-nonfree; }; then
|
||||||
|
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}..."
|
||||||
@@ -232,6 +382,9 @@ 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
@@ -0,0 +1,118 @@
|
|||||||
|
#!/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
|
||||||
@@ -16,16 +16,7 @@ if [[ -n "${_SB_UI_LOADED:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
_SB_UI_LOADED=1
|
_SB_UI_LOADED=1
|
||||||
|
|
||||||
have_gum() {
|
# have_gum lives in lib/common.sh (install_packages needs it too).
|
||||||
command -v gum >/dev/null 2>&1 || return 1
|
|
||||||
# stdin must be interactive — it is inherited by $(...) subshells, so
|
|
||||||
# menus still get the real terminal. stdout must NOT be checked: it is
|
|
||||||
# a pipe by definition inside $(ui_choose ...). gum draws its UI on
|
|
||||||
# /dev/tty and prints only the answer to stdout (which we capture).
|
|
||||||
[ -t 0 ] || return 1
|
|
||||||
[ -w /dev/tty ] || return 1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Single choice from a list.
|
# Single choice from a list.
|
||||||
# ui_choose "<header>" "<prompt>" "<default>" item1 item2 ...
|
# ui_choose "<header>" "<prompt>" "<default>" item1 item2 ...
|
||||||
@@ -62,7 +53,8 @@ ui_choose() {
|
|||||||
local n=${#items[@]} answer
|
local n=${#items[@]} answer
|
||||||
while true; do
|
while true; do
|
||||||
if ! read -t 90 -rp "$prompt [1-$n] (default: shown above): " answer; then
|
if ! read -t 90 -rp "$prompt [1-$n] (default: shown above): " answer; then
|
||||||
printf "\n"; answer=""
|
printf "\n" >&2
|
||||||
|
answer=""
|
||||||
fi
|
fi
|
||||||
# empty/timeout -> the default item (its position in the list)
|
# empty/timeout -> the default item (its position in the list)
|
||||||
if [[ -z "$answer" ]]; then
|
if [[ -z "$answer" ]]; then
|
||||||
@@ -74,6 +66,10 @@ ui_choose() {
|
|||||||
echo "${items[-1]}"
|
echo "${items[-1]}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
if [[ "${answer,,}" == "x" ]]; then
|
||||||
|
echo "$default"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
if [[ "$answer" =~ ^[0-9]+$ ]] && (( answer >= 1 && answer <= n )); then
|
if [[ "$answer" =~ ^[0-9]+$ ]] && (( answer >= 1 && answer <= n )); then
|
||||||
echo "${items[$((answer-1))]}"
|
echo "${items[$((answer-1))]}"
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -17,8 +17,34 @@ 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). Skipping."
|
ok "Dotfiles already set up ($CFG_DIR exists)."
|
||||||
|
ok "cfg alias ensured. Open a new shell or run: source ~/.bashrc"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,354 @@
|
|||||||
|
#!/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."
|
||||||
@@ -12,6 +12,14 @@ 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).
|
||||||
|
export SETUP_DNF_LOG="${SETUP_DNF_LOG:-/tmp/fedora-setup-dnf-$(id -u).log}"
|
||||||
|
: >> "$SETUP_DNF_LOG"
|
||||||
|
|
||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
# Defaults + argument parsing
|
# Defaults + argument parsing
|
||||||
@@ -24,6 +32,7 @@ TWM=""
|
|||||||
SELINUX_OPT=""
|
SELINUX_OPT=""
|
||||||
SNAPPER_OPT=""
|
SNAPPER_OPT=""
|
||||||
DOTFILES_OPT=""
|
DOTFILES_OPT=""
|
||||||
|
NVIDIA_OPT=""
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -40,6 +49,8 @@ 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
|
||||||
@@ -64,6 +75,7 @@ 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 ;;
|
||||||
*)
|
*)
|
||||||
@@ -99,6 +111,14 @@ if [[ ${EUID} -eq 0 ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
if [[ "$ASSUME_YES" == "true" && ( -z "$DE" || -z "$TWM" ) ]]; then
|
||||||
err "--yes requires explicit --de and --twm, so nothing is guessed on an"
|
err "--yes requires explicit --de and --twm, so nothing is guessed on an"
|
||||||
err "unattended run. Example:"
|
err "unattended run. Example:"
|
||||||
@@ -176,6 +196,9 @@ 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
|
||||||
@@ -185,8 +208,8 @@ fi
|
|||||||
info "Ensuring Flathub is available (system-wide)..."
|
info "Ensuring Flathub is available (system-wide)..."
|
||||||
sudo flatpak remote-add --if-not-exists --system flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
sudo flatpak remote-add --if-not-exists --system flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||||
|
|
||||||
# Update the system (spinner when gum is present; output shown on failure)
|
# Update the system (spinner + one-line summary; full output in $SETUP_DNF_LOG)
|
||||||
ui_run "Updating the system" sudo dnf update -y
|
ui_quiet_run "Updating the system" sudo dnf update -y
|
||||||
pause_if_debug
|
pause_if_debug
|
||||||
|
|
||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
@@ -208,7 +231,7 @@ if [[ -z "$SELINUX_OPT" ]]; then
|
|||||||
[[ "$choice" == "yes" ]] && SELINUX_DISABLED=true
|
[[ "$choice" == "yes" ]] && SELINUX_DISABLED=true
|
||||||
printf "\n"
|
printf "\n"
|
||||||
if findmnt -n -o FSTYPE / 2>/dev/null | grep -q btrfs; then
|
if findmnt -n -o FSTYPE / 2>/dev/null | grep -q btrfs; then
|
||||||
ok "Root filesystem is btrfs — snapshots + zstd compression will be set up."
|
ok "Root filesystem is btrfs: snapshots + zstd compression will be set up."
|
||||||
BTRFS_SNAPPER=true
|
BTRFS_SNAPPER=true
|
||||||
else
|
else
|
||||||
info "Root filesystem is not btrfs; skipping snapshot setup."
|
info "Root filesystem is not btrfs; skipping snapshot setup."
|
||||||
@@ -239,6 +262,15 @@ 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
|
||||||
|
|
||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
@@ -247,7 +279,7 @@ pause_if_debug
|
|||||||
if [[ -z "$DE" ]]; then
|
if [[ -z "$DE" ]]; then
|
||||||
step "4 Desktop environment"
|
step "4 Desktop environment"
|
||||||
rule
|
rule
|
||||||
color_gray " Bare Fedora install — picking a DE is what gives you a graphical login."
|
color_gray "Bare Fedora install: picking a DE is what gives you a graphical login."
|
||||||
choice=$(ui_choose "Desktop environment" "Select desktop environment:" \
|
choice=$(ui_choose "Desktop environment" "Select desktop environment:" \
|
||||||
"None (TWM only, SDDM via TWM module)" \
|
"None (TWM only, SDDM via TWM module)" \
|
||||||
"GNOME (gdm)" "Plasma (sddm)" "XFCE (sddm)" "None (TWM only, SDDM via TWM module)")
|
"GNOME (gdm)" "Plasma (sddm)" "XFCE (sddm)" "None (TWM only, SDDM via TWM module)")
|
||||||
@@ -346,6 +378,14 @@ 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
|
||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
@@ -355,22 +395,65 @@ 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"
|
"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 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
|
||||||
install_packages $category
|
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
|
done
|
||||||
ok "Always-packages installed."
|
if [[ -z "$always_failed" ]]; then
|
||||||
|
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)
|
||||||
#------------------------------------------
|
#------------------------------------------
|
||||||
@@ -380,6 +463,9 @@ 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=(
|
||||||
@@ -393,6 +479,22 @@ 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
|
||||||
@@ -426,38 +528,43 @@ 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
|
||||||
|
|
||||||
# Map chosen labels back to their package lists. Word-split each
|
# Resolve each picked label to its OWN package list, then install
|
||||||
# package list into SEPARATE args: quoting it whole makes dnf look for
|
# PER ITEM: one failing package used to kill the whole multi-select
|
||||||
# one package named "qemu-kvm libvirt ..." (seen on the VM).
|
# transaction (VM: brave's no-match took chromium down with it).
|
||||||
local -a to_install=()
|
# Repo wiring inside install_category_pkgs runs per item; failures
|
||||||
|
# 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
|
||||||
[[ -z "$picked" ]] && continue
|
[[ -n "$picked" ]] && picked_items+=("$picked")
|
||||||
|
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%%:*}"
|
||||||
if [[ "$lbl" == "$picked" ]]; then
|
[[ "$lbl" == "$picked" ]] || continue
|
||||||
rest="${entry#*:}"; rest="${rest#*:}" # drop id and label
|
rest="${rest#*:}" # drop id and label -> pkgs
|
||||||
# shellcheck disable=SC2206
|
# word-split: quoting $rest whole makes dnf look for one
|
||||||
to_install+=(${rest})
|
# 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
|
fi
|
||||||
done
|
done
|
||||||
done <<< "$sel"
|
done
|
||||||
|
|
||||||
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
|
||||||
@@ -469,6 +576,35 @@ 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
|
||||||
@@ -493,16 +629,26 @@ install_category_pkgs() {
|
|||||||
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..."
|
||||||
sudo dnf install -y fedora-workstation-repositories 2>/dev/null || true
|
install_optional fedora-workstation-repositories
|
||||||
sudo dnf config-manager setopt google-chrome.enabled=1 2>/dev/null || \
|
if ! sudo dnf config-manager setopt google-chrome.enabled=1 >/dev/null 2>&1; then
|
||||||
sudo dnf install -y "https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm"
|
# repo enable failed: install straight from Google's 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
|
||||||
info "Adding Brave repo..."
|
# dnf5 (Fedora 41+) has no 'config-manager add' subcommand —
|
||||||
sudo dnf install -y dnf-plugins-core
|
# the VM showed 'Unknown argument "add"', so the repo never
|
||||||
sudo dnf config-manager add --from-repofile=https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo || \
|
# landed and brave-browser had no match. Correct form per
|
||||||
true
|
# Brave's own F41+ docs: addrepo. The .repo carries the gpgkey,
|
||||||
sudo rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc 2>/dev/null || true
|
# so no separate rpm --import is needed.
|
||||||
|
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
|
||||||
@@ -519,7 +665,169 @@ 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
|
||||||
@@ -545,6 +853,15 @@ 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}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user