Add cachy-auto-update: unattended background updates for CachyOS

A root systemd service applies pacman, AUR, Flatpak and AppImage updates on
its own, gated on battery state, gaming activity and whether anybody else is
using the package system. The CLI is deliberately two switches plus status.

No user password is stored anywhere: pacman runs as root directly, and the AUR
step - which makepkg forbids running as root - drops to a locked system account
that sudoers permits to call pacman without a password.
This commit is contained in:
Felitendo committed 2026-08-08 02:27:04 +02:00
1 parent 327da1dae3
commit ceb024d7be
28 files changed
+3856 -1

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
# shellcheck shell=bash
#
# 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.
#
# 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
# the user's desktop turns out to be running in.
CAU_NOTIFY_ICON="system-software-update"
CAU_NOTIFY_QUEUE_MAX=20
# cau_notify <urgency> <title-msgid> <body-msgid> [body printf args...]
# Never fails: a machine without libnotify, or with nobody logged in, is a
# normal state, not an error.
cau_notify() {
local urgency="$1" title="$2" body="$3"
shift 3
local -a args=("$@")
local delivered=0 user uid locale t b
[[ $CFG_NOTIFICATIONS == yes ]] || return 0
while read -r user uid; do
[[ -n $user ]] || continue
cau_as_user "$user" "$uid" sh -c 'command -v notify-send >/dev/null' || continue
locale="$(cau_user_locale "$user" "$uid")"
t="$(cau_msg_in "$locale" "$title")"
b="$(cau_msg_in "$locale" "$body" "${args[@]}")"
if cau_as_user "$user" "$uid" notify-send \
--app-name="$CAU_PRETTY" \
--icon="$CAU_NOTIFY_ICON" \
--urgency="$urgency" \
-- "$t" "$b" 2>/dev/null
then
delivered=1
fi
done < <(cau_active_session_users)
(( delivered )) && return 0
cau_notify_enqueue "$urgency" "$title" "$body" "${args[@]}"
}
# cau_notify_enqueue <urgency> <title-msgid> <body-msgid> [args...]
# Tab-separated records, oldest first. The file is world-readable on purpose:
# the login-time delivery runs unprivileged and only ever reads it.
#
# The key is a nanosecond timestamp rather than a second one: delivery marks
# progress by "last key seen", so two records sharing a key could make the
# second one unreachable forever if a login landed between them.
cau_notify_enqueue() {
local urgency="$1" title="$2" body="$3"
shift 3
local record tmp
mkdir -p "$CAU_STATEDIR" 2>/dev/null || return 0
record="$(date +%s%N)"$'\t'"$urgency"$'\t'"$title"$'\t'"$body"
local arg
for arg in "$@"; do
record+=$'\t'"${arg//$'\t'/ }"
done
printf '%s\n' "$record" >> "$CAU_NOTIFY_QUEUE" 2>/dev/null || return 0
# 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"
mv -f "$tmp" "$CAU_NOTIFY_QUEUE"
fi
chmod 0644 "$CAU_NOTIFY_QUEUE" 2>/dev/null || true
}
# cau_notify_deliver_queue
# Runs unprivileged, as the freshly logged-in user, from the XDG autostart
# entry. State about what has already been seen lives in the user's own home,
# so no write access to /var/lib is needed and each user is tracked separately.
cau_notify_deliver_queue() {
local seen_file seen ts urgency title body
local -a args
[[ -r $CAU_NOTIFY_QUEUE ]] || return 0
cau_have notify-send || return 0
seen_file="${XDG_STATE_HOME:-$HOME/.local/state}/cachy-auto-update/notify-seen"
mkdir -p "$(dirname "$seen_file")" 2>/dev/null || return 0
seen=0
[[ -r $seen_file ]] && seen="$(< "$seen_file")"
[[ $seen =~ ^[0-9]+$ ]] || seen=0
local newest="$seen"
while IFS=$'\t' read -r ts urgency title body rest; do
[[ $ts =~ ^[0-9]+$ ]] || continue
(( ts > seen )) || continue
# remaining tab-separated fields are the body's printf arguments
args=()
if [[ -n ${rest:-} ]]; then
IFS=$'\t' read -r -a args <<< "$rest"
fi
notify-send \
--app-name="$CAU_PRETTY" \
--icon="$CAU_NOTIFY_ICON" \
--urgency="${urgency:-normal}" \
-- "$(cau_msg "$title")" "$(cau_msg "$body" "${args[@]}")" 2>/dev/null || true
(( ts > newest )) && newest="$ts"
done < "$CAU_NOTIFY_QUEUE"
printf '%s\n' "$newest" > "$seen_file" 2>/dev/null || true
}