diff --git a/modules/website/website-error-analyzer.sh b/modules/website/website-error-analyzer.sh index c8ddaa6..fbc3df2 100755 --- a/modules/website/website-error-analyzer.sh +++ b/modules/website/website-error-analyzer.sh @@ -310,7 +310,102 @@ extract_useful_info() { return 1 fi - echo "$domain|$file_path|$error_msg" + # Correlate to root cause + root_cause=$(correlate_root_cause "$line" "$error_msg" "$domain") + + echo "$domain|$file_path|$error_msg|$root_cause" +} + +correlate_root_cause() { + local line="$1" + local error_msg="$2" + local domain="$3" + local cause="UNKNOWN" + + # .htaccess issues + if echo "$line" | grep -qiE "\.htaccess|Invalid command|RewriteRule|RewriteCond"; then + cause="HTACCESS" + + # ModSecurity blocks + elif echo "$line" | grep -qiE "ModSecurity|mod_security|WAF"; then + cause="MODSECURITY" + + # PHP memory limit + elif echo "$error_msg" | grep -qiE "memory.*exhausted|Allowed memory size"; then + # Get current memory limit for this domain/user + if [ -n "$domain" ] && [ "$domain" != "unknown" ]; then + user=$(grep -l "DNS.*$domain" /var/cpanel/users/* 2>/dev/null | head -1 | xargs basename 2>/dev/null) + if [ -n "$user" ]; then + mem_limit=$(grep -h "memory_limit" /home/$user/public_html/.user.ini /home/$user/.php.ini 2>/dev/null | tail -1 | cut -d'=' -f2 | tr -d ' ') + [ -n "$mem_limit" ] && cause="PHP_MEMORY:$mem_limit" || cause="PHP_MEMORY:default" + else + cause="PHP_MEMORY" + fi + else + cause="PHP_MEMORY" + fi + + # PHP max execution time + elif echo "$error_msg" | grep -qiE "max_execution_time|Maximum execution time"; then + cause="PHP_TIMEOUT" + + # PHP upload/post size limits + elif echo "$error_msg" | grep -qiE "upload_max_filesize|post_max_size"; then + cause="PHP_UPLOAD_LIMIT" + + # File permissions + elif echo "$error_msg" | grep -qiE "Permission denied|Failed to open stream.*permission"; then + cause="FILE_PERMISSIONS" + + # Missing PHP modules/extensions + elif echo "$error_msg" | grep -qiE "undefined function|Call to undefined function|Class .* not found"; then + # Try to identify which module + if echo "$error_msg" | grep -qiE "imagecreate|imagefilter|gd_"; then + cause="MISSING_PHP_GD" + elif echo "$error_msg" | grep -qiE "curl_|CURL"; then + cause="MISSING_PHP_CURL" + elif echo "$error_msg" | grep -qiE "json_|JSON"; then + cause="MISSING_PHP_JSON" + elif echo "$error_msg" | grep -qiE "mysqli|mysql_connect"; then + cause="MISSING_PHP_MYSQLI" + else + cause="MISSING_PHP_MODULE" + fi + + # Database issues + elif echo "$error_msg" | grep -qiE "database|MySQL|mysqli"; then + if echo "$error_msg" | grep -qiE "Too many connections|max_connections"; then + cause="DB_MAX_CONNECTIONS" + elif echo "$error_msg" | grep -qiE "Access denied|authentication"; then + cause="DB_AUTH_FAILED" + elif echo "$error_msg" | grep -qiE "gone away|Lost connection"; then + cause="DB_TIMEOUT" + else + cause="DB_ERROR" + fi + + # Apache configuration + elif echo "$error_msg" | grep -qiE "Invalid URI|Request exceeded the limit|LimitRequestLine"; then + cause="APACHE_CONFIG" + + # PHP parse/syntax errors (code issues) + elif echo "$error_msg" | grep -qiE "Parse error|syntax error|unexpected"; then + cause="PHP_SYNTAX_ERROR" + + # File not found (may indicate bad symlinks or missing files) + elif echo "$error_msg" | grep -qiE "No such file|failed to open stream"; then + cause="MISSING_FILE" + + # Generic PHP fatal error + elif echo "$error_msg" | grep -qiE "Fatal error"; then + cause="PHP_FATAL_ERROR" + + # 500 errors without specific cause + elif echo "$error_msg" | grep -qiE "500|Internal Server"; then + cause="SERVER_ERROR_500" + fi + + echo "$cause" } ################################################################################ @@ -356,7 +451,12 @@ while IFS='|' read -r log_path log_type; do 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" + # Extract rule ID if available + rule_id=$(echo "$line" | grep -oE "id \"[0-9]+\"" | grep -oE "[0-9]+" || echo "") + root_cause="MODSECURITY" + [ -n "$rule_id" ] && root_cause="MODSECURITY:rule_$rule_id" + + echo "$domain||ModSecurity blocked: $attack_type $uri|$root_cause" >> "$CRITICAL_ERRORS" fi done < <(tail -n 5000 "$log_path" 2>/dev/null) @@ -384,8 +484,17 @@ while IFS='|' read -r log_path log_type; do continue fi + # Determine root cause from status code + case $status in + 500) root_cause="SERVER_ERROR_500" ;; + 502) root_cause="APACHE_CONFIG:bad_gateway" ;; + 503) root_cause="APACHE_CONFIG:service_unavailable" ;; + 504) root_cause="APACHE_CONFIG:gateway_timeout" ;; + *) root_cause="SERVER_ERROR_5XX" ;; + esac + ((critical_found++)) - echo "$domain||HTTP $status - $url (from $ip)" >> "$CRITICAL_ERRORS" + echo "$domain||HTTP $status - $url (from $ip)|$root_cause" >> "$CRITICAL_ERRORS" fi done < <(tail -n 5000 "$log_path" 2>/dev/null) @@ -462,8 +571,8 @@ 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 + key = $1 "|" $3 "|" $4 # domain|error_msg|root_cause + file[$1"|"$3"|"$4] = $2 # Store file path count[key]++ } END { @@ -471,14 +580,15 @@ END { split(key, parts, "|") domain = parts[1] error = parts[2] + root_cause = parts[3] file_path = file[key] # Skip empty errors if (length(error) == 0) next - print count[key] "|" domain "|" file_path "|" error + print count[key] "|" domain "|" file_path "|" error "|" root_cause } -}' "$CRITICAL_ERRORS" | sort -t'|' -k1 -rn | head -20 | while IFS='|' read -r count domain file_path error_msg; do +}' "$CRITICAL_ERRORS" | sort -t'|' -k1 -rn | head -20 | while IFS='|' read -r count domain file_path error_msg root_cause; do # Color code by frequency if [ "$count" -ge 10 ]; then @@ -496,14 +606,150 @@ END { [ -n "$domain" ] && [ "$domain" != "unknown" ] && echo " Domain: $domain" [ -n "$file_path" ] && echo " File: $file_path" echo " Error: $error_msg" + + # Display root cause with color coding and actionable fix + if [ -n "$root_cause" ] && [ "$root_cause" != "UNKNOWN" ]; then + echo -ne " ${CYAN}Root Cause: ${BOLD}" + + case "$root_cause" in + HTACCESS) + echo -e "${YELLOW}⚙️ .htaccess Configuration${NC}" + echo " → Check .htaccess syntax in domain root" + echo " → Look for invalid RewriteRule or directives" + ;; + MODSECURITY*) + rule=$(echo "$root_cause" | cut -d':' -f2) + echo -e "${YELLOW}🛡️ ModSecurity WAF Block${NC}" + [ -n "$rule" ] && echo " → Rule ID: $rule" + echo " → Check if this is a false positive" + echo " → Review: /usr/local/apache/logs/modsec_audit.log" + ;; + PHP_MEMORY*) + limit=$(echo "$root_cause" | cut -d':' -f2) + echo -e "${RED}💾 PHP Memory Exhausted${NC}" + [ -n "$limit" ] && echo " → Current limit: $limit" + echo " → Increase memory_limit in .user.ini or php.ini" + echo " → Recommended: 256M minimum, 512M for WooCommerce" + ;; + PHP_TIMEOUT) + echo -e "${YELLOW}⏱️ PHP Execution Timeout${NC}" + echo " → Increase max_execution_time in php.ini" + echo " → Recommended: 300 for imports/backups" + ;; + PHP_UPLOAD_LIMIT) + echo -e "${YELLOW}📤 PHP Upload Size Limit${NC}" + echo " → Increase upload_max_filesize and post_max_size" + echo " → Match both values (e.g., 64M)" + ;; + FILE_PERMISSIONS) + echo -e "${RED}🔒 File Permission Denied${NC}" + echo " → Check file ownership and permissions" + echo " → Files should be 644, directories 755" + echo " → Owner should match cPanel user" + ;; + MISSING_PHP_GD) + echo -e "${RED}📦 Missing PHP GD Extension${NC}" + echo " → Install: yum install ea-phpXX-php-gd" + echo " → Required for image processing" + ;; + MISSING_PHP_CURL) + echo -e "${RED}📦 Missing PHP cURL Extension${NC}" + echo " → Install: yum install ea-phpXX-php-curl" + ;; + MISSING_PHP_*) + module=$(echo "$root_cause" | sed 's/MISSING_PHP_//' | tr '[:upper:]' '[:lower:]') + echo -e "${RED}📦 Missing PHP Extension: $module${NC}" + echo " → Install: yum install ea-phpXX-php-$module" + ;; + DB_MAX_CONNECTIONS) + echo -e "${RED}🗄️ Database Max Connections Reached${NC}" + echo " → Check: mysql -e 'SHOW VARIABLES LIKE \"max_connections\"'" + echo " → Increase max_connections in my.cnf" + echo " → Or optimize slow queries reducing connection time" + ;; + DB_AUTH_FAILED) + echo -e "${RED}🗄️ Database Authentication Failed${NC}" + echo " → Verify credentials in wp-config.php / config files" + echo " → Check database user permissions" + ;; + DB_TIMEOUT) + echo -e "${YELLOW}🗄️ Database Connection Timeout${NC}" + echo " → MySQL server may be overloaded" + echo " → Check slow query log" + ;; + DB_ERROR) + echo -e "${RED}🗄️ Database Error${NC}" + echo " → Check MySQL error log for details" + ;; + APACHE_CONFIG*) + issue=$(echo "$root_cause" | cut -d':' -f2) + echo -e "${YELLOW}⚙️ Apache Configuration${NC}" + [ -n "$issue" ] && echo " → Issue: $issue" + echo " → Check Apache config and vhost settings" + ;; + PHP_SYNTAX_ERROR) + echo -e "${RED}⚠️ PHP Syntax Error in Code${NC}" + echo " → Review recent code changes" + echo " → Check for missing semicolons, brackets, quotes" + ;; + MISSING_FILE) + echo -e "${YELLOW}📄 File Not Found${NC}" + echo " → File may have been deleted or moved" + echo " → Check for broken symlinks" + ;; + PHP_FATAL_ERROR) + echo -e "${RED}⚠️ PHP Fatal Error${NC}" + echo " → Review PHP error logs for details" + echo " → May be compatibility issue or code bug" + ;; + SERVER_ERROR_500) + echo -e "${RED}🔥 Generic 500 Internal Server Error${NC}" + echo " → Check PHP/Apache error logs for specifics" + ;; + *) + echo -e "${NC}$root_cause" + ;; + esac + fi + echo "" done +################################################################################ +# Root Cause Summary +################################################################################ + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " 📊 ROOT CAUSE BREAKDOWN" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Count errors by root cause +cut -d'|' -f4 "$CRITICAL_ERRORS" | grep -v "^$" | sort | uniq -c | sort -rn | while read -r count cause; do + # Clean up cause display + clean_cause=$(echo "$cause" | sed 's/:.*//; s/_/ /g') + + # Color code by severity + case "$cause" in + PHP_MEMORY*|DB_MAX_CONNECTIONS|PHP_FATAL_ERROR|SERVER_ERROR_500) + color="${RED}"; icon="🔥" ;; + HTACCESS|MODSECURITY*|PHP_TIMEOUT|DB_*) + color="${YELLOW}"; icon="⚠️ " ;; + MISSING_PHP*|FILE_PERMISSIONS) + color="${YELLOW}"; icon="📦" ;; + *) + color="${INFO_COLOR}"; icon="•" ;; + esac + + printf " ${color}${icon} %-35s %4d errors${NC}\n" "$clean_cause" "$count" +done + +echo "" + ################################################################################ # Intelligent Recommendations ################################################################################ -echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " 🔧 RECOMMENDED ACTIONS" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"