Quiet dnf transactions: spinner + one-line summary, log on failure

ui_quiet_run wraps every install_packages/install_optional/group
install/remove and the system update: while gum is available and the
run is interactive, dnf output is hidden behind a spinner, then a
single 'Installing: <pkgs> (N packages)' line. Full output is appended
to $SETUP_DNF_LOG (/tmp/fedora-setup-dnf-<uid>.log); on failure the
last 20 lines print in red so nothing is lost. Unattended and no-gum
runs keep plain full output (logs/cron contract).

Also: fallback ui_choose accepts 'x' to exit (muscle memory from the
old menu), fixed a stray stdout newline leaking into captured answers
(same class as the earlier '\nno' bug), fixed pluralization.

Harness: piped fallback, pty gum run incl. a failing VirtualBox pick
(red failure + log tail, menu survives, subsequent qemu install ok),
unattended regression.
This commit is contained in:
Pathfinder
2026-10-27 19:20:31 +01:00
parent c6e41cbf1e
commit 9dd1ecada8
3 changed files with 63 additions and 19 deletions
+50 -6
View File
@@ -71,16 +71,60 @@ pause_if_debug() {
#------------------------------------------
# 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
if ! have_gum; then
"$@"
return $?
fi
before=$(wc -l < "$log" 2>/dev/null || echo 0)
if LOGFILE="$log" gum spin --title "$msg" -- \
bash -c 'exec "$@" >>"$LOGFILE" 2>&1' _ "$@"; then
n=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null | grep -c '^Installing:' || true)
local plural="s"
(( n == 1 )) && plural=""
ok "$msg (${n} package${plural})"
return 0
else
local rc=$?
color_red " Failed: $msg"
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() {
# -y : no prompts
# 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.
# Use for anything that is a nice-to-have rather than load-bearing.
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: $*"
}
@@ -114,8 +158,8 @@ remove_packages() {
done
if [ ${#packages_to_remove[@]} -gt 0 ]; then
info "Removing packages: ${packages_to_remove[*]}"
sudo dnf remove -y "${packages_to_remove[@]}"
ui_quiet_run "Removing: ${packages_to_remove[*]}" \
sudo dnf remove -y "${packages_to_remove[@]}"
fi
}
@@ -129,7 +173,7 @@ install_group() {
dnf group list --hidden 2>/dev/null | sed 's/^/ /' >&2 || true
return 1
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.
@@ -138,7 +182,7 @@ install_group_any() {
for g in "$@"; do
if dnf group info "$g" >/dev/null 2>&1; then
info "Using dnf group: @$g"
sudo dnf group install -y "$g"
ui_quiet_run "Installing group: @$g" sudo dnf group install -y "$g"
return 0
fi
done