Fix critical bugs and add domain-by-domain batch analyzer
- Fix line 63 in php-analyzer.sh: Add default value for count variable (integer comparison error) - Fix line 655 in php-analyzer.sh: Add default value for memory_error_count (integer comparison error) - Fix line 396 in php-scanner.sh: Replace unsafe eval with safe getent passwd lookup - Add php-ui.sh: User interface and menu system (18KB, 25+ functions) - Add php-scanner.sh: Server enumeration system (17KB, 18 functions) - Add php-action-executor.sh: Optimization execution system (17KB, 20 functions) - Add php-server-manager.sh: Orchestration framework (21KB, 7 functions) - Add php-fpm-batch-analyzer.sh: One-shot diagnostic script showing current vs recommended max_children, memory impact, and optimization potential - Add comprehensive test suite (24 tests) These fixes resolve "integer expression expected" errors during domain analysis. Batch analyzer enables users to see domain-by-domain optimization opportunities before applying changes. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+554
@@ -0,0 +1,554 @@
|
||||
#!/bin/bash
|
||||
# PHP-FPM Server Scanner Module
|
||||
# Handles enumeration of accounts/domains across entire server with filtering
|
||||
# Part of PHP Optimizer - Phase 3 Refactoring
|
||||
# Ensures full server-wide scanning and action capability
|
||||
|
||||
# ============================================================================
|
||||
# ACCOUNT ENUMERATION FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Enumerate all accounts/users on the server
|
||||
enumerate_all_accounts() {
|
||||
local force_refresh="${1:-false}"
|
||||
local cache_file="/tmp/php-scanner-accounts-cache-$$"
|
||||
|
||||
# Return cached results if available (unless force_refresh=true)
|
||||
if [ "$force_refresh" != "true" ] && [ -f "$cache_file" ]; then
|
||||
cat "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Delegate to user-manager.sh if available
|
||||
if type list_all_users >/dev/null 2>&1; then
|
||||
local accounts
|
||||
accounts=$(list_all_users)
|
||||
if [ -n "$accounts" ]; then
|
||||
echo "$accounts" | tee "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback enumeration if user-manager.sh not available
|
||||
case "${SYS_CONTROL_PANEL:-unknown}" in
|
||||
cpanel)
|
||||
_enumerate_cpanel_accounts | tee "$cache_file"
|
||||
;;
|
||||
plesk)
|
||||
_enumerate_plesk_accounts | tee "$cache_file"
|
||||
;;
|
||||
interworx)
|
||||
_enumerate_interworx_accounts | tee "$cache_file"
|
||||
;;
|
||||
*)
|
||||
_enumerate_system_accounts | tee "$cache_file"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# cPanel account enumeration
|
||||
_enumerate_cpanel_accounts() {
|
||||
local cpanel_users_dir="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}"
|
||||
if [ -d "$cpanel_users_dir" ]; then
|
||||
ls "$cpanel_users_dir" 2>/dev/null | grep -v "^system\|^root\|^\." || true
|
||||
else
|
||||
awk -F: '{print $2}' /etc/trueuserdomains 2>/dev/null | sort -u || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Plesk account enumeration
|
||||
_enumerate_plesk_accounts() {
|
||||
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
|
||||
mysql -Ns psa -e "SELECT login FROM sys_users WHERE type='user'" 2>/dev/null || true
|
||||
else
|
||||
find /var/www/vhosts -maxdepth 1 -type d -printf "%f\n" 2>/dev/null | \
|
||||
grep -v "^system$\|^default$\|^chroot$\|^\.skel$\|^fs$\|^fs-passwd$\|^\." || true
|
||||
fi
|
||||
}
|
||||
|
||||
# InterWorx account enumeration
|
||||
_enumerate_interworx_accounts() {
|
||||
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
|
||||
/usr/local/interworx/bin/listaccounts.pex --output user 2>/dev/null || true
|
||||
else
|
||||
if [ -d "/etc/httpd/conf.d" ]; then
|
||||
grep -h "^[[:space:]]*SuexecUserGroup" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
|
||||
awk '{print $2}' | sort -u || true
|
||||
else
|
||||
find /home -maxdepth 1 -type d ! -name "home" ! -name "interworx" -printf "%f\n" 2>/dev/null | sort
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# System-wide account enumeration (fallback)
|
||||
_enumerate_system_accounts() {
|
||||
awk -F: '($3 >= 500) && ($3 != 65534) {print $1}' /etc/passwd 2>/dev/null | \
|
||||
grep -v "^root\|^nobody\|^ntp\|^mysql\|^www-data\|^apache\|^nginx" | \
|
||||
sort -u || true
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# DOMAIN ENUMERATION FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Enumerate all domains for a specific user/account
|
||||
enumerate_user_domains() {
|
||||
[ -z "$1" ] && return 1
|
||||
local username="$1"
|
||||
local force_refresh="${2:-false}"
|
||||
local cache_file="/tmp/php-scanner-domains-${username}-cache-$$"
|
||||
|
||||
# Return cached results if available (unless force_refresh=true)
|
||||
if [ "$force_refresh" != "true" ] && [ -f "$cache_file" ]; then
|
||||
cat "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Delegate to user-manager.sh if available
|
||||
if type get_user_domains >/dev/null 2>&1; then
|
||||
local domains
|
||||
domains=$(get_user_domains "$username")
|
||||
if [ -n "$domains" ]; then
|
||||
echo "$domains" | tee "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback domain enumeration
|
||||
case "${SYS_CONTROL_PANEL:-unknown}" in
|
||||
cpanel)
|
||||
_enumerate_cpanel_domains "$username" | tee "$cache_file"
|
||||
;;
|
||||
plesk)
|
||||
_enumerate_plesk_domains "$username" | tee "$cache_file"
|
||||
;;
|
||||
interworx)
|
||||
_enumerate_interworx_domains "$username" | tee "$cache_file"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# cPanel domain enumeration
|
||||
_enumerate_cpanel_domains() {
|
||||
local username="$1"
|
||||
[ -z "$username" ] && return 1
|
||||
|
||||
# Primary domain
|
||||
grep ": ${username}$" /etc/trueuserdomains 2>/dev/null | cut -d: -f1 || true
|
||||
|
||||
# Addon domains
|
||||
if [ -f "/etc/userdatadomains" ]; then
|
||||
grep "==${username}$" /etc/userdatadomains 2>/dev/null | cut -d: -f1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Plesk domain enumeration
|
||||
_enumerate_plesk_domains() {
|
||||
local username="$1"
|
||||
[ -z "$username" ] && return 1
|
||||
|
||||
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
|
||||
mysql -Ns psa -e "SELECT d.name FROM domains d JOIN sys_users u ON d.id=u.domain_id WHERE u.login='$username'" 2>/dev/null || true
|
||||
elif [ -x "/usr/local/psa/bin/plesk" ]; then
|
||||
/usr/local/psa/bin/plesk bin site --list 2>/dev/null | grep -i "$username" || true
|
||||
elif [ -d "/var/www/vhosts/$username" ]; then
|
||||
echo "$username"
|
||||
fi
|
||||
}
|
||||
|
||||
# InterWorx domain enumeration
|
||||
_enumerate_interworx_domains() {
|
||||
local username="$1"
|
||||
[ -z "$username" ] && return 1
|
||||
|
||||
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
|
||||
/usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \
|
||||
awk -v user="$username" '$1 == user {print $2}'
|
||||
fi
|
||||
|
||||
if [ -d "/etc/httpd/conf.d" ]; then
|
||||
grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
|
||||
sed 's|.*/vhost_||; s|\.conf$||' | \
|
||||
grep -vF "${username}." 2>/dev/null | \
|
||||
sort -u
|
||||
fi
|
||||
}
|
||||
|
||||
# Enumerate ALL domains on the server (across all users)
|
||||
enumerate_all_domains() {
|
||||
local force_refresh="${1:-false}"
|
||||
local cache_file="/tmp/php-scanner-all-domains-cache-$$"
|
||||
local progress_file="/tmp/php-scanner-progress-$$"
|
||||
|
||||
# Return cached results if available (unless force_refresh=true)
|
||||
if [ "$force_refresh" != "true" ] && [ -f "$cache_file" ]; then
|
||||
cat "$cache_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
> "$progress_file" # Clear progress file
|
||||
local users
|
||||
local domain_list=""
|
||||
local user_count=0
|
||||
local current_user=0
|
||||
|
||||
users=$(enumerate_all_accounts)
|
||||
user_count=$(echo "$users" | wc -l)
|
||||
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
|
||||
current_user=$((current_user + 1))
|
||||
echo "$current_user/$user_count: $username" >> "$progress_file"
|
||||
|
||||
local domains
|
||||
domains=$(enumerate_user_domains "$username")
|
||||
if [ -n "$domains" ]; then
|
||||
domain_list="${domain_list}${domains}"$'\n'
|
||||
fi
|
||||
done <<< "$users"
|
||||
|
||||
# Deduplicate and sort
|
||||
echo "$domain_list" | sort -u | grep -v "^$" | tee "$cache_file"
|
||||
|
||||
rm -f "$progress_file"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# FILTERING FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Filter accounts by name pattern
|
||||
filter_accounts_by_name() {
|
||||
local pattern="$1"
|
||||
[ -z "$pattern" ] && return 1
|
||||
|
||||
local all_accounts
|
||||
all_accounts=$(enumerate_all_accounts)
|
||||
|
||||
echo "$all_accounts" | grep -i "$pattern" || true
|
||||
}
|
||||
|
||||
# Filter accounts by resource usage threshold
|
||||
filter_accounts_by_threshold() {
|
||||
local threshold_mb="${1:-1000}"
|
||||
local direction="${2:-above}" # above or below
|
||||
|
||||
local all_accounts
|
||||
all_accounts=$(enumerate_all_accounts)
|
||||
|
||||
local filtered=""
|
||||
while IFS= read -r username; do
|
||||
[ -z "$username" ] && continue
|
||||
|
||||
local usage_mb
|
||||
usage_mb=$(get_account_disk_usage "$username")
|
||||
|
||||
if [ "$direction" = "above" ] && [ "$usage_mb" -gt "$threshold_mb" ]; then
|
||||
filtered="${filtered}${username}"$'\n'
|
||||
elif [ "$direction" = "below" ] && [ "$usage_mb" -lt "$threshold_mb" ]; then
|
||||
filtered="${filtered}${username}"$'\n'
|
||||
fi
|
||||
done <<< "$all_accounts"
|
||||
|
||||
echo "$filtered" | grep -v "^$"
|
||||
}
|
||||
|
||||
# Filter domains by name pattern
|
||||
filter_domains_by_name() {
|
||||
local pattern="$1"
|
||||
[ -z "$pattern" ] && return 1
|
||||
|
||||
local all_domains
|
||||
all_domains=$(enumerate_all_domains)
|
||||
|
||||
echo "$all_domains" | grep -i "$pattern" || true
|
||||
}
|
||||
|
||||
# Filter domains by traffic level
|
||||
filter_domains_by_traffic() {
|
||||
local min_requests="${1:-100}" # Minimum requests per second
|
||||
local direction="${2:-above}" # above or below
|
||||
|
||||
local all_domains
|
||||
all_domains=$(enumerate_all_domains)
|
||||
|
||||
local filtered=""
|
||||
while IFS= read -r domain; do
|
||||
[ -z "$domain" ] && continue
|
||||
|
||||
local peak_concurrent
|
||||
peak_concurrent=$(get_domain_peak_concurrent "$domain")
|
||||
|
||||
if [ "$direction" = "above" ] && [ "$peak_concurrent" -gt "$min_requests" ]; then
|
||||
filtered="${filtered}${domain}"$'\n'
|
||||
elif [ "$direction" = "below" ] && [ "$peak_concurrent" -lt "$min_requests" ]; then
|
||||
filtered="${filtered}${domain}"$'\n'
|
||||
fi
|
||||
done <<< "$all_domains"
|
||||
|
||||
echo "$filtered" | grep -v "^$"
|
||||
}
|
||||
|
||||
# Filter domains by optimization status
|
||||
filter_domains_by_optimization_status() {
|
||||
local status="${1:-needs_optimization}" # needs_optimization or already_optimized
|
||||
|
||||
local all_domains
|
||||
all_domains=$(enumerate_all_domains)
|
||||
|
||||
local filtered=""
|
||||
while IFS= read -r domain; do
|
||||
[ -z "$domain" ] && continue
|
||||
|
||||
local is_optimized
|
||||
is_optimized=$(is_domain_optimized "$domain")
|
||||
|
||||
if [ "$status" = "needs_optimization" ] && [ "$is_optimized" = "0" ]; then
|
||||
filtered="${filtered}${domain}"$'\n'
|
||||
elif [ "$status" = "already_optimized" ] && [ "$is_optimized" = "1" ]; then
|
||||
filtered="${filtered}${domain}"$'\n'
|
||||
fi
|
||||
done <<< "$all_domains"
|
||||
|
||||
echo "$filtered" | grep -v "^$"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# DOMAIN INFORMATION FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Get comprehensive PHP-FPM information for a domain
|
||||
get_domain_php_info() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
local owner username pool_name pool_path
|
||||
|
||||
# Find domain owner
|
||||
owner=$(find_domain_owner "$domain")
|
||||
[ -z "$owner" ] && return 1
|
||||
|
||||
# Find PHP pool
|
||||
pool_name=$(php_detector_get_pool_name "$domain")
|
||||
pool_path=$(php_detector_get_pool_config "$domain")
|
||||
|
||||
# Return info in structured format
|
||||
cat << EOF
|
||||
domain=$domain
|
||||
owner=$owner
|
||||
pool_name=$pool_name
|
||||
pool_path=$pool_path
|
||||
EOF
|
||||
}
|
||||
|
||||
# Get disk usage for an account
|
||||
get_account_disk_usage() {
|
||||
local username="$1"
|
||||
[ -z "$username" ] && return 1
|
||||
|
||||
case "${SYS_CONTROL_PANEL:-unknown}" in
|
||||
cpanel)
|
||||
_get_cpanel_account_usage "$username"
|
||||
;;
|
||||
plesk)
|
||||
_get_plesk_account_usage "$username"
|
||||
;;
|
||||
interworx)
|
||||
_get_interworx_account_usage "$username"
|
||||
;;
|
||||
*)
|
||||
_get_system_account_usage "$username"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_get_cpanel_account_usage() {
|
||||
local username="$1"
|
||||
local home="/home/$username"
|
||||
if [ -d "$home" ]; then
|
||||
du -sb "$home" 2>/dev/null | awk '{printf "%.0f", $1/1048576}'
|
||||
fi
|
||||
}
|
||||
|
||||
_get_plesk_account_usage() {
|
||||
local username="$1"
|
||||
local vhost_path="/var/www/vhosts/$username"
|
||||
if [ -d "$vhost_path" ]; then
|
||||
du -sb "$vhost_path" 2>/dev/null | awk '{printf "%.0f", $1/1048576}'
|
||||
fi
|
||||
}
|
||||
|
||||
_get_interworx_account_usage() {
|
||||
local username="$1"
|
||||
local home="/home/$username"
|
||||
if [ -d "$home" ]; then
|
||||
du -sb "$home" 2>/dev/null | awk '{printf "%.0f", $1/1048576}'
|
||||
fi
|
||||
}
|
||||
|
||||
_get_system_account_usage() {
|
||||
local username="$1"
|
||||
local home
|
||||
home=$(getent passwd "$username" | cut -d: -f6)
|
||||
if [ -n "$home" ] && [ -d "$home" ]; then
|
||||
du -sb "$home" 2>/dev/null | awk '{printf "%.0f", $1/1048576}'
|
||||
fi
|
||||
}
|
||||
|
||||
# Get peak concurrent requests for a domain
|
||||
get_domain_peak_concurrent() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
local log_file
|
||||
log_file=$(find_domain_access_log "$domain")
|
||||
|
||||
if [ -z "$log_file" ] || [ ! -f "$log_file" ]; then
|
||||
echo "0"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Analyze access log for peak concurrent requests (simplified)
|
||||
tail -100000 "$log_file" 2>/dev/null | \
|
||||
awk '{print $4}' | \
|
||||
sed 's/\[//' | \
|
||||
awk -F: '{print $3}' | \
|
||||
sort | uniq -c | \
|
||||
sort -rn | head -1 | \
|
||||
awk '{print $1}' || echo "0"
|
||||
}
|
||||
|
||||
# Check if a domain is already optimized
|
||||
is_domain_optimized() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
# Check if pool has been recently optimized (within last 7 days)
|
||||
local pool_path
|
||||
pool_path=$(php_detector_get_pool_config "$domain")
|
||||
|
||||
if [ -z "$pool_path" ] || [ ! -f "$pool_path" ]; then
|
||||
echo "0"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if pm.max_children is set to something other than default (40)
|
||||
local current_max
|
||||
current_max=$(grep -oP 'pm\.max_children\s*=\s*\K\d+' "$pool_path" 2>/dev/null || echo "40")
|
||||
|
||||
if [ "$current_max" != "40" ]; then
|
||||
echo "1"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
# Find which user owns a domain
|
||||
find_domain_owner() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
case "${SYS_CONTROL_PANEL:-unknown}" in
|
||||
cpanel)
|
||||
grep "^${domain}:" /etc/trueuserdomains 2>/dev/null | cut -d: -f2
|
||||
;;
|
||||
plesk)
|
||||
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
|
||||
mysql -Ns psa -e "SELECT u.login FROM domains d JOIN sys_users u ON d.id=u.domain_id WHERE d.name='$domain' LIMIT 1" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
interworx)
|
||||
grep -l "^${domain}$" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
|
||||
xargs grep "SuexecUserGroup" 2>/dev/null | \
|
||||
head -1 | awk '{print $2}'
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Find access log for a domain
|
||||
find_domain_access_log() {
|
||||
local domain="$1"
|
||||
[ -z "$domain" ] && return 1
|
||||
|
||||
case "${SYS_CONTROL_PANEL:-unknown}" in
|
||||
cpanel)
|
||||
local owner
|
||||
owner=$(find_domain_owner "$domain")
|
||||
if [ -n "$owner" ]; then
|
||||
find "/home/${owner}/public_html" -maxdepth 2 -name "access_log*" -type f 2>/dev/null | head -1
|
||||
fi
|
||||
;;
|
||||
plesk)
|
||||
find "/var/www/vhosts/${domain}/statistics/logs" -name "access_log*" -type f 2>/dev/null | head -1
|
||||
;;
|
||||
interworx)
|
||||
find "/home/*/public_html/${domain}" -name "access_log*" -type f 2>/dev/null | head -1
|
||||
;;
|
||||
*)
|
||||
find /var/log -name "*${domain}*access*log*" -type f 2>/dev/null | head -1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Get count of total accounts
|
||||
get_total_account_count() {
|
||||
enumerate_all_accounts | wc -l
|
||||
}
|
||||
|
||||
# Get count of total domains
|
||||
get_total_domain_count() {
|
||||
enumerate_all_domains | wc -l
|
||||
}
|
||||
|
||||
# Clear enumeration cache
|
||||
clear_enumeration_cache() {
|
||||
rm -f /tmp/php-scanner-*-cache-* 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Display enumeration progress (for use in larger operations)
|
||||
show_enumeration_progress() {
|
||||
local current="$1"
|
||||
local total="$2"
|
||||
|
||||
if [ -z "$total" ] || [ "$total" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local percent=$((current * 100 / total))
|
||||
local filled=$((percent / 5))
|
||||
local empty=$((20 - filled))
|
||||
|
||||
printf "Progress: [%-20s] %3d%% (%d/%d)\r" \
|
||||
"$(printf '#%.0s' $(seq 1 $filled))$(printf ' %.0s' $(seq 1 $empty))" \
|
||||
"$percent" "$current" "$total"
|
||||
}
|
||||
|
||||
export -f enumerate_all_accounts
|
||||
export -f enumerate_user_domains
|
||||
export -f enumerate_all_domains
|
||||
export -f filter_accounts_by_name
|
||||
export -f filter_accounts_by_threshold
|
||||
export -f filter_domains_by_name
|
||||
export -f filter_domains_by_traffic
|
||||
export -f filter_domains_by_optimization_status
|
||||
export -f get_domain_php_info
|
||||
export -f get_account_disk_usage
|
||||
export -f get_domain_peak_concurrent
|
||||
export -f is_domain_optimized
|
||||
export -f find_domain_owner
|
||||
export -f find_domain_access_log
|
||||
export -f get_total_account_count
|
||||
export -f get_total_domain_count
|
||||
export -f clear_enumeration_cache
|
||||
export -f show_enumeration_progress
|
||||
Reference in New Issue
Block a user