175 lines
5.3 KiB
Bash
175 lines
5.3 KiB
Bash
# shellcheck shell=bash
|
|
#
|
|
# AUR packages.
|
|
#
|
|
# 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
|
|
# already have.
|
|
|
|
CAU_AUR_COUNT=0
|
|
CAU_AUR_HELPER=''
|
|
|
|
# Notify only after this many consecutive failed AUR runs. A single failed
|
|
# build is routine (upstream broke a tarball, a checksum moved) and self-heals
|
|
# a day later; nagging about it on someone's parents' machine is noise.
|
|
CAU_AUR_FAILURE_THRESHOLD=2
|
|
|
|
# cau_aur_detect
|
|
# Resolves the helper to use, honouring AURHelper from the config.
|
|
cau_aur_detect() {
|
|
local candidate
|
|
|
|
CAU_AUR_HELPER=''
|
|
|
|
if [[ -n $CFG_AUR_HELPER && $CFG_AUR_HELPER != auto ]]; then
|
|
if cau_have "$CFG_AUR_HELPER"; then
|
|
CAU_AUR_HELPER="$CFG_AUR_HELPER"
|
|
return 0
|
|
fi
|
|
cau_warn "Configured AUR helper '$CFG_AUR_HELPER' not found"
|
|
return 1
|
|
fi
|
|
|
|
for candidate in paru yay pikaur; do
|
|
if cau_have "$candidate"; then
|
|
CAU_AUR_HELPER="$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# cau_as_build_user <command> [args...]
|
|
# A deliberately small environment: the helper gets its own HOME and cache so
|
|
# nothing it downloads ever lands in a human's home directory.
|
|
cau_as_build_user() {
|
|
runuser -u "$CAU_BUILD_USER" -- env \
|
|
"HOME=$CAU_BUILD_HOME" \
|
|
"USER=$CAU_BUILD_USER" \
|
|
"LOGNAME=$CAU_BUILD_USER" \
|
|
"XDG_CACHE_HOME=$CAU_CACHEDIR" \
|
|
"XDG_CONFIG_HOME=$CAU_BUILD_HOME/.config" \
|
|
"XDG_DATA_HOME=$CAU_BUILD_HOME/.local/share" \
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" \
|
|
LC_ALL=C \
|
|
"$@"
|
|
}
|
|
|
|
# cau_aur_ready
|
|
# True when everything the AUR path needs is actually in place.
|
|
cau_aur_ready() {
|
|
cau_aur_detect || { cau_info "No AUR helper installed; skipping AUR updates"; return 1; }
|
|
|
|
if ! getent passwd "$CAU_BUILD_USER" > /dev/null; then
|
|
cau_warn "Build account '$CAU_BUILD_USER' is missing; skipping AUR updates"
|
|
return 1
|
|
fi
|
|
|
|
# -l asks sudo whether the command is permitted; -n guarantees it can
|
|
# never block on a password prompt. Testing `sudo -n true` instead would
|
|
# fail by design, because the rule is scoped to pacman alone.
|
|
if ! cau_as_build_user sudo -n -l /usr/bin/pacman > /dev/null 2>&1; then
|
|
cau_warn "Build account cannot run pacman without a password; check /etc/sudoers.d/cachy-auto-update"
|
|
return 1
|
|
fi
|
|
|
|
if ! cau_have makepkg; then
|
|
cau_warn "makepkg not found (base-devel missing); skipping AUR updates"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# cau_aur_helper_args
|
|
# The flags that turn an interactive helper into a silent one.
|
|
cau_aur_helper_args() {
|
|
case "$CAU_AUR_HELPER" in
|
|
paru)
|
|
printf '%s\n' -Sua --noconfirm --skipreview --removemake --cleanafter --color never
|
|
[[ $CFG_DEVEL == yes ]] && printf '%s\n' --devel
|
|
;;
|
|
yay)
|
|
printf '%s\n' -Sua --noconfirm --removemake --cleanafter --color never
|
|
printf '%s\n' --answerclean All --answerdiff None --answeredit None --answerupgrade None
|
|
[[ $CFG_DEVEL == yes ]] && printf '%s\n' --devel
|
|
;;
|
|
pikaur)
|
|
printf '%s\n' -Sua --noconfirm --noedit
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# cau_aur_pending
|
|
# Number of AUR packages with an update available.
|
|
cau_aur_pending() {
|
|
local out
|
|
out="$(cau_as_build_user "$CAU_AUR_HELPER" -Qua 2>/dev/null | grep -c .)" || out=0
|
|
[[ $out =~ ^[0-9]+$ ]] || out=0
|
|
printf '%s\n' "$out"
|
|
}
|
|
|
|
# cau_aur_update
|
|
# Returns 0 on success or "nothing to do", 1 on a failure worth reporting.
|
|
# Transient build failures are swallowed until they repeat.
|
|
cau_aur_update() {
|
|
local pending failures
|
|
local -a args
|
|
|
|
# No helper, no base-devel, no bar: a step that cannot run should not be
|
|
# holding a share of it.
|
|
cau_aur_ready || { cau_progress_drop aur; return 0; }
|
|
|
|
cau_progress_step aur "Updating AUR packages"
|
|
|
|
pending="$(cau_aur_pending)"
|
|
if (( pending == 0 )); then
|
|
cau_info "No AUR updates pending"
|
|
cau_state_clear aur_failures
|
|
cau_progress_drop aur
|
|
return 0
|
|
fi
|
|
|
|
cau_info "Updating $pending AUR package(s) with $CAU_AUR_HELPER"
|
|
|
|
# The helper builds each package from source and prints plenty about it,
|
|
# none of it countable from this side. A single large package can take ten
|
|
# minutes, so the bar creeps through the step rather than sitting at its
|
|
# start for all of them; the item count stays where it is, because that is
|
|
# the number that would be lying if it moved.
|
|
cau_progress_item 0 "$pending"
|
|
mapfile -t args < <(cau_aur_helper_args)
|
|
cau_progress_creep_start
|
|
|
|
if cau_run_logged cau_as_build_user "$CAU_AUR_HELPER" "${args[@]}"; then
|
|
cau_progress_creep_stop
|
|
CAU_AUR_COUNT="$pending"
|
|
cau_progress_item "$pending"
|
|
cau_state_clear aur_failures
|
|
return 0
|
|
fi
|
|
|
|
cau_progress_creep_stop
|
|
CAU_AUR_COUNT=0
|
|
failures="$(cau_state_read aur_failures 0)"
|
|
[[ $failures =~ ^[0-9]+$ ]] || failures=0
|
|
failures=$(( failures + 1 ))
|
|
cau_state_write aur_failures "$failures"
|
|
|
|
if (( failures >= CAU_AUR_FAILURE_THRESHOLD )); then
|
|
cau_error "AUR update failed $failures times in a row"
|
|
return 1
|
|
fi
|
|
|
|
cau_warn "AUR update failed (attempt $failures); will retry on the next run"
|
|
return 0
|
|
}
|