diff --git a/Makefile b/Makefile index 8562aca..8d68280 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ # Overridable so a packager can pass the version it is actually building # (`make VERSION=$pkgver`). The literal below is the fallback for builds # straight from a checkout, and is what a release tag has to carry. -VERSION ?= 1.2.0 +VERSION ?= 1.2.1 PREFIX ?= /usr DESTDIR ?= diff --git a/po/cachy-auto-update.pot b/po/cachy-auto-update.pot index d3b7061..ddb4e36 100644 --- a/po/cachy-auto-update.pot +++ b/po/cachy-auto-update.pot @@ -174,6 +174,17 @@ msgstr "" msgid "cachy-update's update check has been disabled." msgstr "" +msgid "CachyOS shows a \"Reboot recommended\" notification while an update is still running. Turn it off? [Y/n]" +msgstr "" + +#, c-format +msgid "The reboot notification has been turned off. Undo it by deleting %s." +msgstr "" + +#, c-format +msgid "Could not write %s." +msgstr "" + msgid "No log yet." msgstr "" diff --git a/po/de.po b/po/de.po index 5633b44..31925a0 100644 --- a/po/de.po +++ b/po/de.po @@ -175,6 +175,17 @@ msgstr "cachy-update benachrichtigt ebenfalls über verfügbare Updates. Dessen msgid "cachy-update's update check has been disabled." msgstr "Die Update-Prüfung von cachy-update wurde abgeschaltet." +msgid "CachyOS shows a \"Reboot recommended\" notification while an update is still running. Turn it off? [Y/n]" +msgstr "CachyOS zeigt eine Benachrichtigung \"Neustart empfohlen\", während ein Update noch läuft. Abschalten? [J/n]" + +#, c-format +msgid "The reboot notification has been turned off. Undo it by deleting %s." +msgstr "Die Neustart-Benachrichtigung wurde abgeschaltet. Rückgängig durch Löschen von %s." + +#, c-format +msgid "Could not write %s." +msgstr "%s konnte nicht geschrieben werden." + msgid "No log yet." msgstr "Noch kein Protokoll vorhanden." diff --git a/src/cachy-auto-update b/src/cachy-auto-update index 0e71b4f..030c1b4 100644 --- a/src/cachy-auto-update +++ b/src/cachy-auto-update @@ -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. diff --git a/src/lib/pkg_pacman.sh b/src/lib/pkg_pacman.sh index eec8b8b..34098d1 100644 --- a/src/lib/pkg_pacman.sh +++ b/src/lib/pkg_pacman.sh @@ -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 -# 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 }