#!/usr/bin/env bash
# shellcheck shell=bash
# tss-reset_package_repository.sh
#
# Cross-distro package repository health check + self-repair
# Supports: APT (Debian/Ubuntu), DNF/YUM (RHEL/Rocky/Alma/CentOS), Zypper (SUSE/SLES)
set -euo pipefail
DRY_RUN=0
AGGRESSIVE=0
FORCE_IPV4_APT=1
FORCE_IPV4_OTHERS=0
NO_KILL=0
MAX_WAIT=600
VERBOSE=0
while (( $# )); do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--aggressive) AGGRESSIVE=1 ;;
--ipv4) FORCE_IPV4_APT=1; FORCE_IPV4_OTHERS=1 ;;
--no-kill) NO_KILL=1 ;;
--max-wait) shift; MAX_WAIT="${1:-600}"; [[ "$MAX_WAIT" =~ ^[0-9]+$ ]] || { printf 'Invalid --max-wait\n' >&2; exit 2; } ;;
--verbose) VERBOSE=1 ;;
-h|--help)
cat <<'EOF'
Usage: tss-reset_package_repository.sh [options]
--dry-run Show actions without changing state
--aggressive Heavier cleanup on the first repair attempt
--ipv4 Force IPv4 for all managers (APT already defaults to IPv4 here)
--no-kill Do not kill stuck package managers; rely on timeouts only
--max-wait N Timeout (seconds) for each package op [default: 600]
--verbose Shell trace
EOF
exit 0
;;
*) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;;
esac
shift
done
(( VERBOSE )) && set -x
log() { printf '[%(%F %T)T] %s\n' -1 "$*"; }
warn() { printf '[%(%F %T)T] WARNING: %s\n' -1 "$*" >&2; }
error() { printf '[%(%F %T)T] ERROR: %s\n' -1 "$*" >&2; }
run() {
if (( DRY_RUN )); then
printf '(dry-run) %s\n' "$*"
return 0
fi
bash -c "$*"
}
need_root() {
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
error "Must run as root."
exit 1
fi
}
need_root
have() { command -v "$1" >/dev/null 2>&1; }
check_space() {
local paths=("/" "/var" "/tmp")
local p use_p use_i
for p in "${paths[@]}"; do
if mountpoint -q "$p"; then
use_p=$(df -Ph "$p" | awk 'NR==2{gsub("%","",$5);print $5}')
use_i=$(df -Pi "$p" | awk 'NR==2{gsub("%","",$5);print $5}')
if [[ "$use_p" -ge 95 ]]; then warn "$p is ${use_p}% full; package ops may fail"; fi
if [[ "$use_i" -ge 95 ]]; then warn "$p inodes ${use_i}% used; package ops may fail"; fi
fi
done
}
boost_entropy_if_low() {
local ent=0
if [[ -r /proc/sys/kernel/random/entropy_avail ]]; then
ent=0
if [[ -r /proc/sys/kernel/random/entropy_avail ]]; then
ent=$(cat /proc/sys/kernel/random/entropy_avail 2>/dev/null || echo 0)
fi
fi
if (( ent < 128 )); then
warn "Low entropy (${ent}); restarting systemd-random-seed."
run "systemctl restart systemd-random-seed.service 2>/dev/null || true"
fi
}
with_timeout() {
if ! have timeout; then
warn "'timeout' not found; installing it improves hang protection."
bash -c "$*"
return
fi
timeout -k 30 "${MAX_WAIT}" bash -c "$*"
}
# -------- APT --------
apt_with_lock() {
local lock="/var/run/apt-installer.lock"
with_timeout "flock -w 300 '${lock}' bash -c \"$*\""
}
apt_smart_update() {
local args=()
(( FORCE_IPV4_APT )) && args+=("-o Acquire::ForceIPv4=true")
args+=(
"-o Acquire::Retries=2"
"-o Acquire::http::Timeout=20"
"-o Acquire::https::Timeout=20"
)
log "APT: update (with lock/timeout)"
apt_with_lock "apt-get ${args[*]} update"
}
apt_finish_half_configured() {
log "APT: finishing half-configured packages (dpkg --configure -a)"
run "dpkg --configure -a || true"
run "dpkg --audit || true"
}
apt_stop_backgrounds() {
log "APT: stopping background apt jobs"
run "systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true"
run "pkill -f update-notifier/apt-check 2>/dev/null || true"
}
apt_free_locks_if_stale() {
local f
for f in /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/lib/apt/lists/lock; do
if [[ -e "$f" ]] && ! lsof "$f" >/dev/null 2>&1; then
log "APT: removing stale lock $f"
run "rm -f '$f'"
fi
done
}
apt_kill_if_allowed() {
(( NO_KILL )) && { warn "Skipping kill of apt/apt-get (--no-kill)."; return; }
log "APT: killing stuck apt/apt-get if any"
run "killall apt apt-get 2>/dev/null || true"; sleep 1
run "killall -9 apt apt-get 2>/dev/null || true"
}
apt_heavy_cleanup() {
log "APT: heavy cleanup (lists/cache)"
run "rm -rf /var/lib/apt/lists/partial"
run "apt-get clean"
run "rm -rf /var/lib/apt/lists/*"
run "rm -f /var/cache/apt/pkgcache.bin /var/cache/apt/srcpkgcache.bin"
}
apt_repair_then_update() {
boost_entropy_if_low
apt_stop_backgrounds
apt_finish_half_configured
apt_free_locks_if_stale
apt_kill_if_allowed
if (( AGGRESSIVE )); then
apt_heavy_cleanup
else
log "APT: trying light repair first"
fi
if ! apt_smart_update; then
log "APT: light repair failed; performing heavy cleanup"
apt_heavy_cleanup
apt_smart_update
fi
}
apt_main() {
check_space
if apt_smart_update; then
log "APT: update succeeded."
else
warn "APT: update failed; attempting repair."
apt_repair_then_update
log "APT: update succeeded after repair."
fi
}
# -------- DNF / YUM --------
dnf_cmd() {
if have dnf; then echo dnf; elif have yum; then echo yum; else return 1; fi
}
dnf_with_lock() {
local lock="/var/run/dnf-installer.lock"
with_timeout "flock -w 300 '${lock}' bash -c \"$*\""
}
dnf_smart_makecache() {
local cmd; cmd=$(dnf_cmd) || return 1
local setopts=("--setopt=timeout=20" "--setopt=retries=2")
local pre=""
if (( FORCE_IPV4_OTHERS )); then
pre="export GAI_CONF=/etc/gai.conf; sed -i 's/^#precedence ::ffff:0:0\\/96 100/precedence ::ffff:0:0\\/96 100/' /etc/gai.conf 2>/dev/null || true; "
fi
log "DNF/YUM: makecache (with lock/timeout)"
dnf_with_lock "${pre}${cmd} -y makecache ${setopts[*]}"
}
dnf_stop_backgrounds() {
log "DNF/YUM: stopping background makecache timers"
run "systemctl stop dnf-makecache.timer dnf-makecache.service 2>/dev/null || true"
run "systemctl stop yum-cron.service 2>/dev/null || true"
}
dnf_kill_if_allowed() {
(( NO_KILL )) && { warn "Skipping kill of dnf/yum (--no-kill)."; return; }
log "DNF/YUM: killing stuck dnf/yum if any"
run "killall dnf yum 2>/dev/null || true"; sleep 1
run "killall -9 dnf yum 2>/dev/null || true"
}
dnf_heavy_cleanup() {
local cmd; cmd=$(dnf_cmd) || return 1
log "DNF/YUM: heavy cleanup (cache + rpmdb rebuild)"
if [[ "$cmd" = "dnf" ]]; then
run "dnf -y clean metadata || true"
run "rm -rf /var/cache/dnf/* || true"
else
run "yum -y clean metadata || true"
run "rm -rf /var/cache/yum/* || true"
fi
run "rpm --rebuilddb || true"
}
dnf_repair_then_makecache() {
dnf_stop_backgrounds
dnf_kill_if_allowed
if (( AGGRESSIVE )); then
dnf_heavy_cleanup
fi
if ! dnf_smart_makecache; then
log "DNF/YUM: light attempt failed; performing heavy cleanup"
dnf_heavy_cleanup
dnf_smart_makecache
fi
}
dnf_main() {
check_space
if dnf_smart_makecache; then
log "DNF/YUM: makecache succeeded."
else
warn "DNF/YUM: makecache failed; attempting repair."
dnf_repair_then_makecache
log "DNF/YUM: makecache succeeded after repair."
fi
}
# -------- Zypper --------
zypper_with_lock() {
local lock="/var/run/zypper-installer.lock"
with_timeout "flock -w 300 '${lock}' bash -c \"$*\""
}
zypper_smart_refresh() {
local pre=""
if (( FORCE_IPV4_OTHERS )); then
pre="export GAI_CONF=/etc/gai.conf; sed -i 's/^#precedence ::ffff:0:0\\/96 100/precedence ::ffff:0:0\\/96 100/' /etc/gai.conf 2>/dev/null || true; "
fi
log "Zypper: refresh (with lock/timeout)"
zypper_with_lock "${pre}zypper --non-interactive --gpg-auto-import-keys refresh"
}
zypper_fix_pid() {
if [[ -f /var/run/zypp.pid ]]; then
local pid
pid=$(cat /var/run/zypp.pid 2>/dev/null || echo "")
if [[ -n "$pid" ]] && ! ps -p "$pid" >/dev/null 2>&1; then
log "Zypper: removing stale /var/run/zypp.pid"
run "rm -f /var/run/zypp.pid"
fi
fi
}
zypper_kill_if_allowed() {
(( NO_KILL )) && { warn "Skipping kill of zypper (--no-kill)."; return; }
log "Zypper: killing stuck zypper if any"
run "killall zypper 2>/dev/null || true"; sleep 1
run "killall -9 zypper 2>/dev/null || true"
}
zypper_heavy_cleanup() {
log "Zypper: heavy cleanup (clean all + cache wipe)"
run "zypper --non-interactive clean --all || true"
run "rm -rf /var/cache/zypp/* || true"
}
zypper_repair_then_refresh() {
zypper_fix_pid
zypper_kill_if_allowed
if (( AGGRESSIVE )); then
zypper_heavy_cleanup
fi
if ! zypper_smart_refresh; then
log "Zypper: light attempt failed; performing heavy cleanup"
zypper_heavy_cleanup
zypper_smart_refresh
fi
}
zypper_main() {
check_space
if zypper_smart_refresh; then
log "Zypper: refresh succeeded."
else
warn "Zypper: refresh failed; attempting repair."
zypper_repair_then_refresh
log "Zypper: refresh succeeded after repair."
fi
}
# -------- Dispatch --------
main() {
boost_entropy_if_low
if have apt-get; then
log "Detected APT (Debian/Ubuntu)"
apt_main
exit 0
fi
if have dnf || have yum; then
log "Detected DNF/YUM (RHEL/Rocky/Alma/CentOS)"
dnf_main
exit 0
fi
if have zypper; then
log "Detected Zypper (SUSE/SLES)"
zypper_main
exit 0
fi
error "No supported package manager found (apt-get, dnf/yum, or zypper)."
exit 1
}
main "$@"