- 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.
222 lines
6.3 KiB
Bash
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 ))
|