docs: add claude.md and avoid dashes

This commit is contained in:
Felitendo committed 2026-09-21 14:00:02 +02:00
1 parent dbdbfe5cd5
commit c40dfe0238
22 files changed
+205 -158

No files matched your search

+6 -6
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# cachy-auto-update - unattended updates for CachyOS
# cachy-auto-update: unattended updates for CachyOS
#
# The command line front end. Everything it does is either flipping a switch in
# /etc/cachy-auto-update/cachy-auto-update.conf, driving the systemd timer, or
@@ -77,10 +77,10 @@ cau_do_enable() {
cau_config_load
if [[ $CFG_AUR == yes ]] && ! cau_aur_detect_quiet; then
cau_note "$(cau_msg "No AUR helper found - install paru or yay for AUR updates.")"
cau_note "$(cau_msg "No AUR helper found. Install paru or yay for AUR updates.")"
fi
if [[ $CFG_AUR == yes ]] && ! cau_have makepkg; then
cau_note "$(cau_msg "base-devel is missing - AUR packages cannot be built without it.")"
cau_note "$(cau_msg "base-devel is missing, so AUR packages cannot be built.")"
fi
cau_offer_disable_cachy_update
@@ -89,7 +89,7 @@ cau_do_enable() {
# 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
# is fine, because 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.
@@ -141,7 +141,7 @@ cau_aur_detect_quiet() {
# cachy-update ships an enabled user timer that only ever says "N updates
# available". Once updates apply themselves that notification is pure noise,
# so offer to silence it - but only ask, never decide.
# so offer to silence it. But only ask, never decide.
cau_offer_disable_cachy_update() {
local user uid answer found=0
@@ -163,7 +163,7 @@ cau_offer_disable_cachy_update() {
read -r answer || return 0
# Defaults to yes: once updates install themselves, cachy-update's "N
# updates available" is purely noise. "j" is accepted too - the prompt is
# updates available" is purely noise. "j" is accepted too: the prompt is
# translated, so a German user types the German letter and used to have
# that silently read as "no".
case "${answer,,}" in
+6 -6
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# cachy-auto-update-progress - the update's progress bar on the desktop
# cachy-auto-update-progress: the update's progress bar on the desktop
#
# Started by the update runner, once per logged-in user, inside that user's
# session. Reads one instruction per line on stdin and turns it into the same
@@ -16,7 +16,7 @@
#
# Why a separate process at all: the desktop ties the progress entry to the
# D-Bus connection that asked for it and withdraws the entry the moment that
# connection goes away. One-shot callers - gdbus, busctl, dbus-send - therefore
# connection goes away. One-shot callers (gdbus, busctl, dbus-send) therefore
# cannot drive one, because each invocation is its own connection that closes
# again immediately. So something has to sit there and hold the connection open
# for as long as the update takes, and read its orders from somewhere else.
@@ -54,7 +54,7 @@ class Job:
reply = self.bus.call_sync(
JOB_SERVICE, JOB_PATH, "org.kde.JobViewServerV2", "requestView",
# capabilities 0: no cancel and no pause button. Neither can be
# honoured - pacman's commit phase is not interruptible - and a
# honoured, because pacman's commit phase is not interruptible. A
# button that does nothing is worse than no button.
GLib.Variant("(sia{sv})", (DESKTOP_ENTRY, 0, {})),
GLib.VariantType("(o)"), Gio.DBusCallFlags.NONE, -1, None)
@@ -68,8 +68,8 @@ class Job:
JOB_SERVICE, self.path, "org.kde.JobViewV2", method, variant,
None, Gio.DBusCallFlags.NONE, -1, None)
except GLib.Error:
# The desktop went away mid-update - a logout, or a plasmashell
# restart. The update carries on without a bar.
# The desktop went away mid-update (a logout, or a plasmashell
# restart). The update carries on without a bar.
self.path = None
def close(self, message=""):
@@ -96,7 +96,7 @@ def main():
try:
job.open()
except GLib.Error:
# No job server on this desktop - anything that is not Plasma. Same
# No job server on this desktop (anything that is not Plasma). Same
# deal as a missing binding: drain stdin, stay out of the way.
for _ in sys.stdin:
pass
+6 -6
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# cachy-auto-update-run - one unattended update pass
# cachy-auto-update-run: one unattended update pass
#
# Started by cachy-auto-update.service as root. The timer fires hourly and this
# decides whether anything should happen; that is what makes deferrals free.
@@ -43,7 +43,7 @@ fi
# Force a neutral locale here rather than relying on the unit's Environment=,
# so a run started by hand behaves exactly like one started by the timer.
# pacman failures are classified by matching its output, and on a German system
# that output is German. Text aimed at a person does not come through here - a
# that output is German. Text aimed at a person does not come through here: a
# notification is rendered in the recipient's own locale by cau_msg_in.
export LC_ALL=C LANGUAGE=
@@ -65,7 +65,7 @@ cau_log_open
# Record an interruption rather than leaving the previous run's verdict behind.
# Without this, killing an interactive run leaves last_result at whatever it was
# before - so the menu can keep reporting a problem from hours ago while the
# before. Then the menu can keep reporting a problem from hours ago while the
# machine is in fact fully up to date, which is worse than saying nothing.
# pacman makes the commit phase itself uninterruptible, so the packages either
# all landed or none did; only our own bookkeeping is at risk here.
@@ -193,8 +193,8 @@ progress_steps+=(cleanup)
cau_progress_begin "${progress_steps[@]}"
# And carry the run log's last lines along with it, so "Details" shows what the
# update is doing during the stretches that have nothing to count - an AUR
# package building for a quarter of an hour, most of all.
# update is doing during the stretches that have nothing to count. Most of all
# an AUR package building for a quarter of an hour.
cau_progress_tail_start "$CAU_RUNLOG"
if ! cau_pacman_update; then
@@ -273,7 +273,7 @@ fi
# Recorded, deliberately not announced. The running kernel loses its module
# tree the moment pacman unpacks the new one, so this turns true partway
# through a run that still has AUR builds and Flatpaks ahead of it - and a
# through a run that still has AUR builds and Flatpaks ahead of it. A
# "restart recommended" bubble arriving then reads as an invitation to restart
# while the update is still going. `cachy-auto-update status` and the menu say
# so instead, where nobody is being interrupted mid-transaction.
+7 -7
View File
@@ -49,8 +49,8 @@ export TEXTDOMAINDIR="${CAU_LOCALEDIR}"
#
# Standard POSIX precedence, deliberately: LC_ALL wins outright, and LC_ALL=C
# really does mean English. The service sets LC_ALL=C so that pacman and upower
# stay parseable, which makes the log English - correct, since the log is a
# technical artefact. Anything aimed at a person (a desktop notification) does
# stay parseable, which makes the log English. That is correct, since the log
# is a technical artefact. Anything aimed at a person (a desktop notification) does
# not go through here at all; it names the recipient's own locale explicitly
# via cau_msg_in and cau_user_locale.
cau_ui_locale() {
@@ -117,8 +117,8 @@ cau_msg_in() {
fi
# With no arguments the message is plain text, not a format string. Feeding
# it to printf anyway turns any literal percent sign in it - "Battery (%)",
# "100 % done" - into an invalid conversion, and that is a trap every
# it to printf anyway turns any literal percent sign in it (like
# "Battery (%)" or "100 % done") into an invalid conversion, and that is a trap every
# translator would eventually walk into.
if (( $# == 0 )); then
printf '%s' "$translated"
@@ -184,7 +184,7 @@ cau_log_open() {
# Runs a command, capturing its combined output in the run log. Returns the
# command's exit status.
#
# When a person is watching - `cachy-auto-update run` from a terminal - the
# When a person is watching (`cachy-auto-update run` from a terminal), the
# output is shown as well. Building an AUR package or pulling a few hundred
# megabytes of Flatpak can take minutes, and silence for that long is
# indistinguishable from a hang.
@@ -207,8 +207,8 @@ cau_run_logged() {
# Set by cau_bad and cau_note. The menu redraws immediately after an action,
# which would wipe the screen; this marks that something was printed that the
# user still has to read, so only those cases wait for a keypress. A plain
# success needs no acknowledgement - the status block at the top of the menu
# already shows the new state.
# success needs no acknowledgement, because the status block at the top of the
# menu already shows the new state.
CAU_UI_NEEDS_ACK=''
cau_say() { printf '%s\n' "$*"; }
+4 -4
View File
@@ -16,7 +16,7 @@ CAU_SKIP_REASON=''
# cau_on_ac
# True when running on mains power. systemd-ac-power also returns success when
# the machine has neither a battery nor an adapter, which is exactly right for
# desktops - hand-rolled sysfs globbing gets that case wrong.
# desktops. Hand-rolled sysfs globbing gets that case wrong.
cau_on_ac() {
if cau_have systemd-ac-power; then
systemd-ac-power > /dev/null 2>&1
@@ -41,8 +41,8 @@ cau_on_ac() {
# cau_battery_percent
# Average charge across the system batteries, or failure when the machine has
# none. Peripheral batteries (mice, headsets) advertise type=Battery too and
# are filtered out via the scope attribute; when scope is missing entirely -
# as on many laptops - the device counts as a system battery.
# are filtered out via the scope attribute. When scope is missing entirely (as
# on many laptops), the device counts as a system battery.
cau_battery_percent() {
local ps sum=0 count=0 cap
@@ -139,7 +139,7 @@ cau_gamemode_active() {
while read -r user uid; do
[[ -n $user ]] || continue
# timeout runs inside the runuser call because it has to be a real
# binary there - it cannot wrap a shell function from out here.
# binary there. It cannot wrap a shell function from out here.
out="$(cau_as_user "$user" "$uid" timeout 5 busctl --user --json=short \
get-property com.feralinteractive.GameMode \
/com/feralinteractive/GameMode \
+6 -6
View File
@@ -4,8 +4,8 @@
#
# The contract this file implements: the machine's owner may run any package
# manager at any time and must never see a lock error caused by us. We can only
# guarantee that in one direction - by never *starting* while somebody else is
# mid-transaction - so the checks here run before anything is touched, and a
# guarantee that in one direction: by never *starting* while somebody else is
# mid-transaction. So the checks here run before anything is touched, and a
# refusal simply defers the run to the next hourly tick.
# Package managers that take /var/lib/pacman/db.lck. checkupdates is absent on
@@ -65,7 +65,7 @@ cau_package_manager_busy() {
#
# The rigorous test is the boot time: no process that existed before the
# current boot can still be running, so a db.lck older than boot is abandoned
# by definition - which is exactly what a power cut during an update leaves
# by definition. That is exactly what a power cut during an update leaves
# behind. A lock that is merely unheld *within* this boot is not provable in
# the same way, so it is only reported (see cau_track_stale_lock) and never
# removed; guessing wrong there would corrupt a live transaction.
@@ -90,12 +90,12 @@ cau_pacman_lock_is_stale() {
# cau_recover_stale_lock
# Clears a provably abandoned lock so an interrupted update can be finished on
# the next run. Without this, one power cut during an update stops every future
# update permanently and silently - the worst possible outcome for a machine
# nobody is watching.
# update permanently and silently. That is the worst possible outcome for a
# machine nobody is watching.
cau_recover_stale_lock() {
cau_pacman_lock_is_stale || return 1
cau_warn "Found a pacman lock older than this boot - an update was cut short"
cau_warn "Found a pacman lock older than this boot. An update was cut short"
rm -f "$CAU_PACMAN_LOCK" 2>/dev/null || {
cau_error "Could not remove the stale pacman lock"
return 1
+8 -8
View File
@@ -32,7 +32,7 @@ cau_ui_term_restore() {
}
# Runs an action with the terminal handed back to normal line mode, so anything
# it prints - or prompts for - behaves the way a program expects.
# it prints or prompts for behaves the way a program expects.
cau_ui_cooked() {
cau_ui_term_restore
"$@"
@@ -213,8 +213,8 @@ _cau_setting_cycle() {
# A cursor list rather than a numbered menu: there are eighteen settings, and
# numbering them would run out of digits and force paging.
#
# The frame is assembled in memory and written once. Everything constant - the
# specs, the translated labels, the clear sequence - is resolved before the
# The frame is assembled in memory and written once. Everything constant (the
# specs, the translated labels, the clear sequence) is resolved before the
# loop, and the values are re-read only after something actually changes.
# Drawing the naive way cost a command substitution per label per frame, which
# measured 435 ms per keypress: arrow keys felt like the console was reloading,
@@ -239,7 +239,7 @@ cau_ui_settings() {
local title hint
cau_msg_into "$locale" "Settings"; title="$CAU_MSG_RESULT"
cau_msg_into "$locale" "Up/Down select - Space or Right changes - q goes back"
cau_msg_into "$locale" "Up/Down: select, Space or Right: change, q: back"
hint="$CAU_MSG_RESULT"
# the terminfo clear string, fetched once instead of forking per frame
@@ -320,8 +320,8 @@ cau_ui_edit_text() {
# _cau_row <label> <value>
# printf's %-28s pads by bytes, so a label containing "ü" comes out one column
# short. ${#s} counts characters in a UTF-8 locale, so the padding is computed
# here instead - and applied inline, because command substitution would eat the
# trailing spaces again.
# here instead. It is applied inline, because command substitution would eat
# the trailing spaces again.
_cau_row() {
local label="$1" value="$2" pad
pad=$(( 28 - ${#label} ))
@@ -389,7 +389,7 @@ cau_ui_status() {
if [[ $result == failed ]]; then
printf '\n %s%s%s\n' "$CAU_C_YELLOW" \
"$(cau_msg "The last run reported a problem - see 'cachy-auto-update log'.")" \
"$(cau_msg "The last run reported a problem. See 'cachy-auto-update log'.")" \
"$CAU_C_RESET"
elif [[ $result == interrupted ]]; then
printf '\n %s%s%s\n' "$CAU_C_YELLOW" \
@@ -510,7 +510,7 @@ cau_ui_menu() {
5) cau_ui_status_conditions; cau_pause ;;
6) cau_ui_settings ;;
q|Q) cau_ui_term_restore; trap - EXIT INT TERM; return 0 ;;
# Anything else - Enter, arrow keys, stray characters - just
# Anything else (Enter, arrow keys, stray characters) just
# redraws. Escape is deliberately not a quit key, so a mistyped
# arrow key cannot close the menu.
*) ;;
+7 -7
View File
@@ -3,10 +3,10 @@
# Desktop notifications from a root system service.
#
# Two paths exist:
# * live - somebody has a graphical session, so notify-send is run inside
# it via runuser with the session bus address set;
# * queued - nobody is logged in, so the message is appended to a spool that
# the XDG autostart entry replays at the next login.
# * live: somebody has a graphical session, so notify-send is run inside
# it via runuser with the session bus address set;
# * queued: nobody is logged in, so the message is appended to a spool that
# the XDG autostart entry replays at the next login.
#
# Messages travel as a msgid plus printf arguments rather than as finished
# text, so a notification queued at 04:00 is still rendered in whatever locale
@@ -44,8 +44,8 @@ cau_notify_close() {
# had to be skipped is only ever seen if it waits.
#
# Set explicitly rather than left to the server. Notification daemons do keep
# critical-urgency messages up - the spec asks them to, and Plasma obliges -
# but that is a "should", it says nothing about the normal-urgency messages
# critical-urgency messages up (the spec asks them to, and Plasma obliges).
# But that is a "should", it says nothing about the normal-urgency messages
# here that still need somebody to act, and urgency separately controls sound
# and whether do-not-disturb is overridden. Those are not the same question.
#
@@ -136,7 +136,7 @@ cau_notify_enqueue() {
printf '%s\n' "$record" >> "$CAU_NOTIFY_QUEUE" 2>/dev/null || return 0
# keep the spool bounded - nobody wants three weeks of backlog at login
# keep the spool bounded: nobody wants three weeks of backlog at login
if (( $(wc -l < "$CAU_NOTIFY_QUEUE" 2>/dev/null || echo 0) > CAU_NOTIFY_QUEUE_MAX )); then
tmp="$(mktemp "${CAU_NOTIFY_QUEUE}.XXXXXX")" || return 0
tail -n "$CAU_NOTIFY_QUEUE_MAX" "$CAU_NOTIFY_QUEUE" > "$tmp"
+3 -3
View File
@@ -4,14 +4,14 @@
#
# AppImages have no package manager of their own; Gear Lever is what tracks
# where each one came from and how to fetch a new build. Its CLI is a first
# class interface - `--update --all -y` is exactly the unattended entry point
# class interface: `--update --all -y` is exactly the unattended entry point
# we need, and it skips AppImages whose application is currently running rather
# than pulling the file out from under it (we deliberately do not pass
# --force).
#
# This only runs for users with a live graphical session: Gear Lever is a
# Flatpak GTK application and needs the session's runtime directory. Nothing is
# lost by waiting - the next hourly tick will catch it once they log in.
# lost by waiting, because the next hourly tick will catch it once they log in.
CAU_APPIMAGE_ID="it.mijorus.gearlever"
CAU_APPIMAGE_COUNT=0
@@ -45,7 +45,7 @@ cau_appimage_update() {
# Is there a Gear Lever on this machine at all? Asked before the step is
# announced rather than discovered inside the loop: on a machine without
# one - the common case, it is an optional dependency - a step that exists
# one (the common case, it is an optional dependency), a step that exists
# only to hand its share of the bar straight to the next one is a jump the
# bar does not need. Stops at the first user who has it, so the extra probe
# costs anything only in the case it is there to remove.
+3 -3
View File
@@ -2,15 +2,15 @@
#
# AUR packages.
#
# makepkg - and therefore paru and yay - refuse to run as root, so this is the
# makepkg (and therefore paru and yay) refuse to run as root, so this is the
# one part of the run that cannot happen in the service's own context. It is
# executed as the locked "cachy-auto-update" system account instead, which
# sysusers.d creates with no password and no shell. That account is granted
# NOPASSWD access to /usr/bin/pacman through /etc/sudoers.d/cachy-auto-update,
# which is what lets the helper install what it built without a human present.
#
# The alternative - stashing the user's password somewhere the daemon can read
# it - buys nothing: whatever can decrypt it is exactly what an attacker would
# The alternative (stashing the user's password somewhere the daemon can read
# it) buys nothing: whatever can decrypt it is exactly what an attacker would
# already have.
CAU_AUR_COUNT=0
+2 -2
View File
@@ -5,7 +5,7 @@
# System-wide installations are updated directly as root. That side-steps a
# real obstacle: the shipped polkit rule for Flatpak only grants install and
# uninstall, and only to a subject that is active, local and in the wheel
# group - none of which is true for an unattended service. Being root means
# group. None of that is true for an unattended service. Being root means
# polkit is never consulted in the first place.
#
# Per-user installations live in the user's home and are updated inside their
@@ -77,7 +77,7 @@ cau_flatpak_update() {
fi
done < <(cau_human_users)
# Unused runtimes are the Flatpak equivalent of orphaned packages - this is
# Unused runtimes are the Flatpak equivalent of orphaned packages. This is
# removal of installed software, not cache trimming, so it belongs behind
# RemoveOrphans rather than CleanCache.
if [[ $CFG_REMOVE_ORPHANS == yes ]]; then
+10 -10
View File
@@ -39,7 +39,7 @@ CAU_PACMAN_OP_RE='^(\([[:space:]]*[0-9]+/[0-9]+\) )?(upgrading|installing|reinst
#
# glibc-2.44+r24+g16be1518495f-1-x86_64_v3 downloading...
#
# and nothing else - no counter, no total - so the position here is counted the
# and nothing else: no counter, no total. So the position here is counted the
# same way the transaction is.
#
# The database sync a few lines earlier prints the very same shape (" core
@@ -55,7 +55,7 @@ CAU_PACMAN_DL_AWK='
# Feeds the desktop's progress bar by watching pacman work, through the three
# phases a pacman run has: first it works out what the upgrade consists of,
# then everything is fetched, then everything is unpacked. Three steps on the
# bar rather than one, because they are three stretches to sit through - and
# bar rather than one, because they are three stretches to sit through. And
# each one is silent in its own way.
#
# The first is the one that used to look like a hang. Between "starting full
@@ -73,7 +73,7 @@ CAU_PACMAN_DL_AWK='
#
# 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
# 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.
@@ -104,9 +104,9 @@ _cau_pacman_progress_watch() {
if [[ -n $line ]]; then
# Unpacking has started, so whatever came before it is over. If
# nothing was ever retrieved - every package already sitting in the
# cache, which is the ordinary state of affairs after a run that was
# interrupted once already - then the download step never happened,
# nothing was ever retrieved (every package already sitting in the
# cache, which is normal after a run that was interrupted once
# already), then the download step never happened,
# and it is dropped rather than handed its whole share of the bar in
# exchange for no work at all.
if [[ $phase != install ]]; then
@@ -189,7 +189,7 @@ _cau_pacman_exec() {
fi
# Line-buffered on purpose. pacman writes to a file or through a pipe here,
# never to a terminal, so libc buffers it in 4KB blocks - and 4KB of
# never to a terminal, so libc buffers it in 4KB blocks. And 4KB of
# "upgrading foo..." is on the order of a hundred and sixty packages. The
# watcher would see nothing at all, then a hundred and sixty lines at once,
# which is exactly how a bar comes to sit still and then leap to the end.
@@ -321,7 +321,7 @@ cau_pacman_update() {
#
# While an upgrade runs, a shutdown request is refused by logind and the
# desktop answers with a polkit password prompt reading "Power off the
# system while an application is inhibiting this" - which never mentions
# system while an application is inhibiting this". That never mentions
# updates and, on a German system, is not even translated. Somebody who was
# simply told beforehand does not end up staring at that.
if [[ $CFG_NOTIFY_START == yes ]]; then
@@ -393,7 +393,7 @@ cau_pacman_update() {
dependency)
# Something installed still depends on a package the repos want
# to drop or replace - almost always an AUR package that has not
# to drop or replace, almost always an AUR package that has not
# caught up yet. Nothing here can fix that, and it is not worth
# failing over: letting one stuck package block every other
# update indefinitely is far worse on an unattended machine.
@@ -468,7 +468,7 @@ cau_pacman_cleanup() {
reclaim="$( { paccache -d --nocolor -k"$CFG_KEEP_OLD"; paccache -du --nocolor -k0; } 2>&1 \
| grep -oE 'disk space saved: [^)]*' | paste -sd', ' -)"
cau_info "Trimming the package cache (keeping $CFG_KEEP_OLD old version(s))${reclaim:+ - $reclaim}"
cau_info "Trimming the package cache (keeping $CFG_KEEP_OLD old version(s))${reclaim:+: $reclaim}"
cau_run_logged paccache -r --nocolor -k"$CFG_KEEP_OLD" || cau_warn "paccache -r failed"
cau_run_logged paccache -ru --nocolor -k0 || cau_warn "paccache -ru failed"
fi
+24 -24
View File
@@ -3,8 +3,8 @@
# The update's progress bar on the desktop.
#
# An unattended upgrade can take twenty minutes, and for most of that a user is
# told only that "an update is running". This drives the desktop's job list -
# the same widget that shows a bar while Dolphin copies files - so how far
# told only that "an update is running". This drives the desktop's job list
# (the same widget that shows a bar while Dolphin copies files), so how far
# along the run is stays visible the whole time.
#
# The desktop ends the progress entry as soon as the D-Bus connection that
@@ -14,8 +14,8 @@
# of those pipes, plus the arithmetic that turns "package 120 of 260 in the
# repository step" into one number for the bar.
#
# Absent anywhere along the way - no session, no Plasma, no Python bindings -
# this does nothing at all and the update proceeds exactly as before.
# If anything is missing along the way (no session, no Plasma, no Python
# bindings), this does nothing at all and the update proceeds exactly as before.
CAU_PROGRESS_HELPER="${CAU_LIBEXECDIR}/cachy-auto-update-progress"
@@ -26,14 +26,14 @@ CAU_PROGRESS_FIFOS=()
CAU_PROGRESS_LOCALES=()
# What each step is worth on the bar. Rough shares of a typical run rather than
# anything measured: the repositories dominate - fetching them and unpacking
# them about equally, on a domestic line - and the cleanup is a rounding error.
# They do not have to add up to 100 - only the steps a given run will actually
# anything measured: the repositories dominate (fetching them and unpacking
# them about equally, on a domestic line), and the cleanup is a rounding error.
# They do not have to add up to 100. Only the steps a given run will actually
# perform are counted, and the total is normalised against those.
#
# "resolve" is everything pacman does before it has a transaction: syncing the
# databases and working out what the upgrade actually consists of. It is
# usually seconds, which is why it is worth so little - but on a large backlog
# usually seconds, which is why it is worth so little. But on a large backlog
# it is minutes, and those minutes used to be spent looking at a bar that had
# not moved yet.
declare -A CAU_PROGRESS_WEIGHTS=(
@@ -147,7 +147,7 @@ cau_progress_active() {
# turns up while it runs: nothing to download because every package was already
# in the cache, no AUR updates pending, no Flatpaks installed. A step like that
# keeps its whole share of the bar and then hands it over in a single jump the
# moment the next one starts - which is precisely the stutter this is here to
# moment the next one starts. That is precisely the stutter this is here to
# remove. Dropping it hands its share to the steps that do have work instead,
# so the bar advances at a steady pace rather than leaping across the gaps.
#
@@ -243,7 +243,7 @@ _cau_progress_pct() {
# Never backwards. Two honest things can ask for that: dropping a step
# rescales the run against a smaller total, and the conflict-recovery loop
# restarts pacman - and with it the item tally - from the top. Both are
# restarts pacman (and with it the item tally) from the top. Both are
# real, neither is a reason to show somebody a bar that retreats.
(( pct < CAU_PROGRESS_SHOWN )) && pct=$CAU_PROGRESS_SHOWN
@@ -282,16 +282,16 @@ cau_progress_item() {
# whatsoever between "starting full system upgrade" and the transaction it
# eventually prepares; an AUR helper compiling a package prints plenty, none of
# it countable. On a large backlog either is minutes. There is no honest number
# to show for that - but a bar that has not moved since it appeared is read as
# to show for that. But a bar that has not moved since it appeared is read as
# a hang, and somebody who reads it that way reaches for the power button in
# the middle of an update. That is the failure this is here to prevent.
#
# So it creeps, along a curve that approaches the end of the step without ever
# reaching it: half the step's share after HALFLIFE seconds, three quarters
# after three times that, the whole of it never. Nothing is claimed that is not
# known - the item counter stays empty throughout, which is the field that
# would be lying if it moved - and the step still finishes the instant real
# work reports in, because every real report is further along than the creep.
# known: the item counter stays empty throughout, and it is the field that
# would be lying if it moved. The step still finishes the instant real work
# reports in, because every real report is further along than the creep.
#
# Confined to the step's own span, so a creep can never overtake the step that
# comes after it however long it is left running.
@@ -324,8 +324,8 @@ cau_progress_creep_start() {
# reason cau_progress_begin opens its fifo read-write: a pipe held open
# at both ends never reports end-of-file, so a timed read on it blocks
# for exactly the timeout and nothing else. A forked sleep would also
# survive the kill below - it is a child of this subshell, not this
# subshell - and inherit the fifo's write end, which would keep the
# survive the kill below (it is a child of this subshell, not this
# subshell) and inherit the fifo's write end, which would keep the
# helper from seeing the end of its input until the sleep ran out.
local nap
exec {nap}<> <(:)
@@ -345,15 +345,15 @@ cau_progress_creep_stop() {
# The ticker moved the bar from inside a subshell, so this side never saw
# it happen and still believes the bar is where it was left. Catching up
# costs one recomputation - the curve is a function of elapsed time and
# nothing else - and without it the next ordinary report from here would be
# costs one recomputation, since the curve is a function of elapsed time and
# nothing else. Without it the next ordinary report from here would be
# measured against a stale percentage and send the bar backwards.
cau_progress_creep $(( SECONDS - CAU_PROGRESS_CREEP_T0 ))
}
# cau_progress_detail <label-msgid> <value>
# A labelled line under the entry's "Details" - which package is being unpacked
# right now, say.
# A labelled line under the entry's "Details", for example which package is
# being unpacked right now.
cau_progress_detail() {
local label="$1" value="$2" i fd
@@ -383,7 +383,7 @@ CAU_PROGRESS_TAIL_PID=0
#
# The protocol is one instruction per line, so the tail travels tab separated
# and is put back together on the other side. Tabs inside a log line would
# split it in two on the way, so they become spaces first - the job view
# split it in two on the way, so they become spaces first. The job view
# renders either as whitespace, and a line broken in half renders as nonsense.
cau_progress_log() {
local label="$1" text="$2" i fd
@@ -465,12 +465,12 @@ cau_progress_end() {
# Before the descriptors go: anything still running in the background holds
# its own copy of them, so the helper would not see the end of its input
# until it exited - and it is about to be waited on.
# until it exited. And it is about to be waited on.
cau_progress_creep_stop
cau_progress_tail_stop
# The last step never consumes its own share - nothing reports items for
# the cleanup - so the bar would stop a few percent short of the end and
# The last step never consumes its own share (nothing reports items for
# the cleanup), so the bar would stop a few percent short of the end and
# vanish there. Only on the way out of a run that actually worked, though:
# filling the bar for a failed update says the opposite of what happened.
[[ $outcome == ok ]] && _cau_progress_line 'percent\t100'