tss-reset_package_repository.sh #2

  • //
  • guest/
  • tom_tyler/
  • sw/
  • main/
  • install_sdp/
  • dev/
  • bin/
  • tss-reset_package_repository.sh
  • View
  • Commits
  • Open Download .zip Download (9 KB)
#!/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 "$@"
# Change User Description Committed
#2 32069 C. Thomas Tyler Enhanced version.
#1 32068 C. Thomas Tyler Added wip test support script: tss-reset_package_repository.sh