#!/bin/bash set -euo pipefail # Workstation Package Cleanup Script # Version: 1.0.0 # Target: mw-pfeddersheim-workstation # Purpose: Remove identified cleanup candidates and log for verification MODE="apply" if [[ "${1:-}" == "--check" ]]; then MODE="check" echo "=== Running in CHECK mode (dry-run, no packages removed) ===" elif [[ "${1:-}" == "--help" ]]; then echo "Usage: $0 [--check | --apply | --help]" echo " --check Dry-run: show what would be removed (default)" echo " --apply Actually remove packages" echo " --help Show this help" exit 0 fi # Ensure log directory exists mkdir -p "$HOME/logs" 2>/dev/null || true LOCAL_LOG="$HOME/logs/cleanup-$(date +%Y%m%d-%H%M%S).log" BOLD=$(tput bold 2>/dev/null || true) RED=$(tput setaf 1 2>/dev/null || true) GREEN=$(tput setaf 2 2>/dev/null || true) RESET=$(tput sgr0 2>/dev/null || true) log() { echo -e "$*" | tee -a "$LOCAL_LOG"; } section() { log ""; log "${BOLD}--- $1 ---${RESET}"; } echo "" | tee -a "$LOCAL_LOG" log "=============================================" log " Workstation Package Cleanup" log " Mode: $MODE | Date: $(date)" log " Log: $LOCAL_LOG" log "=============================================" # Track totals TOTAL_CANDIDATES=0 TOTAL_REMOVED=0 TOTAL_ERRORS=0 SKIPPED=0 # Helper: add package to candidate list file add_candidates() { local category="$1" shift echo "$@" >> "$HOME/logs/.cleanup-candidates-${category}.txt" } # ============================================================ # 1. PRE-FLIGHT CHECKS # ============================================================ section "Pre-flight Checks" # Verify pacman is available if ! command -v pacman &> /dev/null; then log "${RED}ERROR: pacman not found. This script must be run on Arch/Manjaro.${RESET}" exit 1 fi # Check disk space (need room for transaction) DISK_PCT=$(df / | awk 'NR==2 {gsub(/%/,"",$5); print $5}') if [[ "$DISK_PCT" -gt 95 ]]; then log "${RED}WARNING: Disk usage at ${DISK_PCT}%. Consider freeing space first.${RESET}" fi DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}') log "Disk available: $DISK_AVAIL" # Check for existing packages (only list ones that are installed) log "Scanning for installed cleanup candidates..." # ============================================================ # 2. OLD MULTIMEDIA PACKAGES # ============================================================ section "[1/7] Old Multimedia Packages" CANDIDATES=() # Old ffmpeg version if pacman -Qe ffmpeg4.4 &>/dev/null || pacman -Q ffmpeg4.4 &>/dev/null; then CANDIDATES+=(ffmpeg4.4) fi # VLC plugins (keep base vlc) while IFS= read -r pkg; do [[ "$pkg" == "vlc" ]] && continue # skip base vlc CANDIDATES+=("$pkg") done < <(pacman -Qq 2>/dev/null | grep -E '^vlc-' || true) TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) log "Found ${#CANDIDATES[@]} multimedia candidates: ${CANDIDATES[*]:0:5}..." if [[ ${#CANDIDATES[@]} -gt 0 ]]; then log "Candidates: ${CANDIDATES[*]}" if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}" else sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) fi fi # ============================================================ # 3. INACTIVE NVIDIA/MHWHD DRIVERS # ============================================================ section "[2/7] Inactive NVIDIA Drivers (mhwd-nvidia-*)" CANDIDATES=() while IFS= read -r pkg; do [[ -n "$pkg" ]] && CANDIDATES+=("$pkg") done < <(pacman -Qq 2>/dev/null | grep -E '^mhwd-nvidia-' || true) TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) if [[ ${#CANDIDATES[@]} -gt 0 ]]; then log "Found ${#CANDIDATES[@]} mhwd-nvidia candidates: ${CANDIDATES[*]}" if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}" else sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) fi else log "No mhwd-nvidia packages found." fi # ============================================================ # 4. OLD KERNELS # ============================================================ section "[3/7] Old Kernel Packages" CANDIDATES=() # linux618 and linux70 (keep current running kernel and its modules) CURRENT_KERNEL=$(uname -r) KMVR="${CURRENT_KERNEL%-MANJARO}" # 6.18.33-1 KMJ="${KMVR%%.*}" # 6 KMREST="${KMVR#*.}" # 18.33-1 KMN="${KMREST%%.*}" # 18 CURRENT_KERNEL_PKG="linux${KMJ}${KMN}" # linux618 log "Current running kernel: $CURRENT_KERNEL (package: $CURRENT_KERNEL_PKG)" for kernel in linux618 linux70; do # Skip if this is the running kernel package if [[ "$kernel" == "linux${CURRENT_KERNEL_PKG}" ]]; then log "Skipping $kernel — this is the running kernel." continue fi if pacman -Q "$kernel" &>/dev/null; then CANDIDATES+=("$kernel") # Also remove its modules/headers/etc (but not nvidia/r8168 for running kernel — already skipped above) while IFS= read -r pkg; do [[ -n "$pkg" ]] && CANDIDATES+=("$pkg") done < <(pacman -Qq 2>/dev/null | grep -E "^${kernel}-" || true) fi done TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) if [[ ${#CANDIDATES[@]} -gt 0 ]]; then log "Found ${#CANDIDATES[@]} kernel candidates: ${CANDIDATES[*]}" if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}" else sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) fi else log "No old kernel packages found." fi # ============================================================ # 5. lib32 PACKAGES # ============================================================ section "[4/7] lib32 Packages" CANDIDATES=() while IFS= read -r pkg; do [[ -n "$pkg" ]] && CANDIDATES+=("$pkg") done < <(pacman -Qq 2>/dev/null | grep -E '^lib32-' || true) TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) log "Found ${#CANDIDATES[@]} lib32 packages" if [[ ${#CANDIDATES[@]} -gt 0 ]]; then # List first 10, then total log "First 10 candidates: ${CANDIDATES[*]:0:10}..." log "Total: ${#CANDIDATES[@]} lib32 packages" # Interactive: ask user if they want to remove lib32 packages if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}" else log "${BOLD}This will remove ALL ${#CANDIDATES[@]} lib32 packages.${RESET}" log "Only remove if you don't run 32-bit applications (games, wine, etc.)" read -rp "Remove all lib32 packages? [y/N] " confirm if [[ "$confirm" =~ ^[Yy]$ ]]; then sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) else log "SKIPPED lib32 removal." SKIPPED=$((SKIPPED + ${#CANDIDATES[@]})) fi fi fi # ============================================================ # 6. REDUNDANT FONTS (Nerd Font variants) # ============================================================ section "[5/7] Redundant Font Packages" CANDIDATES=() # Nerd Font packages (keep the one in active use) while IFS= read -r pkg; do [[ -n "$pkg" ]] && CANDIDATES+=("$pkg") done < <(pacman -Qq 2>/dev/null | grep -iE 'nerd-font' || true) # Also check for duplicate font collections while IFS= read -r pkg; do [[ -n "$pkg" ]] && CANDIDATES+=("$pkg") done < <(pacman -Qq 2>/dev/null | grep -iE 'ttf-(jetbrains|iosevka|fira|hack|noto)' || true) TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) if [[ ${#CANDIDATES[@]} -gt 0 ]]; then log "Found ${#CANDIDATES[@]} font candidates:" for pkg in "${CANDIDATES[@]}"; do log " - $pkg" done if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would show above packages for manual review" else log "${RED}CRITICAL: Review font packages carefully - removing wrong fonts can break UI.${RESET}" read -rp "Remove these ${#CANDIDATES[@]} font packages? [y/N] " confirm if [[ "$confirm" =~ ^[Yy]$ ]]; then sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) else log "SKIPPED font removal." SKIPPED=$((SKIPPED + ${#CANDIDATES[@]})) fi fi else log "No redundant font packages found." fi # ============================================================ # 7. BROWSERS (keep current active, remove extras) # ============================================================ section "[6/7] Browser Packages" CANDIDATES=() # Check which browsers are installed BROWSERS=() for browser in firefox chromium google-chrome firefox-developer-edition brave-bin microsoft-edge; do if pacman -Q "$browser" &>/dev/null; then BROWSERS+=("$browser") fi done log "Installed browsers: ${BROWSERS[*]}" log "${BOLD}Recommended: Keep firefox-developer-edition + one other${RESET}" if [[ ${#BROWSERS[@]} -gt 2 ]]; then # Suggest removal of non-essential browsers SUGGESTED_REMOVAL=() for b in "${BROWSERS[@]}"; do # Keep firefox-developer-edition and chromium/google-chrome if [[ "$b" != "firefox-developer-edition" && "$b" != "chromium" && "$b" != "google-chrome" ]]; then SUGGESTED_REMOVAL+=("$b") fi done if [[ ${#SUGGESTED_REMOVAL[@]} -gt 0 ]]; then CANDIDATES=("${SUGGESTED_REMOVAL[@]}") TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) log "Suggested removal: ${CANDIDATES[*]}" if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}" else sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) fi fi else log "Browser count is manageable (${#BROWSERS[@]}). No action needed." fi # ============================================================ # 8. UNUSED PYTHON PACKAGES # ============================================================ section "[7/7] Unused Python GUI Packages" CANDIDATES=() PYTHON_CANDIDATES=( wxpython wxpython-gtk3 pyqt5 pyqt5-sip pyqt6 pyqt6-sip fluidity tk tkinter python-tk ) for pkg in "${PYTHON_CANDIDATES[@]}"; do if pacman -Q "$pkg" &>/dev/null; then CANDIDATES+=("$pkg") fi done TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]})) if [[ ${#CANDIDATES[@]} -gt 0 ]]; then log "Found ${#CANDIDATES[@]} Python GUI candidates: ${CANDIDATES[*]}" if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}" else sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]})) fi else log "No unused Python GUI packages found." fi # ============================================================ # CLEANUP ORPHANS & CACHE # ============================================================ section "Post-Cleanup: Orphans & Cache" log "Finding orphaned dependencies..." ORPHANS=$(pacman -Qtdq 2>/dev/null || true) if [[ -n "$ORPHANS" ]]; then ORPHAN_COUNT=$(echo "$ORPHANS" | wc -l) log "Found $ORPHAN_COUNT orphaned packages" if [[ "$MODE" == "check" ]]; then log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm $(echo $ORPHANS)" else sudo pacman -Rns --noconfirm $ORPHANS 2>&1 | tee -a "$LOCAL_LOG" || true TOTAL_REMOVED=$((TOTAL_REMOVED + ORPHAN_COUNT)) fi else log "No orphaned packages found." fi log "Cleaning pacman cache (keep last 2 versions)..." if [[ "$MODE" == "check" ]]; then CACHE_SIZE=$(du -sh /var/cache/pacman/pkg/ 2>/dev/null | cut -f1) log "Cache size: $CACHE_SIZE" log "DRY-RUN: Would run: sudo paccache -rk2" else sudo paccache -rk2 2>&1 | tee -a "$LOCAL_LOG" || true fi # ============================================================ # SUMMARY # ============================================================ section "Cleanup Summary" FINAL_DISK=$(df -h / | awk 'NR==2 {print $4}') log "Disk space available after cleanup: $FINAL_DISK" log "Total candidates identified: $TOTAL_CANDIDATES" log "Packages removed: $TOTAL_REMOVED" log "Packages skipped: $SKIPPED" log "Log saved to: $LOCAL_LOG" log "" log "=============================================" log " Cleanup Complete" log "=============================================" # Cleanup temp files rm -f "$HOME/logs/.cleanup-candidates-"*.txt 2>/dev/null || true