From a54f14018e2c408c22dc32e7f443b0f3f4c37b59 Mon Sep 17 00:00:00 2001 From: Pathfinder Date: Tue, 27 Oct 2026 19:31:22 +0100 Subject: [PATCH] Live progress line instead of gum spin (VM: frozen block, 'looks hung') gum spin rendered static on the real VM during long transactions. ui_quiet_run now draws its own line: braille spinner frame + the package dnf is currently working on (from the transaction log), at 150ms ticks. Works without gum (pure bash), gated on interactive terminal, not on gum. set -e + pipefail bug found via pty trace: the first tick races the child's first log line, grep exits 1 (no match) and pipefail turns the whole substitution nonzero, killing the script silently at the first frame. || true on the substitution pipeline fixes it. --- lib/common.sh | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/common.sh b/lib/common.sh index adb408d..554fac8 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -91,21 +91,49 @@ ui_quiet_run() { local log="${SETUP_DNF_LOG:-/tmp/fedora-setup-dnf.log}" local before n - if ! have_gum; then + # 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) - if LOGFILE="$log" gum spin --title "$msg" -- \ - bash -c 'exec "$@" >>"$LOGFILE" 2>&1' _ "$@"; then + + # Child writes to the log; we draw a LIVE line: spinner frame + the package + # dnf is currently working on. gum spin was tried here first but rendered + # as a frozen block on the real VM ("looks like it hangs") — this variant + # animates independently of gum and shows real progress, not just motion. + "$@" >>"$log" 2>&1 & + local pid=$! + local frames=( '⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏' ) + local f=0 cur + while kill -0 "$pid" 2>/dev/null; do + cur=$(sed -n "$((before+1)),\$p" "$log" 2>/dev/null \ + | grep -E '^(Installing|Upgrading|Downgrading|Removing|Verifying|Preparing|Cleanup):' \ + | tail -n 1 || true) + if [[ -n "$cur" ]]; then + printf '\r %s %s — %s' "${frames[$f]}" "$msg" "$cur" >&2 + else + printf '\r %s %s' "${frames[$f]}" "$msg" >&2 + fi + f=$(( (f + 1) % ${#frames[@]} )) + sleep 0.15 + done + + local rc=0 + wait "$pid" || rc=$? + # wipe the progress line before printing the result + printf '\r%*s\r' 100 '' >&2 + + if (( rc == 0 )); 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