Preserve model caches and Docker networks during cleanup
This commit is contained in:
1 parent
3c1080e152
commit
e8512b56f5
9 files changed
+854
-5
No files matched your search
Executable
+443
@@ -0,0 +1,443 @@
|
||||
#!/usr/bin/env bash
|
||||
# arch-maintenance.sh
|
||||
# Konservatives Wartungsskript fuer Arch Linux
|
||||
# Standard: Dry-Run. Loeschen nur mit --apply.
|
||||
# 2026-09-10: Docker-Netze immer erhalten; kein globales system prune.
|
||||
|
||||
set -uo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
APPLY=0
|
||||
YES=0
|
||||
|
||||
PACMAN_KEEP=3
|
||||
PACMAN_KEEP_UNINSTALLED=0
|
||||
|
||||
JOURNAL_MAX_AGE="14d"
|
||||
JOURNAL_MAX_SIZE="500M"
|
||||
|
||||
DO_DOCKER=1
|
||||
DOCKER_UNTIL="168h"
|
||||
DOCKER_AGGRESSIVE=0
|
||||
DOCKER_VOLUMES=0
|
||||
|
||||
DO_ORPHANS=1
|
||||
DO_AUR_CACHE=1
|
||||
AUR_CACHE_DAYS=30
|
||||
THUMB_CACHE_DAYS=30
|
||||
|
||||
LOG_DIR="/var/log/arch-maintenance"
|
||||
LOG_FILE=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Arch Linux Wartungsskript
|
||||
|
||||
Usage:
|
||||
sudo arch-maintenance.sh [Optionen]
|
||||
|
||||
Sicherer Standard:
|
||||
Ohne --apply wird nur analysiert / simuliert.
|
||||
Docker-Netze bleiben auch mit --apply und --docker-aggressive erhalten.
|
||||
User-Caches: nur alte yay/paru-Eintraege und Thumbnails, kein globales Leeren.
|
||||
|
||||
Optionen:
|
||||
--apply Wirklich bereinigen
|
||||
--yes Keine Rueckfragen stellen
|
||||
--no-docker Docker-Bereinigung ueberspringen
|
||||
--docker-aggressive Auch ungenutzte Docker-Images entfernen (-a)
|
||||
--docker-volumes Ungenutzte Docker-Volumes entfernen; Vorsicht: kann Daten loeschen
|
||||
--no-orphans Pacman-Orphans nicht entfernen
|
||||
--no-aur-cache yay/paru Build-Caches nicht bereinigen
|
||||
--pacman-keep N N alte Versionen installierter Pakete behalten, Default: 3
|
||||
--journal-age AGE Journal-Alter, Default: 14d
|
||||
--journal-size SIZE Journal-Groesse, Default: 500M
|
||||
--docker-until AGE Docker prune Filter, Default: 168h
|
||||
--help Hilfe anzeigen
|
||||
|
||||
Beispiele:
|
||||
sudo arch-maintenance.sh
|
||||
sudo arch-maintenance.sh --apply
|
||||
sudo arch-maintenance.sh --apply --yes
|
||||
sudo arch-maintenance.sh --apply --docker-aggressive
|
||||
sudo arch-maintenance.sh --apply --docker-volumes
|
||||
EOF
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
log "WARN: $*"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cmdline() {
|
||||
printf '%q ' "$@"
|
||||
}
|
||||
|
||||
have_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local prompt="$1"
|
||||
|
||||
if (( YES )); then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -t 0 ]]; then
|
||||
warn "Nicht-interaktive Shell ohne --yes: '$prompt' wird uebersprungen."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local answer
|
||||
read -r -p "$prompt [y/N] " answer
|
||||
[[ "$answer" =~ ^([yYjJ]|yes|YES|ja|JA)$ ]]
|
||||
}
|
||||
|
||||
execute() {
|
||||
local description="$1"
|
||||
shift
|
||||
|
||||
log "==> $description"
|
||||
log "CMD: $(cmdline "$@")"
|
||||
|
||||
if (( APPLY )); then
|
||||
if "$@"; then
|
||||
log "OK: $description"
|
||||
else
|
||||
warn "Fehlgeschlagen, fahre fort: $description"
|
||||
fi
|
||||
else
|
||||
log "DRY-RUN: nicht ausgefuehrt"
|
||||
fi
|
||||
}
|
||||
|
||||
run_and_log() {
|
||||
local description="$1"
|
||||
shift
|
||||
|
||||
log "==> $description"
|
||||
log "CMD: $(cmdline "$@")"
|
||||
|
||||
if "$@"; then
|
||||
log "OK: $description"
|
||||
else
|
||||
warn "Fehlgeschlagen, fahre fort: $description"
|
||||
fi
|
||||
}
|
||||
|
||||
init_logging() {
|
||||
mkdir -p "$LOG_DIR" || die "Kann Log-Verzeichnis nicht erstellen: $LOG_DIR"
|
||||
chmod 0755 "$LOG_DIR" || true
|
||||
|
||||
LOG_FILE="$LOG_DIR/maintenance-$(date '+%Y%m%d-%H%M%S').log"
|
||||
touch "$LOG_FILE" || die "Kann Log-Datei nicht erstellen: $LOG_FILE"
|
||||
chmod 0644 "$LOG_FILE" || true
|
||||
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
log "Log-Datei: $LOG_FILE"
|
||||
if (( APPLY )); then
|
||||
log "Modus: APPLY"
|
||||
else
|
||||
log "Modus: DRY-RUN"
|
||||
fi
|
||||
}
|
||||
|
||||
require_root() {
|
||||
[[ "${EUID:-$(id -u)}" -eq 0 ]] || die "Bitte mit sudo/root ausfuehren."
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--apply)
|
||||
APPLY=1
|
||||
;;
|
||||
--yes)
|
||||
YES=1
|
||||
;;
|
||||
--no-docker)
|
||||
DO_DOCKER=0
|
||||
;;
|
||||
--docker-aggressive)
|
||||
DOCKER_AGGRESSIVE=1
|
||||
;;
|
||||
--docker-volumes)
|
||||
DOCKER_VOLUMES=1
|
||||
;;
|
||||
--no-orphans)
|
||||
DO_ORPHANS=0
|
||||
;;
|
||||
--no-aur-cache)
|
||||
DO_AUR_CACHE=0
|
||||
;;
|
||||
--pacman-keep)
|
||||
shift
|
||||
[[ "${1:-}" =~ ^[0-9]+$ ]] || die "--pacman-keep erwartet eine Zahl"
|
||||
PACMAN_KEEP="$1"
|
||||
;;
|
||||
--journal-age)
|
||||
shift
|
||||
JOURNAL_MAX_AGE="${1:-}"
|
||||
[[ -n "$JOURNAL_MAX_AGE" ]] || die "--journal-age erwartet einen Wert"
|
||||
;;
|
||||
--journal-size)
|
||||
shift
|
||||
JOURNAL_MAX_SIZE="${1:-}"
|
||||
[[ -n "$JOURNAL_MAX_SIZE" ]] || die "--journal-size erwartet einen Wert"
|
||||
;;
|
||||
--docker-until)
|
||||
shift
|
||||
DOCKER_UNTIL="${1:-}"
|
||||
[[ -n "$DOCKER_UNTIL" ]] || die "--docker-until erwartet einen Wert"
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "Unbekannte Option: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
real_user() {
|
||||
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER:-}" != "root" ]]; then
|
||||
printf '%s\n' "$SUDO_USER"
|
||||
else
|
||||
logname 2>/dev/null || printf 'root\n'
|
||||
fi
|
||||
}
|
||||
|
||||
home_of_user() {
|
||||
local user="$1"
|
||||
getent passwd "$user" | cut -d: -f6
|
||||
}
|
||||
|
||||
show_disk_usage() {
|
||||
log "==> Speicherbelegung"
|
||||
df -hT / /home /var 2>/dev/null || df -hT
|
||||
}
|
||||
|
||||
cleanup_pacman_cache() {
|
||||
log "==> Pacman Cache"
|
||||
|
||||
if ! have_cmd paccache; then
|
||||
warn "paccache nicht gefunden. Installiere es mit: sudo pacman -S --needed pacman-contrib"
|
||||
return
|
||||
fi
|
||||
|
||||
if (( APPLY )); then
|
||||
execute "Pacman Cache bereinigen; letzte ${PACMAN_KEEP} Versionen installierter Pakete behalten" \
|
||||
paccache -r -k "$PACMAN_KEEP"
|
||||
|
||||
execute "Pacman Cache fuer deinstallierte Pakete bereinigen; ${PACMAN_KEEP_UNINSTALLED} Versionen behalten" \
|
||||
paccache -r -u -k "$PACMAN_KEEP_UNINSTALLED"
|
||||
else
|
||||
run_and_log "Pacman Cache Dry-Run fuer installierte Pakete" \
|
||||
paccache -d -r -k "$PACMAN_KEEP"
|
||||
|
||||
run_and_log "Pacman Cache Dry-Run fuer deinstallierte Pakete" \
|
||||
paccache -d -r -u -k "$PACMAN_KEEP_UNINSTALLED"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_orphans() {
|
||||
(( DO_ORPHANS )) || {
|
||||
log "Pacman-Orphans: uebersprungen"
|
||||
return
|
||||
}
|
||||
|
||||
log "==> Pacman Orphans"
|
||||
|
||||
if ! have_cmd pacman; then
|
||||
warn "pacman nicht gefunden?"
|
||||
return
|
||||
fi
|
||||
|
||||
local -a orphans=()
|
||||
mapfile -t orphans < <(pacman -Qqdt 2>/dev/null || true)
|
||||
|
||||
if ((${#orphans[@]} == 0)); then
|
||||
log "Keine Orphan-Pakete gefunden."
|
||||
return
|
||||
fi
|
||||
|
||||
log "Gefundene Orphan-Pakete:"
|
||||
printf ' %s\n' "${orphans[@]}"
|
||||
|
||||
if (( APPLY )); then
|
||||
if confirm "Diese Orphan-Pakete mit pacman -Rns entfernen?"; then
|
||||
pacman -Rns --noconfirm "${orphans[@]}" || warn "Orphan-Entfernung fehlgeschlagen."
|
||||
else
|
||||
log "Orphan-Entfernung uebersprungen."
|
||||
fi
|
||||
else
|
||||
log "DRY-RUN: Orphans wuerden mit pacman -Rns entfernt."
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_journal() {
|
||||
log "==> systemd Journal"
|
||||
|
||||
if ! have_cmd journalctl; then
|
||||
warn "journalctl nicht gefunden."
|
||||
return
|
||||
fi
|
||||
|
||||
run_and_log "Journal-Groesse vor Bereinigung" journalctl --disk-usage
|
||||
|
||||
if (( APPLY )); then
|
||||
execute "Journal rotieren" journalctl --rotate
|
||||
execute "Journal vacuum: max. ${JOURNAL_MAX_AGE}, max. ${JOURNAL_MAX_SIZE}" \
|
||||
journalctl --vacuum-time="$JOURNAL_MAX_AGE" --vacuum-size="$JOURNAL_MAX_SIZE"
|
||||
run_and_log "Journal-Groesse nach Bereinigung" journalctl --disk-usage
|
||||
else
|
||||
log "DRY-RUN: Journal wuerde rotiert und mit --vacuum-time=${JOURNAL_MAX_AGE} --vacuum-size=${JOURNAL_MAX_SIZE} begrenzt."
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_tmpfiles() {
|
||||
log "==> systemd-tmpfiles"
|
||||
|
||||
if ! have_cmd systemd-tmpfiles; then
|
||||
warn "systemd-tmpfiles nicht gefunden."
|
||||
return
|
||||
fi
|
||||
|
||||
execute "Temporäre Dateien nach systemd-tmpfiles-Regeln bereinigen" \
|
||||
systemd-tmpfiles --clean
|
||||
}
|
||||
|
||||
cleanup_docker() {
|
||||
(( DO_DOCKER )) || {
|
||||
log "Docker: uebersprungen"
|
||||
return
|
||||
}
|
||||
|
||||
log "==> Docker"
|
||||
|
||||
if ! have_cmd docker; then
|
||||
log "Docker nicht installiert, uebersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
warn "Docker daemon nicht erreichbar oder keine Berechtigung. Docker-Bereinigung uebersprungen."
|
||||
return
|
||||
fi
|
||||
|
||||
run_and_log "Docker Speicherbelegung vor Bereinigung" docker system df -v
|
||||
|
||||
log "Docker-Netze bleiben erhalten, auch wenn gerade kein Container sie verwendet."
|
||||
execute "Gestoppte Docker-Container bereinigen, erstellt vor ${DOCKER_UNTIL}" \
|
||||
docker container prune -f --filter "until=${DOCKER_UNTIL}"
|
||||
|
||||
if (( DOCKER_AGGRESSIVE )); then
|
||||
execute "Alle ungenutzten Docker-Images bereinigen, erstellt vor ${DOCKER_UNTIL}" \
|
||||
docker image prune -a -f --filter "until=${DOCKER_UNTIL}"
|
||||
else
|
||||
execute "Dangling Docker-Images bereinigen, erstellt vor ${DOCKER_UNTIL}" \
|
||||
docker image prune -f --filter "until=${DOCKER_UNTIL}"
|
||||
fi
|
||||
|
||||
execute "Docker Builder Cache bereinigen, aelter als ${DOCKER_UNTIL}" \
|
||||
docker builder prune -f --filter "until=${DOCKER_UNTIL}"
|
||||
|
||||
if (( DOCKER_VOLUMES )); then
|
||||
if (( APPLY )); then
|
||||
if confirm "Ungenutzte Docker-Volumes entfernen? Das kann Daten loeschen."; then
|
||||
execute "Ungenutzte Docker-Volumes entfernen; Volumes mit label keep bleiben erhalten" \
|
||||
docker volume prune -f --filter "label!=keep"
|
||||
else
|
||||
log "Docker-Volumes uebersprungen."
|
||||
fi
|
||||
else
|
||||
log "DRY-RUN: Ungenutzte Docker-Volumes wuerden mit docker volume prune entfernt; nur bei --docker-volumes."
|
||||
fi
|
||||
else
|
||||
log "Docker-Volumes werden nicht bereinigt. Nutze --docker-volumes, wenn du das wirklich willst."
|
||||
fi
|
||||
|
||||
run_and_log "Docker Speicherbelegung nach Bereinigung / Analyse" docker system df -v
|
||||
}
|
||||
|
||||
cleanup_user_caches() {
|
||||
(( DO_AUR_CACHE )) || {
|
||||
log "User/AUR Cache: uebersprungen"
|
||||
return
|
||||
}
|
||||
|
||||
local user home
|
||||
user="$(real_user)"
|
||||
home="$(home_of_user "$user")"
|
||||
|
||||
if [[ -z "$home" || ! -d "$home" ]]; then
|
||||
warn "Home-Verzeichnis fuer User '$user' nicht gefunden."
|
||||
return
|
||||
fi
|
||||
|
||||
log "==> User Cache fuer $user ($home)"
|
||||
|
||||
local dir
|
||||
|
||||
for dir in "$home/.cache/yay" "$home/.cache/paru"; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
|
||||
log "AUR Cache Kandidaten in $dir, aelter als ${AUR_CACHE_DAYS} Tage:"
|
||||
find "$dir" -mindepth 1 -maxdepth 1 -mtime +"$AUR_CACHE_DAYS" -print 2>/dev/null || true
|
||||
|
||||
if (( APPLY )); then
|
||||
find "$dir" -mindepth 1 -maxdepth 1 -mtime +"$AUR_CACHE_DAYS" -exec rm -rf -- {} + 2>/dev/null \
|
||||
|| warn "Konnte AUR Cache nicht vollstaendig bereinigen: $dir"
|
||||
else
|
||||
log "DRY-RUN: alte Eintraege in $dir wuerden geloescht."
|
||||
fi
|
||||
done
|
||||
|
||||
dir="$home/.cache/thumbnails"
|
||||
if [[ -d "$dir" ]]; then
|
||||
log "Thumbnail Cache Kandidaten in $dir, aelter als ${THUMB_CACHE_DAYS} Tage:"
|
||||
find "$dir" -type f -mtime +"$THUMB_CACHE_DAYS" -print 2>/dev/null || true
|
||||
|
||||
if (( APPLY )); then
|
||||
find "$dir" -type f -mtime +"$THUMB_CACHE_DAYS" -delete 2>/dev/null \
|
||||
|| warn "Konnte Thumbnail Cache nicht vollstaendig bereinigen."
|
||||
find "$dir" -type d -empty -delete 2>/dev/null || true
|
||||
else
|
||||
log "DRY-RUN: alte Thumbnail-Dateien wuerden geloescht."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
require_root
|
||||
init_logging
|
||||
|
||||
log "Starte Arch-Wartung"
|
||||
show_disk_usage
|
||||
|
||||
cleanup_pacman_cache
|
||||
cleanup_orphans
|
||||
cleanup_journal
|
||||
cleanup_tmpfiles
|
||||
cleanup_docker
|
||||
cleanup_user_caches
|
||||
|
||||
show_disk_usage
|
||||
log "Fertig. Log: $LOG_FILE"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in new issue
Block a user