Files
cachy-auto-update/src/lib/config.sh
T
Felitendo 6aa9b147b8 Put a progress bar on the desktop, and repair the notifications around it
An unattended run takes twenty minutes and said nothing at all while it worked.
The notification area now carries a live entry for the duration - which step is
running, which package is being unpacked, item count, percentage. It is a job
in the sense of org.kde.JobViewServer, the same mechanism Dolphin uses while
copying files, rather than a notification, which is what makes it a real bar
instead of a line of text.

That mechanism needs a process of its own. The desktop ties a job to the D-Bus
connection that requested it and withdraws it the moment that connection
closes, so gdbus, busctl and dbus-send cannot drive one at all - every
invocation is a fresh connection that closes again immediately. A small helper
therefore runs inside each graphical session for the length of the update,
holding the connection open and taking instructions on stdin. It needs
python-gobject; without it there is no bar and nothing else changes. Position
within the repository step is read from pacman's own "(120/260) upgrading foo"
lines. Its other (n/m) sequences are ignored on purpose: checking keys, package
integrity and loading files each count up to the same total, and following them
would run the bar to the end three times before the first package was unpacked.

The "system updated" notification never arrived, which is what prompted looking
at any of this. Tagged notifications were posted with --replace-id naming the
start message, and Plasma silently drops a Notify() whose replaces_id points at
an expired notification: no bubble, no error, and the id it hands back is the
dead one it just ignored. The start bubble times out in seconds and a run lasts
minutes, so the result fell into exactly that hole every single time. The
previous message is now withdrawn and a fresh one posted.

How long a message stays is set per message rather than left to the daemon.
Anything reporting that the machine still needs a person - a failed update, a
locked package database, packages that had to be held back - waits until it is
dismissed. Everything else times out on its own, a successful update included;
nothing should have to be clicked away for having gone right. Daemons do keep
critical-urgency messages up and the specification asks them to, but that is a
should, it says nothing about the normal-urgency messages here that still need
somebody to act, and urgency separately governs sound and do-not-disturb.

"Restart recommended" is gone, and NotifyReboot with it. The running kernel
loses its module tree the moment pacman unpacks the new one, so the notice
fired while the run was still building AUR packages and pulling Flatpaks, where
it reads as an invitation to restart in the middle of an update. The state is
still recorded and cachy-auto-update status still reports it. The option went
rather than only its default, because an installed system keeps its own
configuration file and would have carried on notifying regardless.
2026-08-14 10:49:02 +02:00

141 lines
5.3 KiB
Bash

# shellcheck shell=bash
#
# Reading and writing /etc/cachy-auto-update/cachy-auto-update.conf.
#
# The file is deliberately *parsed* rather than sourced: it is edited by a
# root-run daemon, and sourcing it would turn a stray line into arbitrary code
# execution. The format is one "Key=Value" per line, '#' starts a comment.
# The file is cached and parsed in-process rather than shelled out to sed on
# every lookup. The settings screen reads every key on every redraw, and a fork
# per key made the redraw slow enough to swallow keystrokes.
CAU_CONFIG_CACHE=''
CAU_CONFIG_CACHED=0
_cau_config_slurp() {
(( CAU_CONFIG_CACHED )) && return 0
CAU_CONFIG_CACHE=''
[[ -r $CAU_CONFIG ]] && CAU_CONFIG_CACHE="$(< "$CAU_CONFIG")"
CAU_CONFIG_CACHED=1
return 0
}
# _cau_config_lookup <Key> [default]
# Result in CAU_CONFIG_VALUE. Assigning rather than printing matters on the
# settings screen, which reads every key on every frame: a command substitution
# there is a fork, and forks were the entire cost of a redraw.
CAU_CONFIG_VALUE=''
_cau_config_lookup() {
local key="$1" default="${2:-}" val='' line
CAU_CONFIG_VALUE="$default"
[[ -r $CAU_CONFIG ]] || return 0
_cau_config_slurp
# last assignment wins, matching the previous sed|tail behaviour
while IFS= read -r line; do
[[ $line == *"$key"* ]] || continue
[[ $line =~ ^[[:space:]]*"$key"[[:space:]]*=(.*)$ ]] || continue
val="${BASH_REMATCH[1]}"
done <<< "$CAU_CONFIG_CACHE"
# strip a trailing comment and surrounding whitespace/quotes
val="${val%%#*}"
val="${val#"${val%%[![:space:]]*}"}"
val="${val%"${val##*[![:space:]]}"}"
val="${val%\"}"
val="${val#\"}"
[[ -n $val ]] && CAU_CONFIG_VALUE="$val"
return 0
}
# cau_config_get <Key> [default]
cau_config_get() {
_cau_config_lookup "$@"
printf '%s\n' "$CAU_CONFIG_VALUE"
}
# cau_config_bool <Key> <default: yes|no>
# Succeeds when the key is truthy.
cau_config_bool() {
local val
val="$(cau_config_get "$1" "$2")"
case "${val,,}" in
yes|y|true|1|on|enabled) return 0 ;;
*) return 1 ;;
esac
}
# cau_config_int <Key> <default>
cau_config_int() {
local val
val="$(cau_config_get "$1" "$2")"
[[ $val =~ ^-?[0-9]+$ ]] && printf '%s\n' "$val" || printf '%s\n' "$2"
}
# cau_config_set <Key> <Value>
# Replaces the key in place if present (including a commented-out template
# line), otherwise appends it. Requires write access to the config file.
cau_config_set() {
local key="$1" value="$2" tmp
if [[ ! -e $CAU_CONFIG ]]; then
mkdir -p "$(dirname "$CAU_CONFIG")" || return 1
printf '# %s configuration\n' "$CAU_NAME" > "$CAU_CONFIG" || return 1
fi
[[ -w $CAU_CONFIG ]] || return 1
tmp="$(mktemp "${CAU_CONFIG}.XXXXXX")" || return 1
# keep the original permissions rather than mktemp's 0600
chmod --reference="$CAU_CONFIG" "$tmp" 2>/dev/null || chmod 0644 "$tmp"
if grep -qE "^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*=" "$CAU_CONFIG"; then
awk -v key="$key" -v value="$value" '
!done && $0 ~ "^[[:space:]]*#?[[:space:]]*" key "[[:space:]]*=" {
print key "=" value; done = 1; next
}
# drop any further occurrences so the file cannot grow duplicates
$0 ~ "^[[:space:]]*" key "[[:space:]]*=" { next }
{ print }
' "$CAU_CONFIG" > "$tmp" || { rm -f "$tmp"; return 1; }
else
cat "$CAU_CONFIG" > "$tmp" || { rm -f "$tmp"; return 1; }
printf '%s=%s\n' "$key" "$value" >> "$tmp"
fi
mv -f "$tmp" "$CAU_CONFIG"
CAU_CONFIG_CACHED=0
}
# ---------------------------------------------------------------------------
# Resolved settings
# ---------------------------------------------------------------------------
# Loaded once per process into plain variables so the rest of the code does not
# re-parse the file for every lookup.
cau_config_load() {
CFG_ENABLED=no; cau_config_bool Enabled no && CFG_ENABLED=yes
CFG_NOTIFICATIONS=no; cau_config_bool Notifications yes && CFG_NOTIFICATIONS=yes
CFG_REQUIRE_AC=no; cau_config_bool RequireAC no && CFG_REQUIRE_AC=yes
CFG_SKIP_GAMING=no; cau_config_bool SkipWhenGaming yes && CFG_SKIP_GAMING=yes
CFG_AUR=no; cau_config_bool UpdateAUR yes && CFG_AUR=yes
CFG_FLATPAK=no; cau_config_bool UpdateFlatpak yes && CFG_FLATPAK=yes
CFG_APPIMAGE=no; cau_config_bool UpdateAppImages yes && CFG_APPIMAGE=yes
CFG_DEVEL=no; cau_config_bool UpdateDevel no && CFG_DEVEL=yes
CFG_RESOLVE_CONFLICTS=no; cau_config_bool AutoResolveConflicts yes && CFG_RESOLVE_CONFLICTS=yes
CFG_CLEAN_CACHE=no; cau_config_bool CleanCache yes && CFG_CLEAN_CACHE=yes
CFG_REMOVE_ORPHANS=no; cau_config_bool RemoveOrphans no && CFG_REMOVE_ORPHANS=yes
CFG_NOTIFY_START=no; cau_config_bool NotifyOnStart yes && CFG_NOTIFY_START=yes
CFG_NOTIFY_SUCCESS=no; cau_config_bool NotifyOnSuccess yes && CFG_NOTIFY_SUCCESS=yes
CFG_NOTIFY_ERROR=no; cau_config_bool NotifyOnError yes && CFG_NOTIFY_ERROR=yes
CFG_INTERVAL="$(cau_config_get UpdateInterval 1d)"
CFG_INTERVAL_SECONDS="$(cau_duration_to_seconds "$CFG_INTERVAL" 86400)"
CFG_MIN_BATTERY="$(cau_config_int MinBatteryPercent 30)"
CFG_KEEP_OLD="$(cau_config_int KeepOldPackages 3)"
CFG_AUR_HELPER="$(cau_config_get AURHelper auto)"
CFG_IGNORE_PKG="$(cau_config_get IgnorePkg '')"
}