264 lines
7.5 KiB
Bash
264 lines
7.5 KiB
Bash
#!/usr/bin/env bash
|
|
# Arch LXC Secure Cleanup & Audit Script
|
|
# Usage: sudo ./arch-cleanup.sh [--dry-run]
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Safety check
|
|
# ---------------------------------------------------------------------------
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "Error: Run this script with sudo: sudo $0 [--dry-run]"
|
|
exit 1
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Config & User detection
|
|
# ---------------------------------------------------------------------------
|
|
DRY_RUN=false
|
|
if [[ "${1:-}" == "--dry-run" ]]; then
|
|
DRY_RUN=true
|
|
echo ">>> DRY-RUN MODE: No changes will be made <<<"
|
|
echo ""
|
|
fi
|
|
|
|
TARGET_USER="${SUDO_USER:-$(logname 2>/dev/null || echo '')}"
|
|
TARGET_UID=""
|
|
|
|
if [[ "$TARGET_USER" == "root" ]]; then
|
|
TARGET_USER=""
|
|
fi
|
|
if [[ -n "$TARGET_USER" ]]; then
|
|
TARGET_UID="$(id -u "$TARGET_USER" 2>/dev/null || echo '')"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
RED=$'\e[31m'
|
|
GREEN=$'\e[32m'
|
|
YELLOW=$'\e[33m'
|
|
BLUE=$'\e[34m'
|
|
CYAN=$'\e[36m'
|
|
NC=$'\e[0m'
|
|
|
|
log() { printf "${GREEN}[INFO]${NC} %s\n" "$*"; }
|
|
warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
|
|
err() { printf "${RED}[ERROR]${NC} %s\n" "$*"; }
|
|
section() { printf "\n${BLUE}=== %s ===${NC}\n" "$*"; }
|
|
|
|
confirm() {
|
|
[[ "$DRY_RUN" == true ]] && return 0
|
|
local r
|
|
read -r -p "$1 [y/N] " r
|
|
[[ "$r" =~ ^[Yy]$ ]]
|
|
}
|
|
|
|
run() {
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
printf "${CYAN}[DRY-RUN]${NC} %s\n" "$*"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 1 — Audit: Where is the 170 GB?
|
|
# ---------------------------------------------------------------------------
|
|
section "Disk Usage Summary"
|
|
df -hT / | grep -E 'Filesystem|/dev/'
|
|
|
|
section "Suspect Directory Sizes"
|
|
suspects=(
|
|
/var/cache/pacman/pkg
|
|
/var/lib/containers
|
|
/var/log
|
|
/var/log/journal
|
|
/var/lib/systemd/coredump
|
|
/var/tmp
|
|
/tmp
|
|
/root
|
|
)
|
|
|
|
for dir in "${suspects[@]}"; do
|
|
[[ -d "$dir" ]] || continue
|
|
sz=$(du -sh "$dir" 2>/dev/null | awk '{print $1}')
|
|
printf " %-40s %s\n" "$dir" "$sz"
|
|
done
|
|
|
|
if [[ -n "$TARGET_USER" && -d "/home/$TARGET_USER" ]]; then
|
|
section "User '$TARGET_USER' Suspect Directories"
|
|
user_suspects=(
|
|
"/home/$TARGET_USER/.cache"
|
|
"/home/$TARGET_USER/.cache/paru"
|
|
"/home/$TARGET_USER/.cache/paru/clone"
|
|
"/home/$TARGET_USER/.cache/paru/pkg"
|
|
"/home/$TARGET_USER/.cache/yay"
|
|
"/home/$TARGET_USER/.local/share/containers"
|
|
)
|
|
for dir in "${user_suspects[@]}"; do
|
|
[[ -e "$dir" ]] || continue
|
|
sz=$(du -sh "$dir" 2>/dev/null | awk '{print $1}')
|
|
printf " %-50s %s\n" "$dir" "$sz"
|
|
done
|
|
fi
|
|
|
|
section "Large Individual Files (>1 GB)"
|
|
for search_root in /var /home /root /opt; do
|
|
[[ -d "$search_root" ]] || continue
|
|
find "$search_root" -xdev -maxdepth 5 -type f -size +1G -exec ls -lh {} + 2>/dev/null | head -n 20
|
|
done
|
|
|
|
echo ""
|
|
if ! confirm "Continue with cleanup?"; then
|
|
log "Aborted by user."
|
|
exit 0
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 2 — Cleanup
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# --- Pacman cache ---
|
|
section "Pacman Cache Cleanup"
|
|
if command -v paccache &>/dev/null; then
|
|
log "Removing cached packages for uninstalled software..."
|
|
run paccache -ruv
|
|
|
|
log "Keeping last 2 versions of installed packages, removing older..."
|
|
run paccache -rvk2
|
|
else
|
|
warn "'paccache' (pacman-contrib) not found."
|
|
if confirm "Install pacman-contrib now? (Recommended; needs a little free space)"; then
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log "[DRY-RUN] Would install pacman-contrib and run paccache"
|
|
else
|
|
pacman -S --noconfirm --needed pacman-contrib
|
|
paccache -ruv
|
|
paccache -rvk2
|
|
fi
|
|
else
|
|
if confirm "Run fallback 'pacman -Sc'? (Removes ALL uninstalled cached packages)"; then
|
|
run pacman -Sc --noconfirm
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --- Orphaned packages ---
|
|
section "Orphaned Packages"
|
|
orphans="$(pacman -Qdtq 2>/dev/null || true)"
|
|
if [[ -z "$orphans" ]]; then
|
|
log "No orphaned packages found."
|
|
else
|
|
warn "Orphaned packages found:"
|
|
echo "$orphans"
|
|
if confirm "Remove these orphaned packages?"; then
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log "[DRY-RUN] Would remove: $orphans"
|
|
else
|
|
# shellcheck disable=SC2086
|
|
pacman -Rns --noconfirm $orphans
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --- AUR Helpers (paru / yay) ---
|
|
section "AUR Helper Cache Cleanup"
|
|
|
|
clean_aur() {
|
|
local helper=$1
|
|
local binpath clone_dir pkg_dir sz
|
|
binpath="$(command -v "$helper" 2>/dev/null || true)"
|
|
|
|
if [[ -z "$binpath" ]]; then
|
|
log "$helper not installed."
|
|
return
|
|
fi
|
|
|
|
if [[ -z "$TARGET_USER" ]]; then
|
|
warn "Cannot determine non-root user for $helper cleanup."
|
|
return
|
|
fi
|
|
|
|
log "Running safe cache clean for $helper..."
|
|
run sudo -u "$TARGET_USER" "$binpath" -Sc --noconfirm || warn "$helper -Sc finished with warnings"
|
|
|
|
clone_dir="/home/$TARGET_USER/.cache/$helper/clone"
|
|
if [[ -d "$clone_dir" ]]; then
|
|
sz=$(du -sh "$clone_dir" 2>/dev/null | awk '{print $1}')
|
|
warn "$helper clone directory size: $sz"
|
|
if confirm "Delete $helper PKGBUILD clones? (They must be re-downloaded later)"; then
|
|
run rm -rf "${clone_dir:?}"/*
|
|
log "Cleaned $clone_dir"
|
|
fi
|
|
fi
|
|
|
|
pkg_dir="/home/$TARGET_USER/.cache/$helper/pkg"
|
|
if [[ -d "$pkg_dir" ]] && command -v paccache &>/dev/null; then
|
|
log "Running paccache on $helper package dir..."
|
|
run paccache -ruv --cachedir "$pkg_dir" || true
|
|
fi
|
|
}
|
|
|
|
clean_aur "paru"
|
|
clean_aur "yay"
|
|
|
|
# --- Podman (rootful + rootless) ---
|
|
section "Podman Cleanup"
|
|
|
|
if command -v podman &>/dev/null; then
|
|
log "Rootful Podman disk usage:"
|
|
run podman system df || true
|
|
|
|
if confirm "Prune unused rootful Podman resources (containers, networks, images, build cache)?"; then
|
|
run podman system prune -a -f
|
|
fi
|
|
else
|
|
log "Podman (rootful) not installed."
|
|
fi
|
|
|
|
if [[ -n "$TARGET_USER" && -n "$TARGET_UID" ]]; then
|
|
if sudo -u "$TARGET_USER" podman --version &>/dev/null; then
|
|
log "Rootless Podman disk usage for $TARGET_USER:"
|
|
run sudo -u "$TARGET_USER" podman system df || true
|
|
|
|
if confirm "Prune unused rootless Podman resources for $TARGET_USER?"; then
|
|
run sudo -u "$TARGET_USER" \
|
|
XDG_RUNTIME_DIR="/run/user/$TARGET_UID" \
|
|
podman system prune -a -f ||
|
|
run sudo -u "$TARGET_USER" podman system prune -a -f
|
|
fi
|
|
else
|
|
log "Rootless Podman not configured for $TARGET_USER."
|
|
fi
|
|
else
|
|
log "Skipping rootless Podman (no target user detected)."
|
|
fi
|
|
|
|
# --- Logs ---
|
|
section "System Logs"
|
|
if [[ -d /var/log/journal ]] && command -v journalctl &>/dev/null; then
|
|
jsz=$(du -sh /var/log/journal 2>/dev/null | awk '{print $1}')
|
|
log "Persistent journal size: $jsz"
|
|
if confirm "Vacuum journal to max 500 MB?"; then
|
|
run journalctl --vacuum-size=500M
|
|
fi
|
|
else
|
|
log "No persistent systemd journal detected."
|
|
fi
|
|
|
|
if find /var/log -maxdepth 2 -type f -size +500M 2>/dev/null | grep -q .; then
|
|
warn "Large uncompressed log files remain in /var/log. You may want to truncate them manually."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Final Summary
|
|
# ---------------------------------------------------------------------------
|
|
section "Final Disk Usage"
|
|
df -hT /
|
|
|
|
log "Cleanup complete. Review the output above to see how much was recovered."
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log "This was a DRY RUN. Re-run without --dry-run to actually free space."
|
|
fi
|