Files
cschantz 13d7054aa1 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>
2026-02-17 22:43:49 -05:00

542 lines
21 KiB
Bash
Executable File

#!/bin/bash
# PHP-FPM Server Manager Module
# Orchestrates large-scale server operations: scanning, planning, executing, reporting
# Part of PHP Optimizer - Phase 3 Refactoring
# ============================================================================
# SERVER SCANNING & INVENTORY
# ============================================================================
# Scan entire server and collect comprehensive information
scan_entire_server() {
local filter_mode="${1:-all}" # all, user, pattern, traffic, needs_optimization
local filter_arg="${2:-}"
init_change_tracking
local -a domains_to_analyze
case "$filter_mode" in
all)
mapfile -t domains_to_analyze < <(enumerate_all_domains)
;;
user)
[ -z "$filter_arg" ] && return 1
mapfile -t domains_to_analyze < <(enumerate_user_domains "$filter_arg")
;;
pattern)
[ -z "$filter_arg" ] && return 1
mapfile -t domains_to_analyze < <(filter_domains_by_name "$filter_arg")
;;
traffic)
[ -z "$filter_arg" ] && filter_arg="100"
mapfile -t domains_to_analyze < <(filter_domains_by_traffic "$filter_arg" "above")
;;
needs_optimization)
mapfile -t domains_to_analyze < <(filter_domains_by_optimization_status "needs_optimization")
;;
*)
return 1
;;
esac
local total_domains=${#domains_to_analyze[@]}
local current=0
local -A scan_results
if [ "$total_domains" -eq 0 ]; then
return 0
fi
for domain in "${domains_to_analyze[@]}"; do
[ -z "$domain" ] && continue
current=$((current + 1))
show_enumeration_progress "$current" "$total_domains"
# Collect domain info
local owner
owner=$(find_domain_owner "$domain")
local issues
issues=$(detect_php_config_issues "$owner" "$domain" 2>/dev/null || echo "")
local issue_count
issue_count=$(echo "$issues" | grep -c "^" || echo "0")
scan_results["$domain"]="$owner|$issue_count|$issues"
done
echo ""
# Output results in scannable format
for domain in "${!scan_results[@]}"; do
echo "DOMAIN|$domain|${scan_results[$domain]}"
done
return 0
}
# Analyze entire server for optimization opportunities
analyze_entire_server() {
local -a all_domains
mapfile -t all_domains < <(enumerate_all_domains)
local total_domains=${#all_domains[@]}
local domains_with_issues=0
local critical_count=0
local high_count=0
local medium_count=0
local low_count=0
local current=0
for domain in "${all_domains[@]}"; do
[ -z "$domain" ] && continue
current=$((current + 1))
display_progress "$current" "$total_domains" "Analyzing"
local owner
owner=$(find_domain_owner "$domain")
if [ -z "$owner" ]; then
continue
fi
# Detect issues
local issues
issues=$(detect_php_config_issues "$owner" "$domain" 2>/dev/null)
# Count issues by severity
local c_count h_count m_count l_count
c_count=$(echo "$issues" | grep -c "^[^|]*|CRITICAL|" || echo "0")
h_count=$(echo "$issues" | grep -c "^[^|]*|HIGH|" || echo "0")
m_count=$(echo "$issues" | grep -c "^[^|]*|MEDIUM|" || echo "0")
l_count=$(echo "$issues" | grep -c "^[^|]*|LOW|" || echo "0")
if [ $((c_count + h_count + m_count + l_count)) -gt 0 ]; then
domains_with_issues=$((domains_with_issues + 1))
critical_count=$((critical_count + c_count))
high_count=$((high_count + h_count))
medium_count=$((medium_count + m_count))
low_count=$((low_count + l_count))
fi
done
echo ""
echo "$total_domains|$domains_with_issues|$critical_count|$high_count|$medium_count|$low_count"
}
# ============================================================================
# OPTIMIZATION PLANNING
# ============================================================================
# Plan optimizations for entire server
plan_server_optimizations() {
local filter_mode="${1:-needs_optimization}"
local filter_arg="${2:-}"
local dry_run="${3:-true}"
local -a domains_to_optimize
mapfile -t domains_to_optimize < <(scan_entire_server "$filter_mode" "$filter_arg")
local total_domains=0
local optimization_count=0
# Parse scan results and identify optimization opportunities
declare -A optimization_plan
while IFS='|' read -r type domain owner issue_count rest; do
[ "$type" != "DOMAIN" ] && continue
[ -z "$domain" ] && continue
total_domains=$((total_domains + 1))
if [ "$issue_count" -gt 0 ]; then
optimization_count=$((optimization_count + 1))
optimization_plan["$domain"]="$owner|$issue_count"
fi
done <<< "$(echo "${domains_to_optimize[@]}" | tr ' ' '\n')"
# Generate plan summary
echo "OPTIMIZATION_PLAN"
echo "Total domains: $total_domains"
echo "Domains needing optimization: $optimization_count"
echo ""
# List domains to be optimized
for domain in "${!optimization_plan[@]}"; do
local owner issue_count
owner=$(echo "${optimization_plan[$domain]}" | cut -d'|' -f1)
issue_count=$(echo "${optimization_plan[$domain]}" | cut -d'|' -f2)
echo " - $domain (owner: $owner, $issue_count issues)"
done
return 0
}
# ============================================================================
# OPTIMIZATION EXECUTION
# ============================================================================
# Execute planned optimizations across server
execute_server_optimization_plan() {
local -a domains=("$@")
local dry_run="${DRY_RUN:-false}"
local require_confirmation="${REQUIRE_CONFIRMATION:-true}"
if [ ${#domains[@]} -eq 0 ]; then
return 1
fi
# Show summary before executing
local total=${#domains[@]}
echo ""
echo "Server Optimization Summary:"
echo " Total domains to optimize: $total"
echo " Dry-run mode: $dry_run"
echo ""
if [ "$require_confirmation" = "true" ]; then
if ! confirm "Execute optimizations for $total domain(s)?"; then
return 1
fi
fi
init_change_tracking
local successful=0
local failed=0
local current=0
for domain in "${domains[@]}"; do
[ -z "$domain" ] && continue
current=$((current + 1))
display_progress "$current" "$total" "Optimizing"
local owner
owner=$(find_domain_owner "$domain")
if [ -z "$owner" ]; then
failed=$((failed + 1))
log_change "$domain" "server_optimization" "unknown_owner" "skipped" "failed"
continue
fi
# Apply optimizations
if apply_optimization "$domain" "$owner" "all" "$dry_run"; then
successful=$((successful + 1))
else
failed=$((failed + 1))
fi
done
echo ""
echo "Optimization Results:"
echo " Successful: $successful"
echo " Failed: $failed"
echo " Total: $((successful + failed))"
# Reload PHP-FPM once for all changes
if [ "$dry_run" != "true" ] && [ "$successful" -gt 0 ]; then
echo "Reloading PHP-FPM to apply changes..."
reload_php_fpm
fi
return $((failed > 0 ? 1 : 0))
}
# ============================================================================
# REPORTING
# ============================================================================
# Generate comprehensive server analysis report
generate_server_report() {
local report_file="${1:-/tmp/php-optimizer-server-report-$(date +%Y%m%d-%H%M%S).txt}"
local filter_mode="${2:-all}"
local filter_arg="${3:-}"
{
echo "╔════════════════════════════════════════════════════════════════════════╗"
echo "║ PHP-FPM SERVER ANALYSIS REPORT ║"
echo "╚════════════════════════════════════════════════════════════════════════╝"
echo ""
echo "Generated: $(date)"
echo ""
# Server Information
echo "═══════════════════════════════════════════════════════════════════════════"
echo "SERVER INFORMATION"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
echo "Total RAM: $(free -h | awk '/^Mem:/ {print $2}')"
echo "CPU Cores: $(nproc)"
echo "Total Accounts: $(get_total_account_count)"
echo "Total Domains: $(get_total_domain_count)"
echo ""
# Analysis Results
echo "═══════════════════════════════════════════════════════════════════════════"
echo "ANALYSIS RESULTS"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
local analysis_result
analysis_result=$(analyze_entire_server)
local total_domains domains_with_issues critical high medium low
total_domains=$(echo "$analysis_result" | cut -d'|' -f1)
domains_with_issues=$(echo "$analysis_result" | cut -d'|' -f2)
critical=$(echo "$analysis_result" | cut -d'|' -f3)
high=$(echo "$analysis_result" | cut -d'|' -f4)
medium=$(echo "$analysis_result" | cut -d'|' -f5)
low=$(echo "$analysis_result" | cut -d'|' -f6)
echo "Total Domains Analyzed: $total_domains"
echo "Domains with Issues: $domains_with_issues"
echo ""
echo "Issue Summary:"
echo " CRITICAL: $critical"
echo " HIGH: $high"
echo " MEDIUM: $medium"
echo " LOW: $low"
echo ""
# Health Status
echo "═══════════════════════════════════════════════════════════════════════════"
echo "SERVER HEALTH STATUS"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
local capacity_result
capacity_result=$(calculate_server_memory_capacity 2>/dev/null)
local total_required_mb total_ram_mb percentage status
total_required_mb=$(echo "$capacity_result" | head -1 | cut -d'|' -f1)
total_ram_mb=$(echo "$capacity_result" | head -1 | cut -d'|' -f2)
percentage=$(echo "$capacity_result" | head -1 | cut -d'|' -f3)
status=$(echo "$capacity_result" | head -1 | cut -d'|' -f4)
echo "Total Server RAM: ${total_ram_mb}MB"
echo "Current FPM Capacity: ${total_required_mb}MB (${percentage}% of RAM)"
echo "Server Status: $status"
echo ""
# Recommendations
echo "═══════════════════════════════════════════════════════════════════════════"
echo "RECOMMENDATIONS"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
if [ "$domains_with_issues" -gt 0 ]; then
echo "1. Apply recommended optimizations to $domains_with_issues domain(s)"
if [ "$critical" -gt 0 ]; then
echo " - URGENT: Address $critical CRITICAL issue(s)"
fi
if [ "$high" -gt 0 ]; then
echo " - HIGH PRIORITY: Address $high HIGH severity issue(s)"
fi
else
echo "No issues detected - server configuration is optimal"
fi
case "$status" in
CRITICAL)
echo "2. URGENT: Review memory allocation - server at OOM risk!"
;;
WARNING)
echo "2. Review memory allocation - consider reducing max_children"
;;
CAUTION)
echo "2. Monitor memory usage - consider minor adjustments"
;;
HEALTHY)
echo "2. Continue monitoring - no immediate action needed"
;;
esac
echo ""
# Change History (if available)
if [ -n "$EXECUTOR_CHANGE_LOG" ] && [ -f "$EXECUTOR_CHANGE_LOG" ]; then
echo "═══════════════════════════════════════════════════════════════════════════"
echo "RECENT CHANGES"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
tail -20 "$EXECUTOR_CHANGE_LOG"
echo ""
fi
# Footer
echo "═══════════════════════════════════════════════════════════════════════════"
echo "Report generated by PHP-FPM Optimizer - Phase 3"
echo "═══════════════════════════════════════════════════════════════════════════"
} | tee "$report_file"
echo ""
echo "Report saved to: $report_file"
}
# Generate domain-specific report
generate_domain_report() {
local domain="$1"
local report_file="${2:-/tmp/php-optimizer-${domain}-report-$(date +%Y%m%d-%H%M%S).txt}"
local owner
owner=$(find_domain_owner "$domain")
if [ -z "$owner" ]; then
return 1
fi
{
echo "╔════════════════════════════════════════════════════════════════════════╗"
echo "║ PHP-FPM DOMAIN ANALYSIS REPORT ║"
echo "╚════════════════════════════════════════════════════════════════════════╝"
echo ""
echo "Domain: $domain"
echo "Owner: $owner"
echo "Generated: $(date)"
echo ""
# Domain Information
echo "═══════════════════════════════════════════════════════════════════════════"
echo "DOMAIN INFORMATION"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
local pool_config
pool_config=$(find_fpm_pool_config "$owner" "$domain" 2>/dev/null)
if [ -n "$pool_config" ]; then
echo "Pool Config: $pool_config"
echo ""
echo "Current Settings:"
grep "^pm" "$pool_config" | sed 's/^/ /'
echo ""
fi
# Analysis
echo "═══════════════════════════════════════════════════════════════════════════"
echo "ANALYSIS"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
local issues
issues=$(detect_php_config_issues "$owner" "$domain" 2>/dev/null)
if [ -z "$issues" ] || [ "$(echo "$issues" | wc -l)" -eq 0 ]; then
echo "No issues detected - configuration is optimal"
else
echo "Issues Found:"
echo ""
while IFS='|' read -r issue_type severity message recommendation; do
[ -z "$issue_type" ] && continue
echo "[$severity] $message"
echo "$recommendation"
echo ""
done <<< "$issues"
fi
# Recommendations
echo "═══════════════════════════════════════════════════════════════════════════"
echo "RECOMMENDATIONS"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
local total_ram_mb
total_ram_mb=$(free -m | awk '/^Mem:/ {print $2}')
local improved_result
improved_result=$(calculate_optimal_php_settings "$owner" "$total_ram_mb" 2>/dev/null)
if [ -n "$improved_result" ]; then
local improved_max_children improved_pm_mode improved_reason
improved_max_children=$(echo "$improved_result" | cut -d'|' -f1)
improved_pm_mode=$(echo "$improved_result" | cut -d'|' -f2)
improved_reason=$(echo "$improved_result" | cut -d'|' -f5)
echo "Recommended pm.max_children: $improved_max_children"
echo "Recommended pm mode: $improved_pm_mode"
echo "Reason: $improved_reason"
fi
echo ""
} | tee "$report_file"
echo "Report saved to: $report_file"
}
# ============================================================================
# BATCH OPERATIONS
# ============================================================================
# Perform batch operation on multiple domains
batch_operation() {
local operation="$1" # optimize, analyze, health_check
local filter_mode="${2:-needs_optimization}"
local filter_arg="${3:-}"
local require_confirmation="${4:-true}"
local -a target_domains
mapfile -t target_domains < <(scan_entire_server "$filter_mode" "$filter_arg")
case "$operation" in
optimize)
echo "Planning server-wide optimization..."
plan_server_optimizations "$filter_mode" "$filter_arg"
if [ "$require_confirmation" = "true" ]; then
if ! confirm "Execute optimizations?"; then
return 1
fi
fi
execute_server_optimization_plan "${target_domains[@]}"
;;
analyze)
echo "Analyzing entire server..."
analyze_entire_server
;;
health_check)
echo "Performing health check on all domains..."
init_change_tracking
local total=${#target_domains[@]}
local current=0
for domain in "${target_domains[@]}"; do
[ -z "$domain" ] && continue
current=$((current + 1))
display_progress "$current" "$total"
local owner
owner=$(find_domain_owner "$domain")
[ -n "$owner" ] && perform_health_check "$owner" "$domain" >/dev/null 2>&1
done
echo ""
;;
esac
return $?
}
# ============================================================================
# EXPORT ALL FUNCTIONS
# ============================================================================
export -f scan_entire_server
export -f analyze_entire_server
export -f plan_server_optimizations
export -f execute_server_optimization_plan
export -f generate_server_report
export -f generate_domain_report
export -f batch_operation