Files
cachy-auto-update/src/cachy-auto-update-run
T
Felitendo 459cbbf8e7 Fix locale handling, menu re-entry and empty-checkupdates logging
- The runner now forces LC_ALL=C itself instead of relying on the unit's
  Environment=. pacman failures are classified by matching its output, so a
  run started by hand on a German system was misclassifying every failure.
- 'Update now' in the menu exec'd the runner, which terminated the menu.
- Log a distinct message when checkupdates is unavailable, instead of
  claiming zero pending packages before a full upgrade.
2026-08-08 02:35:34 +02:00

222 lines
6.3 KiB
Bash

#!/usr/bin/env bash
#
# 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.
# Refusing to run is the normal, expected outcome most of the time, so it is
# never treated as an error.
#
# Copyright (C) 2026 Felitendo
# SPDX-License-Identifier: GPL-3.0-or-later
set -uo pipefail
CAU_LIBDIR="${CAU_LIBDIR:-@LIBDIR@}"
for _mod in common config users conditions locks notify \
pkg_pacman pkg_aur pkg_flatpak pkg_appimage; do
# shellcheck source=/dev/null
if ! source "$CAU_LIBDIR/$_mod.sh"; then
printf 'cachy-auto-update-run: cannot load %s/%s.sh\n' "$CAU_LIBDIR" "$_mod" >&2
exit 14
fi
done
unset _mod
FORCE=0
DRY_RUN=0
for arg in "$@"; do
case "$arg" in
--force) FORCE=1 ;;
--dry-run) DRY_RUN=1 ;;
--debug) CAU_DEBUG=1 ;;
esac
done
if [[ $EUID -ne 0 ]]; then
printf 'cachy-auto-update-run: must run as root\n' >&2
exit 2
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
# notification is rendered in the recipient's own locale by cau_msg_in.
export LC_ALL=C LANGUAGE=
# Re-exec under an inhibitor so a suspend or shutdown cannot land in the middle
# of a pacman transaction and leave the database half-written. Done before
# anything else so the whole pass is covered, including the lock.
if [[ -z ${CAU_INHIBITED:-} ]] && command -v systemd-inhibit > /dev/null 2>&1; then
export CAU_INHIBITED=1
exec systemd-inhibit \
--what=sleep:shutdown \
--mode=block \
--who="CachyOS Auto-Update" \
--why="Applying system updates" \
-- "$0" "$@"
fi
cau_config_load
cau_log_open
# ---------------------------------------------------------------------------
# Should this run happen at all?
# ---------------------------------------------------------------------------
defer() {
cau_info "Deferred: $1"
exit 0
}
if ! cau_acquire_lock; then
cau_debug "Another run is already in progress"
exit 0
fi
if (( ! FORCE )); then
if [[ $CFG_ENABLED != yes ]]; then
cau_debug "Automatic updates are disabled"
exit 0
fi
if ! cau_update_due; then
cau_debug "Not due yet (interval $CFG_INTERVAL)"
exit 0
fi
fi
cau_state_write last_run "$(date +%s)"
if (( ! FORCE )); then
CAU_SKIP_REASON=''
cau_power_ok || defer "$CAU_SKIP_REASON"
if [[ $CFG_SKIP_GAMING == yes ]]; then
CAU_SKIP_REASON=''
if cau_busy; then
defer "$CAU_SKIP_REASON"
fi
fi
fi
# This one is checked even with --force: proceeding anyway would just hand the
# user a lock error instead of doing anything useful.
CAU_SKIP_REASON=''
if cau_package_manager_busy; then
if cau_track_stale_lock; then
cau_warn "pacman's database lock appears to be stale"
cau_notify normal \
"Package database locked" \
"pacman's lock file looks left over from an interrupted update. Updates are paused until it is cleared."
fi
defer "$CAU_SKIP_REASON"
fi
cau_state_clear stale_lock_count
# ---------------------------------------------------------------------------
# Dry run
# ---------------------------------------------------------------------------
if (( DRY_RUN )); then
printf '%s\n' "Would update on this machine:"
if cau_pacman_pending; then
printf ' repository packages: %s\n' "$(grep -c . <<< "$CAU_PACMAN_PENDING")"
sed 's/^/ /' <<< "$CAU_PACMAN_PENDING"
else
printf ' repository packages: 0\n'
fi
if [[ $CFG_AUR == yes ]] && cau_aur_ready; then
printf ' AUR packages: %s (%s)\n' "$(cau_aur_pending)" "$CAU_AUR_HELPER"
else
printf ' AUR packages: skipped\n'
fi
if [[ $CFG_FLATPAK == yes ]] && cau_have flatpak; then
printf ' system Flatpaks: %s\n' "$(cau_flatpak_pending_system)"
else
printf ' system Flatpaks: skipped\n'
fi
printf ' AppImages: %s\n' \
"$([[ $CFG_APPIMAGE == yes ]] && printf 'checked via Gear Lever' || printf 'skipped')"
exit 0
fi
# ---------------------------------------------------------------------------
# Do the work
# ---------------------------------------------------------------------------
cau_info "Starting update run"
failed=0
if ! cau_pacman_update; then
failed=1
fi
if [[ $CFG_AUR == yes ]]; then
cau_aur_update || failed=1
fi
if [[ $CFG_FLATPAK == yes ]]; then
cau_flatpak_update || failed=1
fi
if [[ $CFG_APPIMAGE == yes ]]; then
cau_appimage_update || true # best effort; never marks the run as failed
fi
cau_pacman_cleanup
pacnew="$(cau_pacman_pacnew_count)"
if [[ $pacnew =~ ^[0-9]+$ ]] && (( pacnew > 0 )); then
cau_info "$pacnew .pacnew file(s) present; left untouched"
fi
# ---------------------------------------------------------------------------
# Record and report
# ---------------------------------------------------------------------------
total=$(( CAU_PACMAN_COUNT + CAU_AUR_COUNT + CAU_FLATPAK_COUNT + CAU_APPIMAGE_COUNT ))
cau_state_write last_counts \
"$CAU_PACMAN_COUNT $CAU_AUR_COUNT $CAU_FLATPAK_COUNT $CAU_APPIMAGE_COUNT"
if (( failed )); then
cau_state_write last_result failed
cau_error "Update run finished with errors"
[[ $CFG_NOTIFY_ERROR == yes ]] && cau_notify critical \
"Update failed" \
"Something went wrong while updating. Run 'cachy-auto-update log' for details."
else
cau_state_write last_result ok
cau_state_write last_success "$(date +%s)"
cau_info "Update run finished successfully ($total item(s) updated)"
if (( total > 0 )) && [[ $CFG_NOTIFY_SUCCESS == yes ]]; then
cau_notify low "System updated" "%d updates were installed." "$total"
fi
fi
if cau_pacman_reboot_needed; then
cau_state_write reboot_needed 1
cau_info "A kernel update needs a restart"
[[ $CFG_NOTIFY_REBOOT == yes ]] && cau_notify normal \
"Restart recommended" \
"A new kernel was installed. Please restart when it suits you."
else
cau_state_write reboot_needed 0
fi
# Keep the state readable for the unprivileged `status` and `log` commands.
# Only the plain files: the build account's home lives in here too and must
# keep its own ownership and mode.
chmod 0755 "$CAU_STATEDIR" 2>/dev/null || true
find "$CAU_STATEDIR" -maxdepth 1 -type f -exec chmod 0644 {} + 2>/dev/null || true
exit $(( failed ? 1 : 0 ))