#!/bin/bash ################################################################################ # Intelligent Website Error Analyzer ################################################################################ # Purpose: Find REAL website issues affecting REAL users # Filters out: Bots, plugin warnings, deprecation notices, minor issues # Focuses on: Critical errors that break sites for actual visitors ################################################################################ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" source "$SCRIPT_DIR/lib/common-functions.sh" source "$SCRIPT_DIR/lib/system-detect.sh" source "$SCRIPT_DIR/lib/user-manager.sh" # Configuration APACHE_ERROR_LOG="/var/log/apache2/error_log" DOMLOGS_DIR="/var/log/apache2/domlogs" MODSEC_AUDIT_LOG="/usr/local/apache/logs/modsec_audit.log" HOURS_TO_ANALYZE=24 FILTER_USER="" FILTER_DOMAIN="" print_banner "Intelligent Website Error Analyzer" echo "" echo "Finding REAL issues affecting REAL users..." echo "" echo "What we filter out:" echo " ✗ Bot/scanner activity" echo " ✗ Plugin deprecation warnings" echo " ✗ PHP notices (non-critical)" echo " ✗ Random 404s for missing favicons/assets" echo " ✗ Security probe attempts" echo "" echo "What we focus on:" echo " ✓ 500 errors users actually see" echo " ✓ Fatal PHP errors breaking functionality" echo " ✓ Database connection failures" echo " ✓ Permission issues blocking access" echo " ✓ Memory exhaustion affecting performance" echo "" # Ask for filtering scope echo -e "${CYAN}Analysis Scope:${NC}" echo " 1) All users/domains (default)" echo " 2) Specific cPanel user" echo " 3) Specific domain" echo "" read -p "Select option [1]: " scope_choice scope_choice=${scope_choice:-1} case $scope_choice in 2) # Select specific user select_user_interactive "Select cPanel user to analyze" if [ -n "$SELECTED_USER" ]; then FILTER_USER="$SELECTED_USER" echo "→ Filtering for user: $FILTER_USER" fi ;; 3) # Enter specific domain echo "" read -p "Enter domain name (e.g., example.com): " FILTER_DOMAIN if [ -n "$FILTER_DOMAIN" ]; then echo "→ Filtering for domain: $FILTER_DOMAIN" fi ;; *) echo "→ Analyzing all users/domains" ;; esac echo "" # Ask for time range echo -e "${CYAN}How far back should we analyze?${NC}" echo " 1) Last 1 hour" echo " 2) Last 6 hours" echo " 3) Last 24 hours (default)" echo " 4) Last 7 days" echo " 5) Last 30 days" echo "" read -p "Select option [3]: " time_choice time_choice=${time_choice:-3} case $time_choice in 1) HOURS_TO_ANALYZE=1 ;; 2) HOURS_TO_ANALYZE=6 ;; 3) HOURS_TO_ANALYZE=24 ;; 4) HOURS_TO_ANALYZE=168 ;; 5) HOURS_TO_ANALYZE=720 ;; *) HOURS_TO_ANALYZE=24 ;; esac echo "" echo "→ Analyzing last $HOURS_TO_ANALYZE hours..." echo "" # Temporary files TEMP_DIR="/tmp/website-error-analysis-$$" mkdir -p "$TEMP_DIR" trap "rm -rf $TEMP_DIR" EXIT CRITICAL_ERRORS="$TEMP_DIR/critical.txt" USER_IMPACT_ERRORS="$TEMP_DIR/user_impact.txt" LOG_FILES_LIST="$TEMP_DIR/log_files.txt" # Discover all log file locations echo "→ Discovering log file locations..." > "$LOG_FILES_LIST" # Apache main error log (only if not filtering by user) if [ -z "$FILTER_USER" ] && [ -z "$FILTER_DOMAIN" ]; then for log in /var/log/apache2/error_log /usr/local/apache/logs/error_log /var/log/httpd/error_log; do [ -f "$log" ] && echo "$log|apache_main" >> "$LOG_FILES_LIST" done fi # Per-domain PHP error logs in public_html if [ -n "$FILTER_USER" ]; then # Specific user find "/home/$FILTER_USER" -name "error_log" -type f 2>/dev/null | while read -r log; do echo "$log|php_$FILTER_USER" >> "$LOG_FILES_LIST" done elif [ -n "$FILTER_DOMAIN" ]; then # Try to find domain's user and error logs user=$(grep -l "DNS.*$FILTER_DOMAIN" /var/cpanel/users/* 2>/dev/null | head -1 | xargs basename 2>/dev/null) if [ -n "$user" ]; then find "/home/$user" -name "error_log" -type f 2>/dev/null | while read -r log; do echo "$log|php_$FILTER_DOMAIN" >> "$LOG_FILES_LIST" done fi else # All users find /home/*/public_html -name "error_log" -type f 2>/dev/null | while read -r log; do username=$(echo "$log" | grep -oE '/home/[^/]+' | sed 's|/home/||') echo "$log|php_$username" >> "$LOG_FILES_LIST" done fi # Per-domain Apache logs in domlogs if [ -d "$DOMLOGS_DIR" ]; then if [ -n "$FILTER_DOMAIN" ]; then # Specific domain for log in "$DOMLOGS_DIR/$FILTER_DOMAIN" "$DOMLOGS_DIR/$FILTER_DOMAIN-"*; do [ -f "$log" ] && echo "$log|domlog_$FILTER_DOMAIN" >> "$LOG_FILES_LIST" done elif [ -n "$FILTER_USER" ]; then # Specific user - find their domains if [ -f "/var/cpanel/users/$FILTER_USER" ]; then grep "^DNS" "/var/cpanel/users/$FILTER_USER" | awk '{print $2}' | while read -r domain; do for log in "$DOMLOGS_DIR/$domain" "$DOMLOGS_DIR/$domain-"*; do [ -f "$log" ] && echo "$log|domlog_$domain" >> "$LOG_FILES_LIST" done done fi else # All domains for log in "$DOMLOGS_DIR"/*; do [ -f "$log" ] && ! [[ "$log" =~ (bytes_log|offset|error_log|ftpxferlog)$ ]] && \ echo "$log|domlog_$(basename "$log")" >> "$LOG_FILES_LIST" done fi fi # ModSecurity audit log (only if not filtering or matches filter) if [ -f "$MODSEC_AUDIT_LOG" ]; then if [ -z "$FILTER_USER" ] && [ -z "$FILTER_DOMAIN" ]; then echo "$MODSEC_AUDIT_LOG|modsec_audit" >> "$LOG_FILES_LIST" elif [ -n "$FILTER_DOMAIN" ]; then # Will filter ModSec entries during processing echo "$MODSEC_AUDIT_LOG|modsec_audit" >> "$LOG_FILES_LIST" fi fi log_count=$(wc -l < "$LOG_FILES_LIST" 2>/dev/null || echo "0") echo " Found $log_count log files to analyze" if [ -n "$FILTER_USER" ]; then echo " Scope: User '$FILTER_USER'" elif [ -n "$FILTER_DOMAIN" ]; then echo " Scope: Domain '$FILTER_DOMAIN'" fi echo "" ################################################################################ # Intelligent Filtering ################################################################################ is_noise() { local line="$1" # Bot/Scanner patterns if echo "$line" | grep -qiE "bot|crawler|spider|scanner|nikto|nmap|masscan|sqlmap|nessus|acunetix|burp|shodan|censys|zgrab|nuclei|semrush|ahrefs|mj12"; then return 0 fi # Known scanner IPs if echo "$line" | grep -qE "45\.148\.|185\.220\.|89\.248\.165\."; then return 0 fi # WordPress plugin deprecation warnings (not user-facing) if echo "$line" | grep -qiE "PHP Deprecated|Deprecated.*plugin|call_user_func_array.*deprecated"; then return 0 fi # Non-critical PHP notices if echo "$line" | grep -qiE "PHP Notice.*Undefined (variable|index|offset)" && \ ! echo "$line" | grep -qiE "fatal|error 500|white screen"; then return 0 fi # Missing favicon/common assets (not real errors) if echo "$line" | grep -qiE "favicon\.ico|apple-touch-icon|robots\.txt|sitemap\.xml.*404"; then return 0 fi # Security probes if echo "$line" | grep -qiE "wp-admin|wp-login|phpMyAdmin|phpmyadmin|\.env|\.git|config\.php|xmlrpc|eval-stdin|shell\.php|c99\.php|r57\.php|adminer\.php" && \ echo "$line" | grep -qiE "404|403|not found"; then return 0 fi # WordPress auto-updates and cron (normal operations) if echo "$line" | grep -qiE "wp-cron\.php|doing_cron|auto.*update"; then return 0 fi # Common plugin update checks (not errors) if echo "$line" | grep -qiE "update-check|version-check|api\.wordpress\.org"; then return 0 fi return 1 } is_critical_user_facing() { local line="$1" # 500 Internal Server Error (users see white page) if echo "$line" | grep -qiE " 500 |Internal Server Error"; then return 0 fi # PHP Fatal Errors (breaks functionality) if echo "$line" | grep -qiE "PHP Fatal error|Fatal error:.*in /"; then return 0 fi # PHP Parse Errors (site completely broken) if echo "$line" | grep -qiE "PHP Parse error|syntax error"; then return 0 fi # Database connection failures (site down) if echo "$line" | grep -qiE "Error establishing.*database|Can't connect.*MySQL|Access denied for user.*database|Too many connections|MySQL server has gone away"; then return 0 fi # Memory exhaustion (white screen/incomplete pages) if echo "$line" | grep -qiE "Allowed memory size.*exhausted|Out of memory|Fatal.*memory"; then return 0 fi # Segmentation fault (complete crash) if echo "$line" | grep -qiE "Segmentation fault|signal 11"; then return 0 fi # Permission denied on critical files if echo "$line" | grep -qiE "Permission denied" && \ echo "$line" | grep -qE "index\.php|wp-config\.php|\.htaccess|config\.php"; then return 0 fi # File not found for MAIN page (404 on homepage/index) if echo "$line" | grep -qiE "File does not exist.*index\.(php|html)" || \ echo "$line" | grep -qE " 404 .*\" \"GET / "; then return 0 fi return 1 } extract_useful_info() { local line="$1" # Extract domain domain=$(echo "$line" | grep -oE '\[vhost [^:]+' | sed 's/\[vhost //' || \ echo "$line" | grep -oE '[a-zA-Z0-9.-]+\.(com|net|org|io|co|uk|us|dev)' | head -1 || \ echo "$line" | grep -oE '/home/[^/]+' | sed 's|/home/||' || echo "unknown") # Extract file path if PHP error file_path=$(echo "$line" | grep -oE "in /[^ ]+\.php" | sed 's/in //' || echo "") # Extract error message (clean up ModSec noise, timestamps, etc.) error_msg=$(echo "$line" | \ sed 's/^\[.*\] //' | \ sed 's/\[client [^]]*\] //' | \ sed 's/\[unique_id "[^"]*"\]//g' | \ sed 's/\[pid [^]]*\]//g' | \ sed 's/\[tid [^]]*\]//g' | \ grep -v "^$" | \ cut -c1-150) # Skip if error message is empty or just whitespace if [ -z "$(echo "$error_msg" | tr -d '[:space:]')" ]; then return 1 fi echo "$domain|$file_path|$error_msg" } ################################################################################ # Main Analysis ################################################################################ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " SCANNING FOR USER-IMPACTING ERRORS" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" total_lines=0 filtered_out=0 critical_found=0 # Analyze all discovered log files while IFS='|' read -r log_path log_type; do echo " Analyzing: $(basename "$log_path")..." # Determine log type is_access_log=false is_modsec_log=false [[ "$log_type" == domlog_* ]] && ! [[ "$log_path" =~ error ]] && is_access_log=true [[ "$log_type" == modsec_* ]] && is_modsec_log=true if $is_modsec_log; then # ModSecurity audit log - parse for blocks/denials while IFS= read -r line; do ((total_lines++)) # Skip if not a security event echo "$line" | grep -qiE "ModSecurity.*Action.*Intercepted|status: (403|406|500)" || continue # Extract domain/host if filtering if [ -n "$FILTER_DOMAIN" ]; then echo "$line" | grep -q "$FILTER_DOMAIN" || continue fi # Extract useful info if echo "$line" | grep -qiE "SQL Injection|XSS|Command Injection|Path Traversal"; then ((critical_found++)) attack_type=$(echo "$line" | grep -oiE "SQL Injection|XSS|Command Injection|Path Traversal" | head -1) uri=$(echo "$line" | grep -oE "uri: [^ ]+" | sed 's/uri: //' || echo "") domain=$(echo "$line" | grep -oE "hostname: [^ ]+" | sed 's/hostname: //' || echo "unknown") echo "$domain||ModSecurity blocked: $attack_type $uri" >> "$CRITICAL_ERRORS" fi done < <(tail -n 5000 "$log_path" 2>/dev/null) elif $is_access_log; then # Access log - look for 5xx status codes while IFS= read -r line; do ((total_lines++)) # Skip if bot/scanner if is_noise "$line"; then ((filtered_out++)) continue fi # Extract status code and URL if echo "$line" | grep -qE '" 5[0-9]{2} '; then status=$(echo "$line" | grep -oE '" 5[0-9]{2} ' | tr -d '" ') url=$(echo "$line" | awk '{print $7}' | cut -c1-80) ip=$(echo "$line" | awk '{print $1}') domain=$(basename "$log_path" | sed 's/-.*//') # Apply domain filter if set if [ -n "$FILTER_DOMAIN" ] && [ "$domain" != "$FILTER_DOMAIN" ]; then continue fi ((critical_found++)) echo "$domain||HTTP $status - $url (from $ip)" >> "$CRITICAL_ERRORS" fi done < <(tail -n 5000 "$log_path" 2>/dev/null) else # Error log - look for critical errors while IFS= read -r line; do ((total_lines++)) # Skip noise if is_noise "$line"; then ((filtered_out++)) continue fi # Apply user/domain filter if set if [ -n "$FILTER_USER" ]; then echo "$line" | grep -q "/home/$FILTER_USER" || continue fi if [ -n "$FILTER_DOMAIN" ]; then echo "$line" | grep -q "$FILTER_DOMAIN" || continue fi # Check if it's critical and user-facing if is_critical_user_facing "$line"; then ((critical_found++)) extract_useful_info "$line" >> "$CRITICAL_ERRORS" fi done < <(tail -n 10000 "$log_path" 2>/dev/null) fi done < "$LOG_FILES_LIST" echo "" echo "Scanned: $total_lines log entries" echo "Filtered: $filtered_out noise entries (bots, warnings, etc.)" echo "Found: $critical_found critical user-facing errors" echo "" ################################################################################ # Generate Report ################################################################################ if [ ! -f "$CRITICAL_ERRORS" ] || [ ! -s "$CRITICAL_ERRORS" ]; then echo "" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN} ✓ NO CRITICAL USER-FACING ERRORS FOUND!${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo "Your websites appear to be functioning normally for real users." echo "All errors found were from bots, scanners, or non-critical warnings." echo "" exit 0 fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " ⚠️ CRITICAL ERRORS AFFECTING REAL USERS" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Group by domain and count echo -e "${RED}${BOLD}AFFECTED WEBSITES:${NC}" echo "" cat "$CRITICAL_ERRORS" | cut -d'|' -f1 | sort | uniq -c | sort -rn | head -10 | while read -r count domain; do printf " ${RED}●${NC} %-40s ${BOLD}%4d errors${NC}\n" "$domain" "$count" done echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " ERROR DETAILS (Top Issues)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Group identical errors and count them awk -F'|' '{ key = $1 "|" $3 # domain|error_msg (skip file_path for grouping) file[$1"|"$3] = $2 # Store file path count[key]++ } END { for (key in count) { split(key, parts, "|") domain = parts[1] error = parts[2] file_path = file[key] # Skip empty errors if (length(error) == 0) next print count[key] "|" domain "|" file_path "|" error } }' "$CRITICAL_ERRORS" | sort -t'|' -k1 -rn | head -20 | while IFS='|' read -r count domain file_path error_msg; do # Color code by frequency if [ "$count" -ge 10 ]; then color="${RED}" priority="[HIGH FREQUENCY]" elif [ "$count" -ge 5 ]; then color="${YELLOW}" priority="[MEDIUM]" else color="${INFO_COLOR}" priority="" fi echo -e "${color}● Occurred $count times${NC} $priority" [ -n "$domain" ] && [ "$domain" != "unknown" ] && echo " Domain: $domain" [ -n "$file_path" ] && echo " File: $file_path" echo " Error: $error_msg" echo "" done ################################################################################ # Intelligent Recommendations ################################################################################ echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " 🔧 RECOMMENDED ACTIONS" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Detect issue types and provide specific recommendations if grep -qiE "500|Internal Server" "$CRITICAL_ERRORS"; then echo -e "${RED}● 500 Internal Server Errors detected${NC}" echo " Priority: URGENT - Users see broken pages" echo "" echo " Actions:" echo " 1. Check PHP error logs for affected domains:" for domain in $(grep -iE "500|Internal Server" "$CRITICAL_ERRORS" | cut -d'|' -f1 | sort -u | head -3); do echo " tail -100 /home/$domain/public_html/error_log" done echo "" echo " 2. Common causes:" echo " - PHP syntax errors (check recent file changes)" echo " - .htaccess syntax errors" echo " - Incorrect file permissions" echo " - Missing PHP modules" echo "" fi if grep -qiE "Fatal error|Parse error" "$CRITICAL_ERRORS"; then echo -e "${RED}● PHP Fatal/Parse Errors detected${NC}" echo " Priority: URGENT - Site functionality broken" echo "" echo " Actions:" echo " 1. Review recent plugin/theme updates" echo " 2. Check PHP version compatibility" echo " 3. Look for syntax errors in custom code" echo " 4. Temporarily disable recently added plugins" echo "" fi if grep -qiE "database|MySQL|Connection" "$CRITICAL_ERRORS"; then echo -e "${RED}● Database Connection Errors detected${NC}" echo " Priority: CRITICAL - Site may be completely down" echo "" echo " Actions:" echo " 1. Check MySQL service: systemctl status mysql" echo " 2. Verify database credentials in config files" echo " 3. Check max_connections: mysql -e 'SHOW VARIABLES LIKE \"max_connections\"'" echo " 4. Review current connections: mysql -e 'SHOW PROCESSLIST'" echo "" fi if grep -qiE "memory.*exhausted|Out of memory" "$CRITICAL_ERRORS"; then echo -e "${RED}● Memory Exhaustion detected${NC}" echo " Priority: HIGH - Pages loading slowly or incompletely" echo "" echo " Current PHP memory limit: $(php -r 'echo ini_get("memory_limit");' 2>/dev/null)" echo "" echo " Actions:" echo " 1. Increase memory_limit in php.ini or .user.ini" echo " 2. Recommended: 256M minimum, 512M for WooCommerce" echo " 3. Check for memory leaks in plugins" echo " 4. Optimize database queries" echo "" fi if grep -qiE "ModSecurity" "$CRITICAL_ERRORS"; then echo -e "${YELLOW}● ModSecurity Blocks detected${NC}" echo " Priority: MEDIUM - Security rules blocking requests" echo "" echo " Actions:" echo " 1. Review ModSecurity audit log for false positives" echo " 2. Check if legitimate functionality is being blocked" echo " 3. Consider whitelisting specific rules if needed" echo " 4. Review blocked attack types to understand threats" echo " 5. ModSecurity log: $MODSEC_AUDIT_LOG" echo "" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Full analysis saved to: $CRITICAL_ERRORS" echo "" echo "Next steps:" echo " 1. Address critical errors first (500s, database, fatal errors)" echo " 2. Check error_log files for affected domains" echo " 3. Test affected pages after fixes" echo " 4. Re-run this tool to verify issues are resolved" echo "" press_enter