From 9dd1ecada83c0ba8bc349af5733d5972f1efbca9 Mon Sep 17 00:00:00 2001 From: Pathfinder Date: Tue, 27 Oct 2026 19:20:31 +0100 Subject: [PATCH] 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: (N packages)' line. Full output is appended to $SETUP_DNF_LOG (/tmp/fedora-setup-dnf-.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. --- lib/common.sh | 56 +++++++++++++++++++++++++++++++++++++++++++++------ lib/ui.sh | 18 +++++++---------- setup.sh | 8 ++++++-- 3 files changed, 63 insertions(+), 19 deletions(-) diff --git a/lib/common.sh b/lib/common.sh index 5b22c1d..adb408d 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -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 diff --git a/lib/ui.sh b/lib/ui.sh index 57b7f50..d78765f 100644 --- a/lib/ui.sh +++ b/lib/ui.sh @@ -16,16 +16,7 @@ if [[ -n "${_SB_UI_LOADED:-}" ]]; then fi _SB_UI_LOADED=1 -have_gum() { - 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 -} +# have_gum lives in lib/common.sh (install_packages needs it too). # Single choice from a list. # ui_choose "
" "" "" item1 item2 ... @@ -62,7 +53,8 @@ ui_choose() { local n=${#items[@]} answer while true; do if ! read -t 90 -rp "$prompt [1-$n] (default: shown above): " answer; then - printf "\n"; answer="" + printf "\n" >&2 + answer="" fi # empty/timeout -> the default item (its position in the list) if [[ -z "$answer" ]]; then @@ -74,6 +66,10 @@ ui_choose() { echo "${items[-1]}" return 0 fi + if [[ "${answer,,}" == "x" ]]; then + echo "$default" + return 0 + fi if [[ "$answer" =~ ^[0-9]+$ ]] && (( answer >= 1 && answer <= n )); then echo "${items[$((answer-1))]}" return 0 diff --git a/setup.sh b/setup.sh index d5ea316..d04e092 100755 --- a/setup.sh +++ b/setup.sh @@ -13,6 +13,10 @@ source "$SCRIPT_DIR/lib/session.sh" # shellcheck source=lib/ui.sh source "$SCRIPT_DIR/lib/ui.sh" +# Full dnf output from quiet transactions lands here (shown on failure). +export SETUP_DNF_LOG="${SETUP_DNF_LOG:-/tmp/fedora-setup-dnf-$(id -u).log}" +: >> "$SETUP_DNF_LOG" + #------------------------------------------ # Defaults + argument parsing # DEBUG_MODE must be initialised before anything reads it under set -u. @@ -185,8 +189,8 @@ fi info "Ensuring Flathub is available (system-wide)..." sudo flatpak remote-add --if-not-exists --system flathub https://dl.flathub.org/repo/flathub.flatpakrepo -# Update the system (spinner when gum is present; output shown on failure) -ui_run "Updating the system" sudo dnf update -y +# Update the system (spinner + one-line summary; full output in $SETUP_DNF_LOG) +ui_quiet_run "Updating the system" sudo dnf update -y pause_if_debug #------------------------------------------