Make the progress bar follow the transaction it was supposed to be watching

It sat at "0 of 218 items" for an entire run. The watcher looked for
"(120/218) upgrading foo", which pacman only prints when it is drawing its
progress bar - and the unattended runs this exists for are precisely the ones
started with --noprogressbar, where it instead prints "upgrading glibc..." with
no counter at all. So nothing ever matched and the position never moved.

Both shapes are handled now. Where there is no counter the packages are tallied
here, one line each, and the total is read from the "Package (218)" header
pacman prints before it starts - a better number than checkupdates gives, since
that counts packages with an update available and knows nothing about new
dependencies pulled in alongside them. Replayed against a real 218-package run
from the log: 63, 137, 215, 218 of 218, with the package name in each step.

Offer to switch off CachyOS's reboot notification while enabling. cachyos-hooks
ships a PostTransaction hook that pops up "Reboot recommended!" the moment a
kernel, driver or systemd package is unpacked. That is fine for a manual
upgrade, where the transaction is the last thing happening. In an unattended
one it lands mid-run, with AUR packages still to build and Flatpaks still to
pull, and asking for a restart there is an invitation to cut the update in
half. Overriding the hook by name from /etc/pacman.d/hooks is the standard way
to switch a distribution hook off and is undone by deleting the link.

Offered rather than done, like the existing cachy-update prompt: it is another
package's behaviour and it applies to manual upgrades too.
This commit is contained in:
Felitendo committed 2026-08-14 11:34:03 +02:00
1 parent 6aa9b147b8
commit ea3f0d21fa
5 files changed
+117 -21

No files matched your search

+42
View File
@@ -84,6 +84,48 @@ cau_do_enable() {
fi
cau_offer_disable_cachy_update
cau_offer_disable_reboot_hook
}
# CachyOS ships a pacman hook that pops up "Reboot recommended!" the moment a
# kernel, driver or systemd package is unpacked. During a manual upgrade that
# is fine - the transaction is the last thing happening. During an unattended
# one it lands in the middle: the run still has AUR packages to build and
# Flatpaks to pull, and a notification asking for a restart right then is an
# invitation to cut the update in half.
#
# pacman lets a hook be overridden by name from /etc/pacman.d/hooks, which takes
# precedence over /usr/share/libalpm/hooks, and a symlink to /dev/null there
# disables it. That is the standard way to switch off a distribution hook, and
# it is undone by deleting the link.
#
# This one is offered, never decided: it is another package's behaviour and it
# applies to manual upgrades too.
CAU_REBOOT_HOOK="cachyos-reboot-required.hook"
cau_offer_disable_reboot_hook() {
local system="/usr/share/libalpm/hooks/$CAU_REBOOT_HOOK"
local override="/etc/pacman.d/hooks/$CAU_REBOOT_HOOK"
local answer
[[ -t 0 && -t 1 ]] || return 0
[[ -f $system ]] || return 0
[[ -e $override || -L $override ]] && return 0
printf '\n %s\n ' \
"$(cau_msg "CachyOS shows a \"Reboot recommended\" notification while an update is still running. Turn it off? [Y/n]")"
read -r answer || return 0
case "${answer,,}" in
''|y|yes|j|ja) ;;
*) return 0 ;;
esac
if mkdir -p /etc/pacman.d/hooks 2>/dev/null && ln -sfn /dev/null "$override" 2>/dev/null; then
cau_ok "$(cau_msg "The reboot notification has been turned off. Undo it by deleting %s." "$override")"
else
cau_bad "$(cau_msg "Could not write %s." "$override")"
fi
}
# Whether an AUR helper exists at all, without the logging cau_aur_detect does.
+52 -20
View File
@@ -27,37 +27,69 @@ cau_pacman_flags() {
done
}
# The verbs pacman puts in front of a package as it works through a
# transaction. Matched against English on purpose: the runner forces LC_ALL=C
# precisely so pacman's output stays parseable.
CAU_PACMAN_OP_RE='^(\([[:space:]]*[0-9]+/[0-9]+\) )?(upgrading|installing|reinstalling|downgrading|removing) [^[:space:]]+'
# _cau_pacman_progress_watch <logfile>
# Feeds the desktop's progress bar from pacman's own transaction counter.
# --noprogressbar makes pacman print one "(120/260) upgrading foo" line per
# package, and that is the only live measure of how far a transaction has got:
# checkupdates knows the total beforehand, but nothing else knows the position.
# Feeds the desktop's progress bar by watching pacman work.
#
# The other (n/m) sequences pacman prints - checking keys in keyring, checking
# package integrity, loading package files - are deliberately not matched. Each
# counts up to the same total, so following them would run the bar to the end
# three times over before the first package was unpacked.
# pacman announces each package twice over, in one of two shapes, and which one
# depends on a flag this program sets itself:
#
# Read from the log by polling rather than from a pipe: the log is written
# either by pacman directly or through tee, depending on whether a person is
# watching, and one reader that works for both is worth more here than the
# second or so of latency it costs.
# upgrading glibc... with --noprogressbar, i.e. every timer run
# ( 12/218) upgrading glibc [##] with the bar, i.e. an interactive `run`
#
# Only the second carries a counter, and the unattended runs that this bar
# exists for are exactly the ones that do not get it. So the position is
# counted here instead - one line per package - and the total taken from the
# "Package (218)" header pacman prints before it starts. That header is the
# better number anyway: checkupdates counts packages with an update available
# and knows nothing about the new dependencies pulled in alongside them.
#
# What must not be counted is the other (n/m) sequence pacman prints, for
# hooks and for checking keys, integrity and file conflicts. Each of those runs
# up to its own total, so following them would drive the bar to the end several
# times before the first package was unpacked. Requiring one of the verbs above
# is what excludes them.
#
# Read by polling the log rather than from a pipe: the log is written either by
# pacman directly or through tee depending on whether a person is watching, and
# one reader that works for both is worth the second of latency it costs.
_cau_pacman_progress_watch() {
local log="$1" line last='' pkg
local log="$1"
local total="${CAU_PACMAN_COUNT:-0}" announced processed line pkg last=''
while :; do
sleep 1
line="$(grep -aoE '^\([[:space:]]*[0-9]+/[0-9]+\) (upgrading|installing|reinstalling|downgrading|removing) [^[:space:]]+' \
"$log" 2>/dev/null | tail -n1)"
[[ -n $line && $line != "$last" ]] || continue
announced="$(grep -aoE '^Packages? \([0-9]+\)' "$log" 2>/dev/null \
| head -n1 | grep -oE '[0-9]+')"
[[ $announced =~ ^[0-9]+$ ]] && (( announced > 0 )) && total="$announced"
line="$(grep -aoE "$CAU_PACMAN_OP_RE" "$log" 2>/dev/null | tail -n1)"
[[ -n $line ]] || continue
# Nothing new since the last look. Checked before the counting grep
# because on a large upgrade this loop spends most of its life here.
[[ $line != "$last" ]] || continue
last="$line"
[[ $line =~ ^\([[:space:]]*([0-9]+)/([0-9]+)\)[[:space:]]+[a-z]+[[:space:]]+(.+)$ ]] || continue
processed="$(grep -acE "$CAU_PACMAN_OP_RE" "$log" 2>/dev/null)"
[[ $processed =~ ^[0-9]+$ ]] || continue
cau_progress_item "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
pkg="${BASH_REMATCH[3]%...}"
cau_progress_detail "Package" "$pkg"
# Where pacman does carry a counter, believe it over the tally: it is
# the same number, but it also knows the true total.
if [[ $line =~ ^\([[:space:]]*([0-9]+)/([0-9]+)\) ]]; then
processed="${BASH_REMATCH[1]}"
total="${BASH_REMATCH[2]}"
fi
cau_progress_item "$processed" "$total"
pkg="${line##* }"
cau_progress_detail "Package" "${pkg%...}"
done
}