Preserve model caches and Docker networks during cleanup

This commit is contained in:
admin committed 2026-09-10 10:01:46 +02:00
1 parent 3c1080e152
commit e8512b56f5
9 files changed
+854 -5

No files matched your search

+4
View File
@@ -17,3 +17,7 @@ dist/
/hermes-test-*/
__pycache__/
*.pyc
# Lokale KI-Modelle und unvollständige Downloads
*.gguf
*.downloadInProgress
+9
View File
@@ -1,3 +1,12 @@
# 2026-09-10 — Cleanup-Reparatur und dauerhafte Modelle
- Korrigierte Cleanup-Skripte unter maintenance/ aufgenommen: kein globales
Cache-Leeren im Sanity-Skript und keine Docker-Netzbereinigung.
- Vorlage für lokale Modell-/Projektor-Dateien ergänzt; baseline beibehalten.
- Wiederherstellung von Modellserver und hermes-net samt Prüfgrenzen dokumentiert.
- Modelldateien/Downloads von Git ausgeschlossen; Cleanup-Syntaxprüfung ergänzt.
- Workstation-Notiz zur inzwischen erfolgten Updater-Installation korrigiert.
# 2026-09-09 — Workstation-Abgleich
- Healthcheck und llama-Helfer übernommen; portable Helferpfade ergänzt.
+6
View File
@@ -206,3 +206,9 @@ eingefrorenes Arch-Abbild. Für denselben Modell-/Backendstand Quell-SHA sowie
Modell-/Projektor-Prüfsummen notieren. Der offizielle Hermes-Installer und Arch
liefern beim Neuaufsetzen sonst den dann verfügbaren Stand. Technische Quellen
stehen in `docs/LLAMA_INSTALL.md` und `docs/RTX3080.md`.
## Cleanup und dauerhafte Modellablage
Siehe [Cleanup und Wiederherstellung](docs/CLEANUP_RECOVERY.md) und
[Vorlage für lokale Modelle](config/llama-local.env.example).
Die optionalen Skripte in maintenance/ werden nicht automatisch installiert.
+13
View File
@@ -0,0 +1,13 @@
# Alternative zur HF-Bootstrap-Vorlage; erst nach Bereitstellung beider Dateien.
# Bestehende llama.env nur gezielt bearbeiten, nicht ungeprüft überschreiben.
LLAMA_SERVER_BIN=""
LLAMA_MODEL_REVISION="0669b98607d47046c7c2b3f801011d54a08cfccf"
LLAMA_MODEL_DIR="$HOME/.local/share/hermes-stack/models/qwen38-27b/$LLAMA_MODEL_REVISION"
LLAMA_MODEL="$LLAMA_MODEL_DIR/Qwen3.8-27B-Q4_K_M.gguf"
LLAMA_MMPROJ="$LLAMA_MODEL_DIR/mmproj-Qwen3.8-27B-Q8_0.gguf"
LLAMA_HF_REPO=""
LLAMA_ALIAS="qwen38-27b-local"
LLAMA_HOST="127.0.0.1"
LLAMA_PORT="8080"
LLAMA_PROFILE="baseline"
LLAMA_ARGS=()
+88
View File
@@ -0,0 +1,88 @@
# Cleanup-Verträglichkeit und Wiederherstellung
## Verifizierter Stand vom 2026-09-10
Ein globales Leeren von ~/.cache entfernte Modell und Bildprojektor.
Beim Aufwachen aus dem Idle-Sleep brach llama.cpp deshalb ab. Docker system
prune entfernte außerdem das gerade unbenutzte Netz hermes-net.
Der automatische Dienstneustart lud die Dateien erneut herunter.
Nach vollständigem Download wurden Modell und Projektor als reguläre Dateien
außerhalb des Caches abgelegt und mit cmp gegen die Quellen geprüft.
Nach Umstellung und kontrolliertem Neustart bestätigte das Serverlog beide
neuen Pfade. Eine Textanfrage lieferte OK, der Healthcheck Result=success.
Profil baseline bleibt unverändert (65536 Kontext, 1536 MiB Fit-Zielmarge).
Damit sind Modellstart, kurze Textinferenz und leichte Gesundheitsprüfung
belegt; ein kompletter Bildjob und der Docker-Netzwerk-Canary wurden bei
dieser Wiederherstellung noch nicht erneut getestet.
## Modellablage auf weiteren Rechnern
Die bestehende config/llama.env.example dient weiter zum HF-Bootstrap.
Downloads vollständig beenden lassen. Keine .downloadInProgress-Dateien
übernehmen. Die verifizierte Revision lautet:
0669b98607d47046c7c2b3f801011d54a08cfccf
Die Quellen liegen unter:
~/.cache/huggingface/hub/models--ggml-org--Qwen3.8-27B-GGUF/snapshots/REVISION/
Die dauerhaften Dateien gehören unter:
~/.local/share/hermes-stack/models/qwen38-27b/REVISION/
Benötigt werden Qwen3.8-27B-Q4_K_M.gguf und mmproj-Qwen3.8-27B-Q8_0.gguf.
Cache-Symlinks beim Kopieren dereferenzieren (cp -L), reguläre Kopien mit
cmp prüfen und die Cache-Quellen bis zur erfolgreichen Laufzeitprüfung behalten.
Falls der Bootstrap eine andere Revision lädt, diese zuerst prüfen und
bewusst auswählen; nicht verschiedene Modell-/Projektor-Revisionen mischen.
config/llama-local.env.example zeigt die lokale Konfiguration. In bestehender
llama.env nur LLAMA_MODEL und LLAMA_MMPROJ setzen und LLAMA_HF_REPO leeren;
Binary-Auswahl und Profil beibehalten. Vorher sichern. Auf neuen Rechnern die
Vorlage erst nach Bereitstellung der passenden Dateien verwenden.
Die Dateien werden nicht ins Git-Repository aufgenommen. Hermes-Updates
aktualisieren diese fest ausgewählte lokale Modellrevision nicht automatisch.
Prüfen: hermes-stack-llama-run --check und --print. Erst nach Jobende mit
hermes-stack-llama-restart --jobs-finished neu starten. Anschließend /health,
eine kurze Inferenz und hermes-stack-health.service prüfen. Die --check-Prüfung
allein belegt nur Konfiguration und Optionsnamen, keine funktionierende Inferenz.
## Docker-Netz
Am 10. September wurde hermes-net als normale Bridge wieder angelegt:
```bash
docker network create --driver bridge hermes-net
docker network inspect hermes-net
```
Nur bei fehlendem Netz ausführen. Vorhandene Netze nicht entfernen.
Docker wählte auf der Workstation 172.18.0.0/16 mit Gateway 172.18.0.1.
Diese Adressen sind kein portabler Sollwert; auf neuen Rechnern Docker wählen
lassen. Sonderoptionen des früher gelöschten Netzes waren nicht dokumentiert.
In der privaten config.env DOCKER_NETWORK=hermes-net setzen, wenn Hermes
dieses Netz verwendet. Die leere Deployment-Vorlage deaktiviert die Prüfung.
install.sh erstellt das Netz nicht; Anlage und Container-Verbindung separat prüfen.
## Cleanup-Skripte
maintenance/ enthält die korrigierten Skripte. install.sh installiert und
startet sie nicht. Auf der Workstation liegen sie unter ~/Scripts; vorhandene
Dateien vor einem manuellen Austausch sichern. Keine Zugangsdaten übernehmen.
- arch-maintenance.sh: standardmäßig Dry-Run, Änderungen erst mit --apply.
Container, Images und Builder-Cache werden getrennt bereinigt; Netze bleiben.
--docker-volumes bleibt eine ausdrückliche, datenlöschende Option.
- arch-sanity-cleanup.sh: interaktiv, kein allgemeiner Dry-Run. Im allgemeinen
User-Cache nur Thumbnail-/Fontconfig-Dateien älter als 30 Tage löschen.
Modellcache und Downloads bleiben erhalten. Docker-Netze werden nicht bereinigt.
- Beide Skripte behalten weitere Wartungsfunktionen, etwa Paket- und AUR-
Bereinigung. --docker-aggressive bzw. die Image-Rückfrage kann weiterhin
unbenutzte Runtime-Images entfernen; diese müssen später erneut geladen werden.
- Eigene tmpfiles-Regeln, andere Cleanup-Werkzeuge und manuelle Löschbefehle
sind hiervon nicht erfasst. Ein Netzname allein schützt nicht vor prune.
Die Korrekturen wurden mit bash -n sowie gezielten Tests mit temporären Dateien
und einem Docker-Ersatzprogramm geprüft: Modell-/Download-Dateien erhalten,
alte Thumbnail-/Fontdateien entfernt, keine Docker-Netzbereinigung aufgerufen.
Die normale Repo-Prüfung prüft die Cleanup-Skripte auf Syntax, führt sie aber
nicht aus. Root-Wartung und echte Docker-Prunes sind kein Bestandteil der Tests.
+10 -4
View File
@@ -116,7 +116,13 @@ Die Docker-Netzprüfung ist optional: DOCKER_NETWORK in config.env auf den
Namen des verwendeten Netzes setzen. Leer bedeutet keine Netzprüfung.
Die Prüfung erstellt kein Netz. Private Konfigurationen bleiben außerhalb Git.
Der Repository-Updater enthält die aufgerufenen Retry-Pfade und den gemeinsamen
Benachrichtigungshelfer. Die zuletzt verglichene installierte Fassung enthält
noch ungenutzte Retry-Funktionen. Der Git-Abgleich aktualisiert die Workstation
nicht; die Übernahme des Repository-Updaters steht separat aus.
Der Repository-Updater wurde am 2026-09-09 mit Sicherung auf die Workstation
übernommen und bytegleich geprüft; die Konfigurationsdefaults wurden ergänzt.
Ein echter Update-/Rollback-Lauf der neuen Fassung war damit noch nicht geprüft.
## Wiederherstellung vom 2026-09-10
Modell und Projektor liegen jetzt außerhalb von ~/.cache. Die korrigierten
Cleanup-Skripte sind unter ~/Scripts installiert. Details und Prüfgrenzen:
[CLEANUP_RECOVERY.md](CLEANUP_RECOVERY.md).
+443
View File
@@ -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 "$@"
+280
View File
@@ -0,0 +1,280 @@
#!/usr/bin/env bash
set -euo pipefail
# Arch Sanity Cleanup Script (with rclone-mount exclusions)
# - conservative defaults
# - prompts before destructive actions
# - avoids scanning rclone mounts (OneDrive/Nextcloud)
# - preserves model caches and Docker networks (2026-09-10)
# ----------------- CONFIG: set your rclone mountpoints here -----------------
ONEDRIVE_MOUNT="${ONEDRIVE_MOUNT:-$HOME/OneDrive}"
NEXTCLOUD_MOUNT="${NEXTCLOUD_MOUNT:-$HOME/Nextcloud}"
EXCLUDE_PATHS=("$ONEDRIVE_MOUNT" "$NEXTCLOUD_MOUNT")
# ---------------------------------------------------------------------------
have() { command -v "$1" >/dev/null 2>&1; }
hr() { printf "\n%s\n" "------------------------------------------------------------"; }
ask() {
local prompt="$1"
read -r -p "$prompt [y/N] " reply
[[ "${reply:-}" =~ ^[Yy]$ ]]
}
bytes_free_root() {
df -B1 --output=avail / | tail -n1 | tr -d ' '
}
to_gib() {
python - <<'PY'
import sys
b=int(sys.stdin.read().strip() or "0")
print(f"{b/1024/1024/1024:.2f} GiB")
PY
}
report_space() {
local b
b="$(bytes_free_root)"
echo "Free space on / : $(printf "%s" "$b" | to_gib)"
}
is_mountpoint() {
local p="$1"
[[ -n "$p" ]] && mountpoint -q "$p" 2>/dev/null
}
du_safe() {
# du with exclusions for rclone mounts
local path="$1"
shift || true
local args=(du -sh)
for ex in "${EXCLUDE_PATHS[@]}"; do
args+=(--exclude="$ex")
done
"${args[@]}" "$path" "$@" 2>/dev/null || true
}
find_bigfiles_safe() {
# find with pruning rclone mounts
local root="$1"
local minsize="$2" # e.g. +2G
shift 2 || true
# If the root itself is (or is under) a mount we want to exclude, bail.
for ex in "${EXCLUDE_PATHS[@]}"; do
[[ "$root" == "$ex" || "$root" == "$ex/"* ]] && return 0
done
# Build prune expression for all excluded paths that exist
local prune=()
for ex in "${EXCLUDE_PATHS[@]}"; do
[[ -e "$ex" ]] && prune+=( -path "$ex" -o -path "$ex/*" -o )
done
if (( ${#prune[@]} > 0 )); then
# Remove trailing -o
unset 'prune[${#prune[@]}-1]'
find "$root" \( "${prune[@]}" \) -prune -o -type f -size "$minsize" -printf "%10s %p\n" 2>/dev/null \
| sort -nr | head -n 15 || true
else
find "$root" -type f -size "$minsize" -printf "%10s %p\n" 2>/dev/null \
| sort -nr | head -n 15 || true
fi
}
cleanup_safe_user_caches() {
local cache_root="${1:-${XDG_CACHE_HOME:-$HOME/.cache}}"
local dir
echo "Gezielte User-Cache-Bereinigung: nur Thumbnail-/Fontconfig-Dateien aelter als 30 Tage."
echo "Andere Caches, insbesondere Hugging-Face-Modelle, bleiben erhalten."
if [[ -L "$cache_root" ]]; then
echo "Cache-Wurzel ist ein Symlink; Bereinigung uebersprungen."
return 0
fi
for dir in "$cache_root/thumbnails" "$cache_root/fontconfig"; do
[[ -d "$dir" && ! -L "$dir" ]] || continue
if is_mountpoint "$dir"; then
echo "Mountpoint bleibt erhalten: $dir"
continue
fi
du -sh "$dir" 2>/dev/null || true
if ask "Alte Dateien in $dir entfernen?"; then
find "$dir" -xdev -type f -mtime +30 -delete
fi
done
}
cleanup_docker_preserving_networks() {
if ! docker info >/dev/null 2>&1; then
echo "Docker nicht erreichbar; Bereinigung uebersprungen."
return 0
fi
docker system df || true
echo "Docker-Netze und Volumes bleiben bei dieser Bereinigung erhalten."
if ask "Gestoppte Container, dangling Images und Build-Cache entfernen?"; then
docker container prune -f
docker image prune -f
docker builder prune -f
fi
if ask "Auch alle ungenutzten Docker-Images entfernen (-a)?"; then
docker image prune -a -f
fi
}
# ----------------- intro -----------------
hr
echo "Arch Sanity Cleanup"
echo "Date: $(date)"
report_space
hr
echo "Excluding rclone mounts from scans:"
for ex in "${EXCLUDE_PATHS[@]}"; do
if [[ -e "$ex" ]]; then
if is_mountpoint "$ex"; then
echo " - $ex (mountpoint ✅ excluded)"
else
echo " - $ex (path exists, but not a mountpoint ⚠️ still excluded)"
fi
else
echo " - $ex (not found ⚠️ will be ignored)"
fi
done
# ----------------- quick size hints -----------------
hr
echo "Quick size hints (excluding rclone mounts; may take a bit):"
for p in /var /var/cache /var/log "$HOME" "$HOME/.cache"; do
[[ -e "$p" ]] && du_safe "$p"
done
# ----------------- pacman cache -----------------
hr
if [[ -d /var/cache/pacman/pkg ]]; then
echo "Pacman cache size:"
sudo du -sh /var/cache/pacman/pkg 2>/dev/null || true
if have paccache; then
echo
echo "paccache available."
echo " - paccache -r keeps last 3 versions (safe default)"
echo " - paccache -rk1 keeps last 1 version (more aggressive)"
if ask "Clean pacman cache (keep last 3 versions)?" ; then
sudo paccache -r
fi
if ask "Also clean pacman cache more aggressively (keep last 1 version)?" ; then
sudo paccache -rk1
fi
else
echo "paccache not found. Install pacman-contrib for safer cache cleanup:"
echo " sudo pacman -S pacman-contrib"
if ask "Run 'sudo pacman -Sc' (removes uninstalled pkg cache)?" ; then
sudo pacman -Sc
fi
fi
fi
# ----------------- orphan dependencies -----------------
hr
echo "Checking orphan packages (no longer required dependencies)..."
orphans="$(pacman -Qtdq 2>/dev/null || true)"
if [[ -n "${orphans}" ]]; then
echo "Orphans found:"
echo "${orphans}" | sed 's/^/ - /'
if ask "Remove ALL orphan packages (sudo pacman -Rns ...) ?" ; then
# shellcheck disable=SC2086
sudo pacman -Rns ${orphans}
fi
else
echo "No orphan packages found."
fi
# ----------------- foreign (AUR) info + yay cache -----------------
hr
echo "Foreign packages (usually AUR) installed:"
foreign="$(pacman -Qm 2>/dev/null || true)"
if [[ -n "${foreign}" ]]; then
echo "${foreign}" | sed 's/^/ - /' | head -n 50
[[ $(echo "${foreign}" | wc -l) -gt 50 ]] && echo " ... (showing first 50)"
else
echo "None."
fi
if have yay; then
hr
echo "yay cache size (if exists):"
[[ -d "$HOME/.cache/yay" ]] && du -sh "$HOME/.cache/yay" || echo " (no ~/.cache/yay)"
if ask "Clean yay build/cache (yay -Sc)?" ; then
yay -Sc
fi
fi
# ----------------- systemd journal cleanup -----------------
hr
if have journalctl; then
echo "Journald disk usage:"
sudo journalctl --disk-usage || true
if ask "Vacuum journal to 500M?" ; then
sudo journalctl --vacuum-size=500M
fi
if ask "Also vacuum journal older than 2 weeks?" ; then
sudo journalctl --vacuum-time=2weeks
fi
fi
# ----------------- user caches -----------------
hr
cleanup_safe_user_caches
# ----------------- dotnet / nuget caches -----------------
hr
if have dotnet; then
echo "NuGet package cache size:"
[[ -d "$HOME/.nuget/packages" ]] && du -sh "$HOME/.nuget/packages" || echo " (no ~/.nuget/packages)"
echo
if ask "Clear dotnet NuGet locals (dotnet nuget locals all --clear)?" ; then
dotnet nuget locals all --clear
fi
fi
# ----------------- docker / podman -----------------
hr
if have docker; then
cleanup_docker_preserving_networks
fi
if have podman; then
hr
echo "Podman disk usage:"
podman system df || true
echo
echo "WARNING: podman prune can delete unused images/containers."
if ask "Run 'podman system prune'?" ; then
podman system prune
fi
fi
# ----------------- flatpak -----------------
hr
if have flatpak; then
echo "Flatpak unused cleanup:"
if ask "Remove unused flatpak runtimes/apps (flatpak uninstall --unused)?" ; then
flatpak uninstall --unused
fi
fi
# ----------------- big files finder (informational) -----------------
hr
echo "Top large files (>2GiB) under /var and ~ (excluding rclone mounts):"
echo " /var:"
sudo bash -c "$(declare -f find_bigfiles_safe); $(declare -p EXCLUDE_PATHS); find_bigfiles_safe /var +2G" || true
echo " $HOME:"
find_bigfiles_safe "$HOME" +2G || true
# ----------------- summary -----------------
hr
echo "Cleanup done."
report_space
hr
echo "Tip: for interactive analysis, avoid scanning rclone mounts:"
echo " sudo ncdu --exclude $ONEDRIVE_MOUNT --exclude $NEXTCLOUD_MOUNT /"
+1 -1
View File
@@ -5,7 +5,7 @@ ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)"
failures=0
for script in "$ROOT_DIR/install.sh" "$ROOT_DIR/uninstall.sh" \
"$ROOT_DIR"/libexec/* "$ROOT_DIR"/scripts/*.sh "$ROOT_DIR"/config/*.example; do
"$ROOT_DIR"/libexec/* "$ROOT_DIR"/scripts/*.sh "$ROOT_DIR"/config/*.example "$ROOT_DIR"/maintenance/*.sh; do
if bash -n "$script"; then
printf 'OK bash -n %s\n' "${script#"$ROOT_DIR/"}"
else