From 4e49776a3583c46e87096f86f2f10cb37ab15ddd Mon Sep 17 00:00:00 2001 From: cschantz Date: Wed, 3 Dec 2025 00:52:44 -0500 Subject: [PATCH] Fix SCRIPT_DIR variable collision in PHP libraries CRITICAL BUG FIX: Problem: php-detector.sh and php-analyzer.sh were setting SCRIPT_DIR which collided with parent script's SCRIPT_DIR variable causing /lib/lib/ double path bug when sourcing libraries. Solution: - Changed SCRIPT_DIR to _LIB_DIR in both php-detector.sh and php-analyzer.sh - Changed exit 1 to return 1 in sourced libraries (exit kills parent script) Files modified: - lib/php-detector.sh: Use _LIB_DIR instead of SCRIPT_DIR - lib/php-analyzer.sh: Use _LIB_DIR instead of SCRIPT_DIR, return instead of exit This prevents variable collision when libraries are sourced by modules. --- lib/php-analyzer.sh | 6 +- lib/php-detector.sh | 428 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 lib/php-detector.sh diff --git a/lib/php-analyzer.sh b/lib/php-analyzer.sh index 0cd840f..2992a6a 100644 --- a/lib/php-analyzer.sh +++ b/lib/php-analyzer.sh @@ -4,9 +4,9 @@ # Dependencies: lib/php-detector.sh, lib/system-detect.sh # Source required libraries -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; exit 1; } -source "$SCRIPT_DIR/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; exit 1; } +_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$_LIB_DIR/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; return 1; } +source "$_LIB_DIR/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; return 1; } # ============================================================================ # ERROR LOG ANALYSIS diff --git a/lib/php-detector.sh b/lib/php-detector.sh new file mode 100644 index 0000000..bac1d1e --- /dev/null +++ b/lib/php-detector.sh @@ -0,0 +1,428 @@ +#!/bin/bash + +################################################################################ +# PHP Detector Library +# Part of Server Toolkit - PHP & Server Optimizer +# +# Purpose: Detect all PHP configurations, pools, versions, and settings +# Author: Server Toolkit Team +# Dependencies: system-detect.sh, user-manager.sh +################################################################################ + +# Source dependencies (if not already loaded) +if [ -z "$SYS_CONTROL_PANEL" ]; then + _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + source "$_LIB_DIR/lib/system-detect.sh" 2>/dev/null + source "$_LIB_DIR/lib/user-manager.sh" 2>/dev/null +fi + +################################################################################ +# PHP Version Detection +################################################################################ + +# Detect all installed PHP versions on the system +detect_installed_php_versions() { + local -a php_versions=() + + # cPanel EA-PHP + if [ -d "/opt/cpanel" ]; then + while IFS= read -r dir; do + local version=$(basename "$dir" | sed 's/ea-php//') + php_versions+=("ea-php$version") + done < <(find /opt/cpanel -maxdepth 1 -type d -name "ea-php*" 2>/dev/null | sort) + fi + + # CloudLinux Alt-PHP + if [ -d "/opt/alt" ]; then + while IFS= read -r dir; do + local version=$(basename "$dir" | sed 's/php//') + php_versions+=("alt-php$version") + done < <(find /opt/alt -maxdepth 1 -type d -name "php*" 2>/dev/null | grep -E "php[0-9]" | sort) + fi + + # Plesk PHP + if [ -d "/opt/plesk/php" ]; then + while IFS= read -r dir; do + local version=$(basename "$dir") + php_versions+=("plesk-php$version") + done < <(find /opt/plesk/php -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort) + fi + + # System PHP + if command -v php &>/dev/null; then + local sys_version=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null) + [ -n "$sys_version" ] && php_versions+=("system-php$sys_version") + fi + + # Return array + printf '%s\n' "${php_versions[@]}" +} + +# Get PHP version for a specific domain/user +detect_php_version_for_domain() { + local domain="$1" + local username="$2" + + case "$SYS_CONTROL_PANEL" in + cpanel) + # Check userdata for PHP version + local userdata_file="/var/cpanel/userdata/$username/$domain" + if [ -f "$userdata_file" ]; then + local php_ver=$(grep "phpversion:" "$userdata_file" | awk '{print $2}' | tr -d "'\"") + echo "$php_ver" + return 0 + fi + + # Fallback: Check FPM pool config + local pool_config=$(find /opt/cpanel/ea-php*/root/etc/php-fpm.d/ -name "$username.conf" 2>/dev/null | head -1) + if [ -n "$pool_config" ]; then + local php_path=$(dirname "$(dirname "$(dirname "$pool_config")")") + basename "$php_path" + return 0 + fi + ;; + + plesk) + # Query Plesk database + if command -v plesk &>/dev/null; then + plesk bin site -i "$domain" 2>/dev/null | grep "PHP version" | awk '{print $NF}' + return 0 + fi + ;; + + interworx) + # Check SiteWorx config + local domain_conf="/home/$username/var/$domain/siteworx.conf" + if [ -f "$domain_conf" ]; then + grep "php_version" "$domain_conf" | cut -d'=' -f2 + return 0 + fi + ;; + esac + + # Fallback: Try to execute PHP as user + su -s /bin/bash "$username" -c "php -r 'echo PHP_MAJOR_VERSION.\".\".PHP_MINOR_VERSION;'" 2>/dev/null +} + +################################################################################ +# PHP Configuration File Detection +################################################################################ + +# Find ALL php.ini files affecting a domain (in priority order) +find_all_php_configs() { + local username="$1" + local domain="$2" + local php_version="${3:-}" # Optional: e.g., "82" or "8.2" or "ea-php82" + + declare -a config_files=() + + # Normalize PHP version format + local php_ver_short=$(echo "$php_version" | grep -oE '[0-9]+' | head -c2) + local php_ver_dot="${php_ver_short:0:1}.${php_ver_short:1}" + + # PRIORITY 1: Per-Directory .user.ini files + if [ -d "/home/$username" ]; then + while IFS= read -r file; do + config_files+=("P1|$file|.user.ini") + done < <(find "/home/$username" -name ".user.ini" -type f 2>/dev/null) + fi + + # Check Plesk domain root + if [ -d "/var/www/vhosts/$domain" ]; then + while IFS= read -r file; do + config_files+=("P1|$file|.user.ini") + done < <(find "/var/www/vhosts/$domain" -name ".user.ini" -type f 2>/dev/null) + fi + + # PRIORITY 2: User-Specific php.ini files + local user_configs=( + "/home/$username/public_html/php.ini" + "/home/$username/php.ini" + "/home/$username/.php/$php_ver_dot/php.ini" + "/home/$username/.php/$php_ver_short/php.ini" + "/home/$username/etc/php.ini" + "/home/$username/etc/php$php_ver_short/php.ini" + "/home/$username/var/$domain/etc/php.ini" # InterWorx + "/var/www/vhosts/system/$domain/etc/php.ini" # Plesk + ) + + for config in "${user_configs[@]}"; do + [ -f "$config" ] && config_files+=("P2|$config|user php.ini") + done + + # PRIORITY 3: Pool/Version-Specific php.ini + if [ -n "$php_ver_short" ]; then + # cPanel EA-PHP + local cpanel_ini="/opt/cpanel/ea-php${php_ver_short}/root/etc/php.ini" + [ -f "$cpanel_ini" ] && config_files+=("P3|$cpanel_ini|pool php.ini") + + # cPanel EA-PHP additional .ini files + if [ -d "/opt/cpanel/ea-php${php_ver_short}/root/etc/php.d" ]; then + while IFS= read -r file; do + config_files+=("P3|$file|pool php.d") + done < <(find "/opt/cpanel/ea-php${php_ver_short}/root/etc/php.d" -name "*.ini" -type f 2>/dev/null | sort) + fi + + # CloudLinux Alt-PHP + local alt_ini="/opt/alt/php${php_ver_short}/etc/php.ini" + [ -f "$alt_ini" ] && config_files+=("P3|$alt_ini|alt-php ini") + + # Plesk PHP + local plesk_ini="/opt/plesk/php/$php_ver_dot/etc/php.ini" + [ -f "$plesk_ini" ] && config_files+=("P3|$plesk_ini|plesk php.ini") + fi + + # PRIORITY 4: System-Wide + [ -f "/etc/php.ini" ] && config_files+=("P4|/etc/php.ini|system php.ini") + + # Return the array (priority|path|description) + printf '%s\n' "${config_files[@]}" +} + +# Get effective value of a PHP setting for a specific user +get_effective_php_setting() { + local username="$1" + local setting="$2" + + # Query PHP directly as the user (most accurate!) + su -s /bin/bash "$username" -c "php -r 'echo ini_get(\"$setting\");'" 2>/dev/null +} + +# Get all effective PHP settings for a user +get_all_php_settings() { + local username="$1" + + # Get all settings in parseable format + su -s /bin/bash "$username" -c "php -r 'foreach(ini_get_all() as \$k=>\$v) { echo \$k.\"=\".\$v[\"local_value\"].\"\\n\"; }'" 2>/dev/null +} + +################################################################################ +# PHP-FPM Pool Detection +################################################################################ + +# Find PHP-FPM pool configuration for a user/domain +find_fpm_pool_config() { + local username="$1" + local domain="$2" + local php_version="${3:-}" + + local pool_config="" + + # cPanel EA-PHP pools + if [ -n "$php_version" ]; then + pool_config="/opt/cpanel/$php_version/root/etc/php-fpm.d/$username.conf" + [ -f "$pool_config" ] && echo "$pool_config" && return 0 + fi + + # Search all EA-PHP versions + pool_config=$(find /opt/cpanel/ea-php*/root/etc/php-fpm.d/ -name "$username.conf" 2>/dev/null | head -1) + [ -n "$pool_config" ] && echo "$pool_config" && return 0 + + # Plesk pools + pool_config="/etc/php-fpm.d/plesk-php*-fpm/$domain.conf" + [ -f "$pool_config" ] && echo "$pool_config" && return 0 + + # InterWorx pools + pool_config="/home/$username/var/$domain/php-fpm.conf" + [ -f "$pool_config" ] && echo "$pool_config" && return 0 + + return 1 +} + +# Parse PHP-FPM pool config and extract all settings +parse_fpm_pool_config() { + local pool_config="$1" + + [ ! -f "$pool_config" ] && return 1 + + # Extract key settings + local pm=$(grep "^pm\s*=" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local max_children=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local start_servers=$(grep "^pm.start_servers" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local min_spare=$(grep "^pm.min_spare_servers" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local max_spare=$(grep "^pm.max_spare_servers" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local max_requests=$(grep "^pm.max_requests" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local idle_timeout=$(grep "^pm.process_idle_timeout" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local request_terminate=$(grep "^request_terminate_timeout" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + local slowlog_timeout=$(grep "^request_slowlog_timeout" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ') + + # Output in key=value format + echo "pm=$pm" + echo "pm.max_children=$max_children" + echo "pm.start_servers=$start_servers" + echo "pm.min_spare_servers=$min_spare" + echo "pm.max_spare_servers=$max_spare" + echo "pm.max_requests=$max_requests" + echo "pm.process_idle_timeout=$idle_timeout" + echo "request_terminate_timeout=$request_terminate" + echo "request_slowlog_timeout=$slowlog_timeout" +} + +# Get current FPM process count for a pool +get_fpm_process_count() { + local pool_name="$1" # Usually username or domain + + ps aux | grep -E "php-fpm.*pool\s+${pool_name}" | grep -v grep | wc -l +} + +# Get memory usage per FPM process for a pool +get_fpm_memory_usage() { + local pool_name="$1" + + # Get average memory per process (in KB) + ps aux | grep -E "php-fpm.*pool\s+${pool_name}" | grep -v grep | awk '{sum+=$6; count++} END {if(count>0) print int(sum/count); else print 0}' +} + +################################################################################ +# PHP Log File Detection +################################################################################ + +# Find PHP error logs for a user/domain +find_php_error_logs() { + local username="$1" + local domain="$2" + + declare -a log_files=() + + # Common error log locations + local possible_logs=( + "/home/$username/logs/error_log" + "/home/$username/public_html/error_log" + "/home/$username/logs/$domain.error_log" + "/var/www/vhosts/$domain/logs/error_log" + "/home/$username/var/$domain/logs/error_log" + ) + + for log in "${possible_logs[@]}"; do + [ -f "$log" ] && log_files+=("$log") + done + + printf '%s\n' "${log_files[@]}" +} + +# Find PHP-FPM error logs +find_fpm_error_logs() { + local username="$1" + local php_version="${2:-}" + + declare -a log_files=() + + if [ -n "$php_version" ]; then + # Specific version + log_files+=("/opt/cpanel/$php_version/root/usr/var/log/php-fpm/$username-error.log") + else + # All versions + while IFS= read -r log; do + log_files+=("$log") + done < <(find /opt/cpanel/ea-php*/root/usr/var/log/php-fpm/ -name "$username-error.log" 2>/dev/null) + fi + + printf '%s\n' "${log_files[@]}" +} + +# Find PHP-FPM slow logs +find_fpm_slow_logs() { + local username="$1" + local php_version="${2:-}" + + declare -a log_files=() + + if [ -n "$php_version" ]; then + log_files+=("/opt/cpanel/$php_version/root/usr/var/log/php-fpm/$username-slow.log") + else + while IFS= read -r log; do + log_files+=("$log") + done < <(find /opt/cpanel/ea-php*/root/usr/var/log/php-fpm/ -name "$username-slow.log" 2>/dev/null) + fi + + printf '%s\n' "${log_files[@]}" +} + +################################################################################ +# OPcache Detection +################################################################################ + +# Check if OPcache is enabled for a user +check_opcache_enabled() { + local username="$1" + + su -s /bin/bash "$username" -c "php -r 'echo (function_exists(\"opcache_get_status\") && opcache_get_status() !== false) ? \"1\" : \"0\";'" 2>/dev/null +} + +# Get OPcache statistics for a user +get_opcache_stats() { + local username="$1" + + su -s /bin/bash "$username" -c "php -r 'if(function_exists(\"opcache_get_status\")) { \$s=opcache_get_status(); echo \"enabled=\".(\$s!==false?1:0).\"\\n\"; if(\$s) { echo \"memory_used=\".\$s[\"memory_usage\"][\"used_memory\"].\"\\n\"; echo \"memory_free=\".\$s[\"memory_usage\"][\"free_memory\"].\"\\n\"; echo \"memory_wasted=\".\$s[\"memory_usage\"][\"wasted_memory\"].\"\\n\"; echo \"num_cached_scripts=\".\$s[\"opcache_statistics\"][\"num_cached_scripts\"].\"\\n\"; echo \"max_cached_scripts=\".\$s[\"opcache_statistics\"][\"max_cached_scripts\"].\"\\n\"; echo \"hits=\".\$s[\"opcache_statistics\"][\"hits\"].\"\\n\"; echo \"misses=\".\$s[\"opcache_statistics\"][\"misses\"].\"\\n\"; } }'" 2>/dev/null +} + +# Calculate OPcache hit rate +calculate_opcache_hit_rate() { + local username="$1" + + local stats=$(get_opcache_stats "$username") + [ -z "$stats" ] && echo "0" && return 1 + + local hits=$(echo "$stats" | grep "^hits=" | cut -d'=' -f2) + local misses=$(echo "$stats" | grep "^misses=" | cut -d'=' -f2) + + [ -z "$hits" ] || [ -z "$misses" ] && echo "0" && return 1 + [ "$hits" -eq 0 ] && [ "$misses" -eq 0 ] && echo "0" && return 1 + + local total=$((hits + misses)) + local hit_rate=$((hits * 100 / total)) + + echo "$hit_rate" +} + +################################################################################ +# Helper Functions +################################################################################ + +# Check if PHP-FPM is in use (vs mod_php) +is_using_php_fpm() { + # Check if any PHP-FPM processes are running + pgrep php-fpm &>/dev/null && return 0 + return 1 +} + +# Get PHP binary path for a specific version +get_php_binary_path() { + local php_version="$1" + + case "$php_version" in + ea-php*) + echo "/opt/cpanel/$php_version/root/usr/bin/php" + ;; + alt-php*) + local ver=$(echo "$php_version" | sed 's/alt-php//') + echo "/opt/alt/php$ver/usr/bin/php" + ;; + plesk-php*) + local ver=$(echo "$php_version" | sed 's/plesk-php//') + echo "/opt/plesk/php/$ver/bin/php" + ;; + *) + which php + ;; + esac +} + +# Export all functions +export -f detect_installed_php_versions +export -f detect_php_version_for_domain +export -f find_all_php_configs +export -f get_effective_php_setting +export -f get_all_php_settings +export -f find_fpm_pool_config +export -f parse_fpm_pool_config +export -f get_fpm_process_count +export -f get_fpm_memory_usage +export -f find_php_error_logs +export -f find_fpm_error_logs +export -f find_fpm_slow_logs +export -f check_opcache_enabled +export -f get_opcache_stats +export -f calculate_opcache_hit_rate +export -f is_using_php_fpm +export -f get_php_binary_path