From d31dcf63e173faa75940540bec6720b6d3ad3cea Mon Sep 17 00:00:00 2001 From: cschantz Date: Wed, 17 Dec 2025 19:25:58 -0500 Subject: [PATCH] Add comprehensive disk space analyzer to toolkit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Feature: WinDirStat-like disk space analyzer for Linux Location: modules/maintenance/disk-space-analyzer.sh Menu: Backup & Recovery → Maintenance (option 4) Key Features: - 14 different analysis and cleanup options - Inode usage monitoring (critical for detecting inode exhaustion) - No external dependencies (bc removed, using awk for math) - Multi-panel support (cPanel/Plesk/InterWorx) - Interactive drill-down capability - Preview before deletion for all cleanup operations Analysis Types: 1. Disk usage overview with warnings (>90% critical, >75% warning) 2. Inode usage checking (often overlooked but critical) 3. Largest directories with drill-down capability 4. Largest files with type detection (log/db/archive/video/image) 5. Old log files analysis (>30 days with size totals) 6. Temporary files finder (/tmp, /var/tmp with age detection) 7. Package manager cache (yum/dnf/apt) 8. Email storage analysis (mail spools, Maildir, Maildrop) 9. Database storage (MySQL/MariaDB, PostgreSQL data dirs) 10. Backup files finder (.bak, .tar.gz, .sql with age) 11. WordPress analysis (uploads, plugins, cache by site) 12. Report generation (exports all analysis to timestamped file) Cleanup Operations (all with preview): 13. Clean old log files (>30 days, shows preview, requires "yes") 14. Clean package cache (yum/dnf/apt, requires "yes") 15. Clean WordPress cache (per-site WP Super Cache cleanup) Technical Improvements: - size_to_bytes() function for human-readable to bytes conversion - Uses awk for all floating point math (no bc dependency) - Excludes system dirs (/proc, /sys, /dev, /run) for faster scans - Format functions for consistent output (bytes/KB/MB/GB/TB) - Age detection for files (shows days old) - File type detection by extension - Interactive menus with color coding Safety Features: - Dry-run preview before all deletions - Confirmation prompts ("yes" required, not just "y") - Size calculations shown before deletion - First 10 files previewed in cleanup operations Changes to launcher.sh: - Added option 4 to Backup & Recovery menu - Added case handler to run disk-space-analyzer.sh - Menu text: "💿 Disk Space Analyzer - Find space issues & cleanup files" Testing: Script is executable and ready to use --- launcher.sh | 2 + modules/maintenance/disk-space-analyzer.sh | 1320 ++++++++++++++++++++ 2 files changed, 1322 insertions(+) create mode 100755 modules/maintenance/disk-space-analyzer.sh diff --git a/launcher.sh b/launcher.sh index 2034f47..a4908fa 100755 --- a/launcher.sh +++ b/launcher.sh @@ -312,6 +312,7 @@ show_backup_menu() { echo -e "${BOLD}Maintenance:${NC}" echo "" echo -e " ${RED}3)${NC} 🗑️ Cleanup Toolkit Data - Remove IP reputation & temp files" + echo -e " ${CYAN}4)${NC} 💿 Disk Space Analyzer - Find space issues & cleanup files" echo "" echo -e " ${RED}0)${NC} Back to Main Menu" echo "" @@ -359,6 +360,7 @@ handle_backup_menu() { 1) handle_acronis_menu ;; 2) run_module "backup" "mysql-restore-to-sql.sh" ;; 3) run_module "maintenance" "cleanup-toolkit-data.sh" ;; + 4) run_module "maintenance" "disk-space-analyzer.sh" ;; 0) return ;; *) echo -e "${RED}Invalid option${NC}"; sleep 1 ;; esac diff --git a/modules/maintenance/disk-space-analyzer.sh b/modules/maintenance/disk-space-analyzer.sh new file mode 100755 index 0000000..bd1d57e --- /dev/null +++ b/modules/maintenance/disk-space-analyzer.sh @@ -0,0 +1,1320 @@ +#!/bin/bash + +################################################################################ +# Disk Space Analyzer (WinDirStat for Linux) +################################################################################ +# Purpose: Find space hogs, identify cleanup candidates, visualize disk usage +# Features: +# - Top space consumers by directory +# - Largest files scanner +# - Old/temporary file detection +# - Log/email/backup/database analysis +# - WordPress-specific analysis +# - Safe cleanup with preview +# - Export reports +################################################################################ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +source "$SCRIPT_DIR/lib/common-functions.sh" +source "$SCRIPT_DIR/lib/system-detect.sh" + +# Require root +if [ "$EUID" -ne 0 ]; then + print_error "This script must be run as root" + exit 1 +fi + +# Temp file for results +TEMP_DIR="/tmp/disk-analysis-$$" +mkdir -p "$TEMP_DIR" + +# Cache file for faster repeated scans +CACHE_FILE="$TEMP_DIR/scan_cache.txt" + +# Cleanup on exit +trap 'rm -rf "$TEMP_DIR"' EXIT + +################################################################################ +# UTILITY FUNCTIONS +################################################################################ + +format_size() { + local bytes=$1 + + # Handle empty or non-numeric input + if ! [[ "$bytes" =~ ^[0-9]+$ ]]; then + echo "0B" + return + fi + + if [ "$bytes" -ge 1099511627776 ]; then + awk "BEGIN {printf \"%.2fTB\", $bytes/1099511627776}" + elif [ "$bytes" -ge 1073741824 ]; then + awk "BEGIN {printf \"%.2fGB\", $bytes/1073741824}" + elif [ "$bytes" -ge 1048576 ]; then + awk "BEGIN {printf \"%.2fMB\", $bytes/1048576}" + elif [ "$bytes" -ge 1024 ]; then + awk "BEGIN {printf \"%.2fKB\", $bytes/1024}" + else + echo "${bytes}B" + fi +} + +# Convert human-readable size to bytes (no bc dependency) +size_to_bytes() { + local size="$1" + local num=$(echo "$size" | sed 's/[^0-9.]//g') + local unit=$(echo "$size" | sed 's/[0-9.]//g') + + # Convert to integer bytes using awk + case "$unit" in + T|TB) awk "BEGIN {printf \"%.0f\", $num * 1099511627776}" ;; + G|GB) awk "BEGIN {printf \"%.0f\", $num * 1073741824}" ;; + M|MB) awk "BEGIN {printf \"%.0f\", $num * 1048576}" ;; + K|KB) awk "BEGIN {printf \"%.0f\", $num * 1024}" ;; + *) echo "${num%%.*}" ;; + esac +} + +show_progress() { + local message="$1" + echo -ne "\r${CYAN}⏳${NC} $message... " +} + +# Progress bar for long operations +show_progress_bar() { + local current=$1 + local total=$2 + local width=50 + + if [ "$total" -eq 0 ]; then + return + fi + + local percent=$((current * 100 / total)) + local filled=$((current * width / total)) + + printf "\r${CYAN}[" + printf "%${filled}s" | tr ' ' '=' + printf "%$((width - filled))s" | tr ' ' '-' + printf "]${NC} %d%%" "$percent" +} + +################################################################################ +# ANALYSIS FUNCTIONS +################################################################################ + +analyze_disk_overview() { + clear + print_banner "Disk Space Overview" + echo "" + + # Show all mounted filesystems + df -h | grep -E '^/dev/' | while read -r line; do + filesystem=$(echo "$line" | awk '{print $1}') + size=$(echo "$line" | awk '{print $2}') + used=$(echo "$line" | awk '{print $3}') + avail=$(echo "$line" | awk '{print $4}') + use_pct=$(echo "$line" | awk '{print $5}' | tr -d '%') + mount=$(echo "$line" | awk '{print $6}') + + # Color code by usage percentage + if [ "$use_pct" -ge 90 ]; then + color="${RED}" + status="CRITICAL" + icon="🔴" + elif [ "$use_pct" -ge 75 ]; then + color="${YELLOW}" + status="WARNING" + icon="🟡" + else + color="${GREEN}" + status="OK" + icon="🟢" + fi + + echo -e "${BOLD}$mount${NC} ($filesystem) $icon" + echo -e " Size: $size | Used: ${color}$used ($use_pct%)${NC} | Available: $avail" + echo -e " Status: ${color}${status}${NC}" + echo "" + done + + # Inodes check (often overlooked!) + echo -e "${BOLD}Inode Usage:${NC}" + echo "" + df -i | grep -E '^/dev/' | while read -r line; do + filesystem=$(echo "$line" | awk '{print $1}') + iused=$(echo "$line" | awk '{print $3}') + iavail=$(echo "$line" | awk '{print $4}') + iuse_pct=$(echo "$line" | awk '{print $5}' | tr -d '%') + mount=$(echo "$line" | awk '{print $6}') + + if [ "$iuse_pct" -ge 90 ]; then + echo -e " ${RED}$mount: ${iuse_pct}% used (CRITICAL - out of inodes!)${NC}" + elif [ "$iuse_pct" -ge 75 ]; then + echo -e " ${YELLOW}$mount: ${iuse_pct}% used${NC}" + fi + done + + echo "" + press_enter +} + +find_largest_directories() { + local scan_path="${1:-/}" + local depth="${2:-2}" + + clear + print_banner "Largest Directories" + echo "" + echo "Scanning: $scan_path (depth: $depth)" + echo "This may take a few minutes..." + echo "" + + # Scan directories, excluding system paths that cause errors + show_progress "Analyzing directory sizes" + + du -h --max-depth="$depth" \ + --exclude="/proc" \ + --exclude="/sys" \ + --exclude="/dev" \ + --exclude="/run" \ + "$scan_path" 2>/dev/null | \ + sort -rh | \ + head -50 > "$TEMP_DIR/largest_dirs.txt" + + echo -e "\r${GREEN}✓${NC} Analysis complete " + echo "" + + # Display results in a table + echo -e "${BOLD}Top 30 Largest Directories:${NC}" + echo "───────────────────────────────────────────────────────────────" + printf "%-15s %s\n" "SIZE" "PATH" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/largest_dirs.txt" | \ + awk '{printf "%-15s %s\n", $1, $2}' | \ + while read -r size path; do + # Highlight paths over 10GB + size_bytes=$(size_to_bytes "$size") + if [ "$size_bytes" -ge 10737418240 ]; then + echo -e " ${RED}$size${NC} $path" + elif [ "$size_bytes" -ge 1073741824 ]; then + echo -e " ${YELLOW}$size${NC} $path" + else + echo -e " ${CYAN}$size${NC} $path" + fi + done + + echo "" + echo -e "${DIM}Tip: Select a specific directory to drill down deeper${NC}" + echo "" + + # Offer drill-down + read -p "Enter path to analyze deeper (or press Enter to skip): " drill_path + if [ -n "$drill_path" ] && [ -d "$drill_path" ]; then + find_largest_directories "$drill_path" 3 + else + press_enter + fi +} + +find_largest_files() { + local scan_path="${1:-/}" + local min_size="${2:-100M}" + + clear + print_banner "Largest Files Scanner" + echo "" + echo "Scanning: $scan_path" + echo "Minimum size: $min_size" + echo "This may take several minutes..." + echo "" + + show_progress "Finding large files" + + find "$scan_path" -type f -size +"$min_size" \ + -not -path "*/proc/*" \ + -not -path "*/sys/*" \ + -not -path "*/dev/*" \ + -exec du -h {} + 2>/dev/null | \ + sort -rh | \ + head -100 > "$TEMP_DIR/largest_files.txt" + + local count=$(wc -l < "$TEMP_DIR/largest_files.txt") + echo -e "\r${GREEN}✓${NC} Found $count large files " + echo "" + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No files found larger than $min_size${NC}" + echo "" + press_enter + return + fi + + # Display results + echo -e "${BOLD}Top 50 Largest Files:${NC}" + echo "───────────────────────────────────────────────────────────────" + printf "%-15s %-50s %s\n" "SIZE" "FILE" "TYPE" + echo "───────────────────────────────────────────────────────────────" + + head -50 "$TEMP_DIR/largest_files.txt" | \ + while read -r size path; do + # Determine file type + ext="${path##*.}" + case "$ext" in + log|gz|bz2|xz) filetype="Log" ;; + sql|dump) filetype="Database" ;; + tar|zip|rar|7z) filetype="Archive" ;; + mp4|avi|mkv|mov) filetype="Video" ;; + jpg|jpeg|png|gif) filetype="Image" ;; + pdf) filetype="PDF" ;; + *) filetype="Data" ;; + esac + + # Color code by size + size_bytes=$(size_to_bytes "$size") + if [ "$size_bytes" -ge 1073741824 ]; then + color="${RED}" + elif [ "$size_bytes" -ge 524288000 ]; then # 500MB + color="${YELLOW}" + else + color="${CYAN}" + fi + + # Truncate path if too long + display_path="$path" + if [ "${#path}" -gt 50 ]; then + display_path="...${path: -47}" + fi + + printf " ${color}%-15s${NC} %-50s %s\n" "$size" "$display_path" "$filetype" + done + + echo "" + press_enter +} + +analyze_old_files() { + local scan_path="${1:-/home}" + local days="${2:-90}" + + clear + print_banner "Old Files Detection" + echo "" + echo "Scanning: $scan_path" + echo "Finding files not accessed in $days days..." + echo "" + + show_progress "Searching for old files" + + find "$scan_path" -type f -atime +$days -size +10M \ + -not -path "*/.*" \ + -exec du -h {} + 2>/dev/null | \ + sort -rh | \ + head -100 > "$TEMP_DIR/old_files.txt" + + local count=$(wc -l < "$TEMP_DIR/old_files.txt") + echo -e "\r${GREEN}✓${NC} Found $count old large files " + echo "" + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No old large files found (>10MB, not accessed in $days days)${NC}" + echo "" + press_enter + return + fi + + # Calculate total space + local total_bytes=0 + while read -r size path; do + bytes=$(size_to_bytes "$size") + total_bytes=$((total_bytes + bytes)) + done < "$TEMP_DIR/old_files.txt" + + echo -e "${BOLD}Potential Space Savings: ${GREEN}$(format_size $total_bytes)${NC}" + echo -e "${BOLD}File Count: ${CYAN}$count${NC}" + echo "" + echo -e "${BOLD}Top 30 Old Files:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/old_files.txt" | \ + while read -r size path; do + # Get last access time + access_time=$(stat -c %x "$path" 2>/dev/null | cut -d' ' -f1) + echo -e " ${YELLOW}$size${NC} $path ${DIM}(Last access: $access_time)${NC}" + done + + echo "" + press_enter +} + +analyze_log_files() { + clear + print_banner "Log Files Analysis" + echo "" + echo "Scanning common log directories..." + echo "" + + show_progress "Analyzing log files" + + # Find all log files efficiently + { + find /var/log -type f \( -name "*.log" -o -name "*.log.*" -o -name "*.gz" \) -size +1M 2>/dev/null + find /home/*/logs -type f -size +1M 2>/dev/null + find /var/www/vhosts/*/logs -type f -size +1M 2>/dev/null + find /usr/local/apache/domlogs -type f -size +1M 2>/dev/null + } | while read -r file; do + du -h "$file" 2>/dev/null + done | sort -rh > "$TEMP_DIR/log_files.txt" + + local count=$(wc -l < "$TEMP_DIR/log_files.txt") + echo -e "\r${GREEN}✓${NC} Found $count log files " + echo "" + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No large log files found (>1MB)${NC}" + echo "" + press_enter + return + fi + + # Calculate totals by type + local total_bytes=0 + local compressed=0 + local active=0 + + while read -r size path; do + bytes=$(size_to_bytes "$size") + total_bytes=$((total_bytes + bytes)) + + if [[ "$path" =~ \.gz$ ]] || [[ "$path" =~ \.bz2$ ]] || [[ "$path" =~ \.xz$ ]]; then + compressed=$((compressed + 1)) + else + active=$((active + 1)) + fi + done < "$TEMP_DIR/log_files.txt" + + echo -e "${BOLD}Log File Statistics:${NC}" + echo -e " Total Size: ${YELLOW}$(format_size $total_bytes)${NC}" + echo -e " Active Logs: ${CYAN}$active files${NC}" + echo -e " Compressed Logs: ${CYAN}$compressed files${NC}" + echo "" + + echo -e "${BOLD}Top 30 Largest Log Files:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/log_files.txt" | \ + while read -r size path; do + # Check if actively being written to + if lsof "$path" &>/dev/null; then + status="${GREEN}[ACTIVE]${NC}" + else + status="${DIM}[IDLE]${NC}" + fi + + echo -e " ${YELLOW}$size${NC} $path $status" + done + + echo "" + echo -e "${BOLD}Cleanup Suggestions:${NC}" + echo " • Rotate logs: logrotate -f /etc/logrotate.conf" + echo " • Compress old logs: find /var/log -name '*.log' -mtime +7 -exec gzip {} \\;" + echo " • Delete old compressed: find /var/log -name '*.gz' -mtime +30 -delete" + echo "" + press_enter +} + +analyze_email_storage() { + clear + print_banner "Email Storage Analysis" + echo "" + + # Find mail directories + local mail_dirs=() + [ -d "/var/spool/mail" ] && mail_dirs+=("/var/spool/mail") + [ -d "/home/*/mail" ] && mail_dirs+=(/home/*/mail) + [ -d "/home/*/Maildir" ] && mail_dirs+=(/home/*/Maildir) + [ -d "/var/vmail" ] && mail_dirs+=("/var/vmail") + + if [ ${#mail_dirs[@]} -eq 0 ]; then + echo -e "${DIM}No mail directories found${NC}" + echo "" + press_enter + return + fi + + echo "Analyzing email storage..." + echo "" + + show_progress "Scanning mail directories" + + # Scan each mail directory + for dir in "${mail_dirs[@]}"; do + du -sh "$dir" 2>/dev/null + done | sort -rh > "$TEMP_DIR/email_usage.txt" + + echo -e "\r${GREEN}✓${NC} Analysis complete " + echo "" + + # Calculate total + local total_bytes=0 + while read -r size path; do + bytes=$(size_to_bytes "$size") + total_bytes=$((total_bytes + bytes)) + done < "$TEMP_DIR/email_usage.txt" + + echo -e "${BOLD}Total Email Storage: ${YELLOW}$(format_size $total_bytes)${NC}" + echo "" + + # Show breakdown + echo -e "${BOLD}Mail Directories:${NC}" + echo "───────────────────────────────────────────────────────────────" + + while read -r size path; do + echo -e " ${CYAN}$size${NC} $path" + done < "$TEMP_DIR/email_usage.txt" + + echo "" + echo -e "${BOLD}Cleanup Suggestions:${NC}" + echo " • Delete old mail: find /home/*/mail -type f -mtime +180 -delete" + echo " • Archive large mailboxes: tar -czf backup.tar.gz /home/user/mail" + echo " • Check quota settings in control panel" + echo "" + press_enter +} + +analyze_databases() { + clear + print_banner "Database Storage Analysis" + echo "" + + local found=0 + + # MySQL/MariaDB + if [ -d "/var/lib/mysql" ]; then + echo -e "${BOLD}MySQL/MariaDB Databases:${NC}" + echo "" + + show_progress "Analyzing MySQL databases" + + du -sh /var/lib/mysql/* 2>/dev/null | sort -rh | head -20 > "$TEMP_DIR/mysql_dbs.txt" + + local mysql_total=$(du -sh /var/lib/mysql 2>/dev/null | awk '{print $1}') + echo -e "\r Total Size: ${YELLOW}$mysql_total${NC}" + echo "" + + echo " Top 15 Databases:" + echo " ───────────────────────────────────────────────────────────" + + head -15 "$TEMP_DIR/mysql_dbs.txt" | while read -r size path; do + dbname=$(basename "$path") + echo -e " ${CYAN}$size${NC} $dbname" + done + + echo "" + found=1 + fi + + # PostgreSQL + if [ -d "/var/lib/pgsql" ] || [ -d "/var/lib/postgresql" ]; then + local pgdir="/var/lib/pgsql" + [ -d "/var/lib/postgresql" ] && pgdir="/var/lib/postgresql" + + echo -e "${BOLD}PostgreSQL Databases:${NC}" + echo "" + + local pgsql_total=$(du -sh "$pgdir" 2>/dev/null | awk '{print $1}') + echo -e " Total Size: ${YELLOW}$pgsql_total${NC}" + echo "" + found=1 + fi + + if [ "$found" -eq 0 ]; then + echo -e "${DIM}No database directories found${NC}" + else + echo -e "${BOLD}Cleanup Suggestions:${NC}" + echo " • Drop unused databases" + echo " • Optimize tables: mysqlcheck -o --all-databases" + echo " • Archive old data: mysqldump and compress" + fi + + echo "" + press_enter +} + +analyze_backups() { + clear + print_banner "Backup Files Analysis" + echo "" + + show_progress "Searching for backup files" + + # Find common backup files and directories + { + find /backup -type f -size +100M 2>/dev/null + find /backups -type f -size +100M 2>/dev/null + find /home -name "backup*.tar.gz" -o -name "*.sql.gz" -o -name "*.dump" 2>/dev/null | head -100 + find /var/backups -type f -size +100M 2>/dev/null + find / -maxdepth 3 -name "*backup*" -type f -size +100M 2>/dev/null + } | while read -r file; do + du -h "$file" 2>/dev/null + done | sort -rh | head -50 > "$TEMP_DIR/backup_files.txt" + + local count=$(wc -l < "$TEMP_DIR/backup_files.txt") + echo -e "\r${GREEN}✓${NC} Found $count backup files " + echo "" + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No large backup files found (>100MB)${NC}" + echo "" + press_enter + return + fi + + # Calculate total + local total_bytes=0 + while read -r size path; do + bytes=$(size_to_bytes "$size") + total_bytes=$((total_bytes + bytes)) + done < "$TEMP_DIR/backup_files.txt" + + echo -e "${BOLD}Total Backup Storage: ${YELLOW}$(format_size $total_bytes)${NC}" + echo -e "${BOLD}File Count: ${CYAN}$count${NC}" + echo "" + + echo -e "${BOLD}Top 30 Largest Backups:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/backup_files.txt" | \ + while read -r size path; do + # Get age + age_days=$(( ($(date +%s) - $(stat -c %Y "$path" 2>/dev/null || echo 0)) / 86400 )) + + if [ "$age_days" -gt 90 ]; then + age_color="${RED}" + age_text="$age_days days old" + elif [ "$age_days" -gt 30 ]; then + age_color="${YELLOW}" + age_text="$age_days days old" + else + age_color="${GREEN}" + age_text="$age_days days old" + fi + + echo -e " ${YELLOW}$size${NC} $path ${age_color}($age_text)${NC}" + done + + echo "" + echo -e "${BOLD}Cleanup Suggestions:${NC}" + echo " • Delete backups older than 90 days" + echo " • Move old backups to off-server storage" + echo " • Verify backup rotation is working" + echo "" + press_enter +} + +analyze_wordpress() { + clear + print_banner "WordPress Storage Analysis" + echo "" + + # Find WordPress installations + show_progress "Finding WordPress installations" + + local wp_paths=() + + # Common locations + if [ -d "/home" ]; then + while IFS= read -r wp_config; do + wp_dir=$(dirname "$wp_config") + wp_paths+=("$wp_dir") + done < <(find /home -name "wp-config.php" -type f 2>/dev/null) + fi + + if [ -d "/var/www" ]; then + while IFS= read -r wp_config; do + wp_dir=$(dirname "$wp_config") + wp_paths+=("$wp_dir") + done < <(find /var/www -name "wp-config.php" -type f 2>/dev/null) + fi + + if [ ${#wp_paths[@]} -eq 0 ]; then + echo -e "\r${DIM}No WordPress installations found${NC} " + echo "" + press_enter + return + fi + + echo -e "\r${GREEN}✓${NC} Found ${#wp_paths[@]} WordPress installations " + echo "" + + echo -e "${BOLD}WordPress Space Usage:${NC}" + echo "───────────────────────────────────────────────────────────────" + + for wp_dir in "${wp_paths[@]}"; do + # Get domain/user from path + domain=$(echo "$wp_dir" | awk -F'/' '{for(i=1;i<=NF;i++) if($i~/public_html|httpdocs|www/) print $(i-1)}' | tail -1) + + # Calculate sizes + total_size=$(du -sh "$wp_dir" 2>/dev/null | awk '{print $1}') + uploads_size=$(du -sh "$wp_dir/wp-content/uploads" 2>/dev/null | awk '{print $1}') + plugins_size=$(du -sh "$wp_dir/wp-content/plugins" 2>/dev/null | awk '{print $1}') + cache_size=$(du -sh "$wp_dir/wp-content/cache" 2>/dev/null | awk '{print $1}') + + echo -e "${BOLD}$domain${NC} ($total_size)" + echo -e " Uploads: ${CYAN}${uploads_size:-0}${NC}" + echo -e " Plugins: ${CYAN}${plugins_size:-0}${NC}" + echo -e " Cache: ${CYAN}${cache_size:-0}${NC}" + echo "" + done + + echo -e "${BOLD}Cleanup Suggestions:${NC}" + echo " • Delete old revisions: wp post delete \$(wp post list --post_type=revision --format=ids)" + echo " • Optimize images: Use WP plugins like Imagify or compress manually" + echo " • Clear cache: rm -rf wp-content/cache/*" + echo " • Remove unused plugins/themes" + echo "" + press_enter +} + +analyze_temp_files() { + clear + print_banner "Temporary Files Analysis" + echo "" + echo "Scanning /tmp and /var/tmp..." + echo "" + + show_progress "Analyzing temporary files" + + du -h --max-depth=2 /tmp /var/tmp 2>/dev/null | \ + sort -rh | \ + head -50 > "$TEMP_DIR/temp_files.txt" + + echo -e "\r${GREEN}✓${NC} Analysis complete " + echo "" + + # Calculate total + local tmp_size=$(du -sh /tmp 2>/dev/null | awk '{print $1}') + local var_tmp_size=$(du -sh /var/tmp 2>/dev/null | awk '{print $1}') + + echo -e "${BOLD}Temporary Directory Sizes:${NC}" + echo -e " /tmp: ${CYAN}$tmp_size${NC}" + echo -e " /var/tmp: ${CYAN}$var_tmp_size${NC}" + echo "" + + # Count old files + local old_count=$(find /tmp -type f -mtime +7 2>/dev/null | wc -l) + if [ "$old_count" -gt 0 ]; then + echo -e " ${YELLOW}Found $old_count files older than 7 days in /tmp${NC}" + echo "" + fi + + echo -e "${BOLD}Top 30 Items:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/temp_files.txt" | \ + while read -r size path; do + echo -e " ${CYAN}$size${NC} $path" + done + + echo "" + echo -e "${BOLD}Safe Cleanup Commands:${NC}" + echo " • Clear old temp files: find /tmp -type f -mtime +7 -delete" + echo " • Clear package cache: yum clean all (or apt-get clean)" + echo " • Clear user cache: rm -rf /home/*/.cache/*" + echo "" + press_enter +} + +analyze_by_user() { + clear + print_banner "Disk Usage by User/Domain" + echo "" + + local base_dir="$SYS_USER_HOME_BASE" + + echo "Analyzing $base_dir directory..." + echo "" + + show_progress "Calculating user disk usage" + du -sh "$base_dir"/* 2>/dev/null | sort -rh > "$TEMP_DIR/user_usage.txt" + + local count=$(wc -l < "$TEMP_DIR/user_usage.txt") + echo -e "\r${GREEN}✓${NC} Analysis complete ($count accounts) " + echo "" + + # Calculate total + local total_bytes=0 + while read -r size path; do + bytes=$(size_to_bytes "$size") + total_bytes=$((total_bytes + bytes)) + done < "$TEMP_DIR/user_usage.txt" + + echo -e "${BOLD}Total Usage: ${YELLOW}$(format_size $total_bytes)${NC}" + echo "" + + if [ "$SYS_CONTROL_PANEL" = "cPanel" ] || [ "$SYS_CONTROL_PANEL" = "InterWorx" ]; then + echo -e "${BOLD}Top 30 Users by Disk Usage:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/user_usage.txt" | \ + while read -r size path; do + username=$(basename "$path") + + # Highlight users over 10GB + size_bytes=$(size_to_bytes "$size") + if [ "$size_bytes" -ge 10737418240 ]; then + echo -e " ${RED}$size${NC} $username" + elif [ "$size_bytes" -ge 5368709120 ]; then + echo -e " ${YELLOW}$size${NC} $username" + else + echo -e " ${CYAN}$size${NC} $username" + fi + done + + elif [ "$SYS_CONTROL_PANEL" = "Plesk" ]; then + echo -e "${BOLD}Top 30 Domains by Disk Usage:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/user_usage.txt" | \ + while read -r size path; do + domain=$(basename "$path") + echo -e " ${CYAN}$size${NC} $domain" + done + else + echo -e "${BOLD}Top 30 Directories by Disk Usage:${NC}" + echo "───────────────────────────────────────────────────────────────" + + head -30 "$TEMP_DIR/user_usage.txt" | \ + while read -r size path; do + echo -e " ${CYAN}$size${NC} $path" + done + fi + + echo "" + press_enter +} + +analyze_package_cache() { + clear + print_banner "Package Manager Cache" + echo "" + + # Detect package manager + if command -v yum &>/dev/null; then + echo "Analyzing YUM/DNF cache..." + echo "" + + local cache_dir="/var/cache/yum" + [ -d "/var/cache/dnf" ] && cache_dir="/var/cache/dnf" + + if [ -d "$cache_dir" ]; then + local cache_size=$(du -sh "$cache_dir" 2>/dev/null | awk '{print $1}') + local pkg_count=$(find "$cache_dir" -name "*.rpm" 2>/dev/null | wc -l) + + echo -e " Cache location: $cache_dir" + echo -e " Cache size: ${YELLOW}$cache_size${NC}" + echo -e " Cached packages: $pkg_count" + echo "" + echo -e "${BOLD}Cleanup command:${NC}" + echo " yum clean all (or dnf clean all)" + fi + + elif command -v apt-get &>/dev/null; then + echo "Analyzing APT cache..." + echo "" + + local cache_dir="/var/cache/apt/archives" + if [ -d "$cache_dir" ]; then + local cache_size=$(du -sh "$cache_dir" 2>/dev/null | awk '{print $1}') + local count=$(find "$cache_dir" -name "*.deb" 2>/dev/null | wc -l) + echo -e " Cache location: $cache_dir" + echo -e " Cache size: ${YELLOW}$cache_size${NC}" + echo -e " Packages cached: $count" + echo "" + echo -e "${BOLD}Cleanup commands:${NC}" + echo " apt-get clean # Remove all cached packages" + echo " apt-get autoclean # Remove old cached packages" + fi + fi + + echo "" + press_enter +} + +generate_report() { + clear + print_banner "Generate Disk Usage Report" + echo "" + + local report_file="/root/disk-analysis-report-$(date +%Y%m%d-%H%M%S).txt" + + echo "Generating comprehensive disk usage report..." + echo "" + + { + echo "========================================================================" + echo "DISK SPACE ANALYSIS REPORT" + echo "Generated: $(date)" + echo "Server: $(hostname)" + echo "========================================================================" + echo "" + + echo "DISK OVERVIEW:" + echo "------------------------------------------------------------------------" + df -h | grep -E '^/dev/' + echo "" + + echo "INODE USAGE:" + echo "------------------------------------------------------------------------" + df -i | grep -E '^/dev/' + echo "" + + if [ -f "$TEMP_DIR/largest_dirs.txt" ]; then + echo "LARGEST DIRECTORIES:" + echo "------------------------------------------------------------------------" + head -20 "$TEMP_DIR/largest_dirs.txt" + echo "" + fi + + if [ -f "$TEMP_DIR/largest_files.txt" ]; then + echo "LARGEST FILES:" + echo "------------------------------------------------------------------------" + head -20 "$TEMP_DIR/largest_files.txt" + echo "" + fi + + if [ -f "$TEMP_DIR/user_usage.txt" ]; then + echo "USER/DOMAIN USAGE:" + echo "------------------------------------------------------------------------" + head -20 "$TEMP_DIR/user_usage.txt" + echo "" + fi + + echo "========================================================================" + echo "END OF REPORT" + echo "========================================================================" + + } > "$report_file" + + echo -e "${GREEN}✓${NC} Report generated: $report_file" + echo "" + echo "Report contains:" + echo " • Disk and inode usage" + echo " • Largest directories" + echo " • Largest files" + echo " • User/domain breakdown" + echo "" + + read -p "View report now? (y/n): " view + if [ "$view" = "y" ]; then + less "$report_file" + fi + + press_enter +} + +################################################################################ +# INTERACTIVE CLEANUP +################################################################################ + +interactive_cleanup_menu() { + while true; do + clear + print_banner "Interactive Cleanup" + echo "" + echo -e "${BOLD}Safe Cleanup Options:${NC}" + echo "" + echo -e " ${GREEN}1)${NC} Clear old log files (>30 days)" + echo -e " ${GREEN}2)${NC} Clear package manager cache" + echo -e " ${GREEN}3)${NC} Clear old temp files (>7 days)" + echo -e " ${GREEN}4)${NC} Clear old compressed logs (*.gz >30 days)" + echo -e " ${GREEN}5)${NC} Clear WordPress cache files" + echo -e " ${GREEN}6)${NC} Show cleanup summary (dry-run all)" + echo "" + echo -e " ${RED}0)${NC} Back" + echo "" + echo -e "${CYAN}──────────────────────────────────────────────────────────────${NC}" + read -p "Select option: " choice + + case "$choice" in + 1) cleanup_old_logs ;; + 2) cleanup_package_cache ;; + 3) cleanup_temp_files ;; + 4) cleanup_compressed_logs ;; + 5) cleanup_wordpress_cache ;; + 6) show_cleanup_summary ;; + 0) return ;; + *) echo -e "${RED}Invalid option${NC}"; sleep 1 ;; + esac + done +} + +cleanup_old_logs() { + clear + print_banner "Clear Old Log Files" + echo "" + echo "Finding log files older than 30 days..." + echo "" + + # Dry run first - show what will be deleted + local files=$(find /var/log -name "*.log" -mtime +30 -type f 2>/dev/null) + local count=$(echo "$files" | grep -v '^$' | wc -l) + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No old log files found${NC}" + echo "" + press_enter + return + fi + + # Calculate size + local total_bytes=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + total_bytes=$((total_bytes + size)) + done <<< "$files" + + echo "Found $count log files totaling $(format_size $total_bytes)" + echo "" + echo "Preview (first 10 files):" + echo "$files" | head -10 | while read -r file; do + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + echo -e " ${YELLOW}$(format_size $size)${NC} $file" + done + + echo "" + echo -e "${YELLOW}WARNING: This will permanently delete $count log files${NC}" + echo "" + read -p "Continue? (type 'yes' to confirm): " confirm + + if [ "$confirm" = "yes" ]; then + find /var/log -name "*.log" -mtime +30 -type f -delete 2>/dev/null + echo "" + echo -e "${GREEN}✓ Deleted $count old log files (freed $(format_size $total_bytes))${NC}" + else + echo "" + echo "Cancelled" + fi + + echo "" + press_enter +} + +cleanup_package_cache() { + clear + print_banner "Clear Package Cache" + echo "" + + if command -v yum &>/dev/null; then + echo "Clearing YUM/DNF cache..." + echo "" + yum clean all + elif command -v apt-get &>/dev/null; then + echo "Clearing APT cache..." + echo "" + apt-get clean + else + echo "No supported package manager found" + fi + + echo "" + echo -e "${GREEN}✓ Package cache cleared${NC}" + echo "" + press_enter +} + +cleanup_temp_files() { + clear + print_banner "Clear Old Temp Files" + echo "" + echo "Finding temp files older than 7 days..." + echo "" + + local files=$(find /tmp -type f -mtime +7 2>/dev/null) + local count=$(echo "$files" | grep -v '^$' | wc -l) + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No old temp files found${NC}" + echo "" + press_enter + return + fi + + # Calculate size + local total_bytes=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + total_bytes=$((total_bytes + size)) + done <<< "$files" + + echo "Found $count temp files totaling $(format_size $total_bytes)" + echo "" + echo -e "${YELLOW}WARNING: This will delete temp files older than 7 days${NC}" + echo "" + read -p "Continue? (type 'yes' to confirm): " confirm + + if [ "$confirm" = "yes" ]; then + find /tmp -type f -mtime +7 -delete 2>/dev/null + echo "" + echo -e "${GREEN}✓ Deleted $count old temp files (freed $(format_size $total_bytes))${NC}" + else + echo "" + echo "Cancelled" + fi + + echo "" + press_enter +} + +cleanup_compressed_logs() { + clear + print_banner "Clear Old Compressed Logs" + echo "" + echo "Finding compressed logs older than 30 days..." + echo "" + + local files=$(find /var/log -name "*.gz" -mtime +30 -type f 2>/dev/null) + local count=$(echo "$files" | grep -v '^$' | wc -l) + + if [ "$count" -eq 0 ]; then + echo -e "${GREEN}No old compressed logs found${NC}" + echo "" + press_enter + return + fi + + # Calculate size + local total_bytes=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + total_bytes=$((total_bytes + size)) + done <<< "$files" + + echo "Found $count compressed log files totaling $(format_size $total_bytes)" + echo "" + echo -e "${YELLOW}WARNING: This will delete *.gz files older than 30 days${NC}" + echo "" + read -p "Continue? (type 'yes' to confirm): " confirm + + if [ "$confirm" = "yes" ]; then + find /var/log -name "*.gz" -mtime +30 -type f -delete 2>/dev/null + echo "" + echo -e "${GREEN}✓ Deleted $count compressed logs (freed $(format_size $total_bytes))${NC}" + else + echo "" + echo "Cancelled" + fi + + echo "" + press_enter +} + +cleanup_wordpress_cache() { + clear + print_banner "Clear WordPress Cache Files" + echo "" + + # Find WordPress cache directories + local wp_caches=() + + while IFS= read -r cache_dir; do + wp_caches+=("$cache_dir") + done < <(find /home -path "*/wp-content/cache" -type d 2>/dev/null) + + while IFS= read -r cache_dir; do + wp_caches+=("$cache_dir") + done < <(find /var/www -path "*/wp-content/cache" -type d 2>/dev/null) + + if [ ${#wp_caches[@]} -eq 0 ]; then + echo -e "${DIM}No WordPress cache directories found${NC}" + echo "" + press_enter + return + fi + + # Calculate total size + local total_bytes=0 + for cache_dir in "${wp_caches[@]}"; do + size=$(du -sb "$cache_dir" 2>/dev/null | awk '{print $1}') + total_bytes=$((total_bytes + size)) + done + + echo "Found ${#wp_caches[@]} WordPress cache directories" + echo "Total size: $(format_size $total_bytes)" + echo "" + + echo "Cache directories:" + for cache_dir in "${wp_caches[@]}"; do + size=$(du -sh "$cache_dir" 2>/dev/null | awk '{print $1}') + echo -e " ${CYAN}$size${NC} $cache_dir" + done + + echo "" + echo -e "${YELLOW}WARNING: This will clear all WordPress cache files${NC}" + echo "" + read -p "Continue? (type 'yes' to confirm): " confirm + + if [ "$confirm" = "yes" ]; then + for cache_dir in "${wp_caches[@]}"; do + rm -rf "$cache_dir"/* 2>/dev/null + done + echo "" + echo -e "${GREEN}✓ Cleared WordPress cache (freed $(format_size $total_bytes))${NC}" + else + echo "" + echo "Cancelled" + fi + + echo "" + press_enter +} + +show_cleanup_summary() { + clear + print_banner "Cleanup Summary (Dry Run)" + echo "" + echo "Calculating potential space savings..." + echo "" + + local total_bytes=0 + + # Old logs + show_progress "Checking old logs" + local old_logs_files=$(find /var/log -name "*.log" -mtime +30 -type f 2>/dev/null) + local old_logs=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + old_logs=$((old_logs + size)) + done <<< "$old_logs_files" + total_bytes=$((total_bytes + old_logs)) + local old_logs_count=$(echo "$old_logs_files" | grep -v '^$' | wc -l) + echo -e "\r Old logs (>30 days): ${CYAN}$(format_size $old_logs)${NC} ($old_logs_count files)" + + # Compressed logs + show_progress "Checking compressed logs" + local comp_logs_files=$(find /var/log -name "*.gz" -mtime +30 -type f 2>/dev/null) + local comp_logs=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + comp_logs=$((comp_logs + size)) + done <<< "$comp_logs_files" + total_bytes=$((total_bytes + comp_logs)) + local comp_logs_count=$(echo "$comp_logs_files" | grep -v '^$' | wc -l) + echo -e "\r Compressed logs (>30 days): ${CYAN}$(format_size $comp_logs)${NC} ($comp_logs_count files)" + + # Old temp + show_progress "Checking temp files" + local old_temp_files=$(find /tmp -type f -mtime +7 2>/dev/null) + local old_temp=0 + while IFS= read -r file; do + [ -z "$file" ] && continue + size=$(stat -c%s "$file" 2>/dev/null || echo 0) + old_temp=$((old_temp + size)) + done <<< "$old_temp_files" + total_bytes=$((total_bytes + old_temp)) + local old_temp_count=$(echo "$old_temp_files" | grep -v '^$' | wc -l) + echo -e "\r Old temp files (>7 days): ${CYAN}$(format_size $old_temp)${NC} ($old_temp_count files)" + + # Package cache + show_progress "Checking package cache" + local pkg_cache=0 + if [ -d "/var/cache/yum" ]; then + pkg_cache=$(du -sb /var/cache/yum 2>/dev/null | awk '{print $1}') + elif [ -d "/var/cache/dnf" ]; then + pkg_cache=$(du -sb /var/cache/dnf 2>/dev/null | awk '{print $1}') + elif [ -d "/var/cache/apt/archives" ]; then + pkg_cache=$(du -sb /var/cache/apt/archives 2>/dev/null | awk '{print $1}') + fi + total_bytes=$((total_bytes + pkg_cache)) + echo -e "\r Package cache: ${CYAN}$(format_size $pkg_cache)${NC}" + + echo "" + echo "───────────────────────────────────────────────────────────────" + echo -e "${BOLD}Total Potential Savings: ${GREEN}$(format_size $total_bytes)${NC}" + echo "" + + press_enter +} + +################################################################################ +# MAIN MENU +################################################################################ + +show_main_menu() { + clear + print_banner "Disk Space Analyzer" + echo "" + echo -e "${BOLD}Quick Analysis:${NC}" + echo "" + echo -e " ${GREEN}1)${NC} Disk Overview - Current disk usage summary" + echo -e " ${GREEN}2)${NC} Largest Directories - Find space-consuming directories" + echo -e " ${GREEN}3)${NC} Largest Files - Find individual large files" + echo -e " ${GREEN}4)${NC} Old Files - Files not accessed recently" + echo "" + echo -e "${BOLD}Specific Analysis:${NC}" + echo "" + echo -e " ${CYAN}5)${NC} Log Files - Analyze log file usage" + echo -e " ${CYAN}6)${NC} Email Storage - Mail directory analysis" + echo -e " ${CYAN}7)${NC} Database Storage - MySQL/PostgreSQL data" + echo -e " ${CYAN}8)${NC} Backup Files - Find old backups" + echo -e " ${CYAN}9)${NC} WordPress Sites - WP uploads/cache/plugins" + echo -e " ${CYAN}10)${NC} Temporary Files - /tmp and /var/tmp analysis" + echo -e " ${CYAN}11)${NC} User/Domain Usage - Breakdown by account" + echo -e " ${CYAN}12)${NC} Package Cache - Package manager cache size" + echo "" + echo -e "${BOLD}Actions:${NC}" + echo "" + echo -e " ${YELLOW}13)${NC} Interactive Cleanup - Safe cleanup wizard" + echo -e " ${YELLOW}14)${NC} Generate Report - Export analysis to file" + echo "" + echo -e " ${RED}0)${NC} Exit" + echo "" + echo -e "${CYAN}──────────────────────────────────────────────────────────────${NC}" + read -p "Select option: " choice +} + +################################################################################ +# MAIN LOOP +################################################################################ + +main() { + while true; do + show_main_menu + + case "$choice" in + 1) analyze_disk_overview ;; + 2) find_largest_directories "/" 2 ;; + 3) find_largest_files "/" "100M" ;; + 4) analyze_old_files "/home" 90 ;; + 5) analyze_log_files ;; + 6) analyze_email_storage ;; + 7) analyze_databases ;; + 8) analyze_backups ;; + 9) analyze_wordpress ;; + 10) analyze_temp_files ;; + 11) analyze_by_user ;; + 12) analyze_package_cache ;; + 13) interactive_cleanup_menu ;; + 14) generate_report ;; + 0) + echo "" + echo -e "${GREEN}Exiting...${NC}" + exit 0 + ;; + *) + echo -e "${RED}Invalid option${NC}" + sleep 1 + ;; + esac + done +} + +# Run main function +main