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
+580
@@ -0,0 +1,580 @@
|
||||
#!/bin/bash
|
||||
# PHP-FPM Action Executor Module
|
||||
# Handles optimization application, change tracking, and rollback
|
||||
# Part of PHP Optimizer - Phase 3 Refactoring
|
||||
|
||||
# ============================================================================
|
||||
# CHANGE TRACKING
|
||||
# ============================================================================
|
||||
|
||||
# Initialize change tracking for a session
|
||||
init_change_tracking() {
|
||||
local session_id="${1:-$(date +%s)}"
|
||||
local tracking_dir="/var/log/php-optimizer/changes"
|
||||
|
||||
mkdir -p "$tracking_dir" 2>/dev/null || true
|
||||
export EXECUTOR_SESSION_ID="$session_id"
|
||||
export EXECUTOR_TRACKING_DIR="$tracking_dir"
|
||||
export EXECUTOR_CHANGE_LOG="${tracking_dir}/change-${session_id}.log"
|
||||
|
||||
> "$EXECUTOR_CHANGE_LOG" # Clear the log file
|
||||
}
|
||||
|
||||
# Log a change for audit trail
|
||||
log_change() {
|
||||
local domain="$1"
|
||||
local action="$2"
|
||||
local before="$3"
|
||||
local after="$4"
|
||||
local status="${5:-pending}"
|
||||
|
||||
if [ -z "$EXECUTOR_CHANGE_LOG" ]; then
|
||||
init_change_tracking
|
||||
fi
|
||||
|
||||
local timestamp
|
||||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
cat >> "$EXECUTOR_CHANGE_LOG" << EOF
|
||||
$timestamp|$domain|$action|$status
|
||||
Before: $before
|
||||
After: $after
|
||||
---
|
||||
EOF
|
||||
}
|
||||
|
||||
# Get change history
|
||||
get_change_history() {
|
||||
local domain="${1:-all}"
|
||||
local limit="${2:-50}"
|
||||
|
||||
if [ -z "$EXECUTOR_TRACKING_DIR" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$domain" = "all" ]; then
|
||||
tail -n "$limit" "$EXECUTOR_TRACKING_DIR"/change-*.log 2>/dev/null || true
|
||||
else
|
||||
grep "^[^|]*|$domain|" "$EXECUTOR_TRACKING_DIR"/change-*.log 2>/dev/null | tail -n "$limit" || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Get list of all changes from a specific date
|
||||
get_changes_since() {
|
||||
local since_date="$1"
|
||||
[ -z "$since_date" ] && return 1
|
||||
|
||||
if [ -z "$EXECUTOR_TRACKING_DIR" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
find "$EXECUTOR_TRACKING_DIR" -name "change-*.log" -newer /tmp/php-optimizer-since-"$since_date" 2>/dev/null | \
|
||||
xargs cat 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# BACKUP & ROLLBACK
|
||||
# ============================================================================
|
||||
|
||||
# Create backup of a domain's FPM pool config before making changes
|
||||
backup_domain_config() {
|
||||
local domain="$1"
|
||||
local username="${2:-}"
|
||||
|
||||
local pool_config
|
||||
if [ -n "$username" ]; then
|
||||
pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null)
|
||||
else
|
||||
pool_config=$(find_fpm_pool_by_domain "$domain" 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local backup_dir="/var/lib/php-optimizer/backups"
|
||||
mkdir -p "$backup_dir" 2>/dev/null || true
|
||||
|
||||
local backup_file
|
||||
backup_file="${backup_dir}/${domain}-$(date +%Y%m%d-%H%M%S).conf"
|
||||
|
||||
cp "$pool_config" "$backup_file" 2>/dev/null || return 1
|
||||
echo "$backup_file"
|
||||
}
|
||||
|
||||
# Rollback a domain's config to a specific backup
|
||||
rollback_domain_config() {
|
||||
local domain="$1"
|
||||
local backup_file="$2"
|
||||
|
||||
[ -z "$domain" ] || [ -z "$backup_file" ] && return 1
|
||||
[ ! -f "$backup_file" ] && return 1
|
||||
|
||||
local pool_config
|
||||
pool_config=$(find_fpm_pool_by_domain "$domain" 2>/dev/null)
|
||||
|
||||
if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$backup_file" "$pool_config" 2>/dev/null || return 1
|
||||
log_change "$domain" "rollback" "current" "restored_from_backup"
|
||||
|
||||
# Reload PHP-FPM
|
||||
reload_php_fpm
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION MODIFICATION
|
||||
# ============================================================================
|
||||
|
||||
# Update a PHP pool configuration parameter
|
||||
update_pool_parameter() {
|
||||
local pool_config="$1"
|
||||
local parameter="$2"
|
||||
local value="$3"
|
||||
|
||||
[ -z "$pool_config" ] || [ -z "$parameter" ] || [ -z "$value" ] && return 1
|
||||
[ ! -f "$pool_config" ] && return 1
|
||||
|
||||
# Check if parameter exists
|
||||
if grep -q "^${parameter}\s*=" "$pool_config"; then
|
||||
# Update existing parameter
|
||||
sed -i.bak "s/^${parameter}\s*=.*/${parameter} = ${value}/" "$pool_config"
|
||||
else
|
||||
# Add new parameter
|
||||
echo "${parameter} = ${value}" >> "$pool_config"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Update multiple pool parameters at once
|
||||
update_pool_parameters() {
|
||||
local pool_config="$1"
|
||||
shift # Remove first argument
|
||||
local -a params=("$@")
|
||||
|
||||
[ -f "$pool_config" ] || return 1
|
||||
|
||||
# Create backup before making multiple changes
|
||||
local backup_file
|
||||
backup_file=$(backup_domain_config "temp" 2>/dev/null) || backup_file="${pool_config}.backup"
|
||||
cp "$pool_config" "$backup_file" 2>/dev/null
|
||||
|
||||
local all_success=true
|
||||
for param_pair in "${params[@]}"; do
|
||||
local param_name param_value
|
||||
param_name=$(echo "$param_pair" | cut -d'=' -f1)
|
||||
param_value=$(echo "$param_pair" | cut -d'=' -f2)
|
||||
|
||||
if ! update_pool_parameter "$pool_config" "$param_name" "$param_value"; then
|
||||
all_success=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_success" = false ]; then
|
||||
# Restore backup on failure
|
||||
cp "$backup_file" "$pool_config" 2>/dev/null
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Apply max_children optimization
|
||||
apply_max_children_optimization() {
|
||||
local domain="$1"
|
||||
local username="$2"
|
||||
local new_max_children="$3"
|
||||
|
||||
local pool_config
|
||||
pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null)
|
||||
|
||||
if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get current value for logging
|
||||
local current_value
|
||||
current_value=$(grep "^pm.max_children" "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
current_value=${current_value:-unknown}
|
||||
|
||||
# Create backup
|
||||
local backup_file
|
||||
backup_file=$(backup_domain_config "$domain" "$username")
|
||||
|
||||
# Update the parameter
|
||||
if ! update_pool_parameter "$pool_config" "pm.max_children" "$new_max_children"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Log the change
|
||||
log_change "$domain" "max_children" "$current_value" "$new_max_children" "completed"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Apply PM mode optimization
|
||||
apply_pm_mode_optimization() {
|
||||
local domain="$1"
|
||||
local username="$2"
|
||||
local pm_mode="$3"
|
||||
local min_spare="${4:-10}"
|
||||
local max_spare="${5:-20}"
|
||||
|
||||
local pool_config
|
||||
pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null)
|
||||
|
||||
if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get current values for logging
|
||||
local current_mode current_min current_max
|
||||
current_mode=$(grep "^pm\s*=" "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
current_min=$(grep "^pm.min_spare_servers" "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
current_max=$(grep "^pm.max_spare_servers" "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
|
||||
# Create backup
|
||||
local backup_file
|
||||
backup_file=$(backup_domain_config "$domain" "$username")
|
||||
|
||||
# Update parameters
|
||||
local params=(
|
||||
"pm=$pm_mode"
|
||||
"pm.min_spare_servers=$min_spare"
|
||||
"pm.max_spare_servers=$max_spare"
|
||||
)
|
||||
|
||||
if ! update_pool_parameters "$pool_config" "${params[@]}"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Log the change
|
||||
log_change "$domain" "pm_mode" "$current_mode/$current_min/$current_max" "$pm_mode/$min_spare/$max_spare" "completed"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# OPTIMIZATION APPLICATION
|
||||
# ============================================================================
|
||||
|
||||
# Apply optimization to a single domain
|
||||
apply_optimization() {
|
||||
local domain="$1"
|
||||
local username="$2"
|
||||
local optimization_type="${3:-all}" # all, max_children, pm_mode, opcache
|
||||
local dry_run="${4:-false}"
|
||||
|
||||
if [ "$dry_run" = "true" ]; then
|
||||
return 0 # Skip actual changes in dry-run mode
|
||||
fi
|
||||
|
||||
case "$optimization_type" in
|
||||
max_children)
|
||||
apply_max_children_optimization "$domain" "$username" "$5" || return 1
|
||||
;;
|
||||
pm_mode)
|
||||
apply_pm_mode_optimization "$domain" "$username" "$5" "$6" "$7" || return 1
|
||||
;;
|
||||
all)
|
||||
# Apply all recommendations
|
||||
if [ -n "$5" ]; then
|
||||
apply_max_children_optimization "$domain" "$username" "$5" || return 1
|
||||
fi
|
||||
if [ -n "$6" ]; then
|
||||
apply_pm_mode_optimization "$domain" "$username" "$6" "$7" "$8" || return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Apply optimizations to multiple domains (batch operation)
|
||||
apply_batch_optimization() {
|
||||
local -a domains=("$@")
|
||||
local dry_run="${DRY_RUN:-false}"
|
||||
local total_domains=${#domains[@]}
|
||||
local current=0
|
||||
local successful=0
|
||||
local failed=0
|
||||
|
||||
init_change_tracking
|
||||
|
||||
for domain in "${domains[@]}"; do
|
||||
[ -z "$domain" ] && continue
|
||||
|
||||
current=$((current + 1))
|
||||
show_enumeration_progress "$current" "$total_domains"
|
||||
|
||||
local username
|
||||
username=$(find_domain_owner "$domain")
|
||||
|
||||
if [ -z "$username" ]; then
|
||||
failed=$((failed + 1))
|
||||
log_change "$domain" "batch_optimization" "unknown_user" "skipped" "failed"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Apply optimization
|
||||
if apply_optimization "$domain" "$username" "all" "$dry_run"; then
|
||||
successful=$((successful + 1))
|
||||
log_change "$domain" "batch_optimization" "started" "completed" "completed"
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
log_change "$domain" "batch_optimization" "attempted" "failed" "failed"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
return $((failed > 0 ? 1 : 0))
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# VERIFICATION & VALIDATION
|
||||
# ============================================================================
|
||||
|
||||
# Verify that changes were applied correctly
|
||||
verify_applied_changes() {
|
||||
local domain="$1"
|
||||
local username="$2"
|
||||
local expected_max_children="${3:-}"
|
||||
local expected_pm_mode="${4:-}"
|
||||
|
||||
local pool_config
|
||||
pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null)
|
||||
|
||||
if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local verify_success=true
|
||||
|
||||
# Verify max_children if expected
|
||||
if [ -n "$expected_max_children" ]; then
|
||||
local actual_max_children
|
||||
actual_max_children=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
|
||||
if [ "$actual_max_children" != "$expected_max_children" ]; then
|
||||
verify_success=false
|
||||
echo "max_children mismatch: expected $expected_max_children, got $actual_max_children"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify PM mode if expected
|
||||
if [ -n "$expected_pm_mode" ]; then
|
||||
local actual_pm_mode
|
||||
actual_pm_mode=$(grep "^pm\s*=" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
|
||||
if [ "$actual_pm_mode" != "$expected_pm_mode" ]; then
|
||||
verify_success=false
|
||||
echo "pm mode mismatch: expected $expected_pm_mode, got $actual_pm_mode"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$verify_success" = true ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if changes are valid (syntax, no conflicts)
|
||||
validate_pool_config() {
|
||||
local pool_config="$1"
|
||||
|
||||
[ ! -f "$pool_config" ] && return 1
|
||||
|
||||
# Basic syntax check
|
||||
if grep -q "^[a-z_]*\s*=\s*[^;]*$" "$pool_config"; then
|
||||
# Check for common issues
|
||||
if grep -q "^pm.max_children\s*=\s*0" "$pool_config"; then
|
||||
return 1 # max_children cannot be 0
|
||||
fi
|
||||
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# PHP-FPM SERVICE OPERATIONS
|
||||
# ============================================================================
|
||||
|
||||
# Reload PHP-FPM to apply changes
|
||||
reload_php_fpm() {
|
||||
local php_version="${1:-}"
|
||||
|
||||
# Try common PHP-FPM service names
|
||||
local service_names=("php-fpm" "php7.4-fpm" "php8.0-fpm" "php8.1-fpm" "php8.2-fpm" "php8.3-fpm")
|
||||
|
||||
if [ -n "$php_version" ]; then
|
||||
service_names=("php${php_version}-fpm" "php-fpm")
|
||||
fi
|
||||
|
||||
for service in "${service_names[@]}"; do
|
||||
if systemctl is-active --quiet "$service" 2>/dev/null; then
|
||||
systemctl reload "$service" 2>/dev/null || service "$service" reload 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Fallback: try service command
|
||||
service php-fpm reload 2>/dev/null || return 1
|
||||
}
|
||||
|
||||
# Restart PHP-FPM (full restart, not just reload)
|
||||
restart_php_fpm() {
|
||||
local php_version="${1:-}"
|
||||
|
||||
local service_names=("php-fpm" "php7.4-fpm" "php8.0-fpm" "php8.1-fpm" "php8.2-fpm" "php8.3-fpm")
|
||||
|
||||
if [ -n "$php_version" ]; then
|
||||
service_names=("php${php_version}-fpm" "php-fpm")
|
||||
fi
|
||||
|
||||
for service in "${service_names[@]}"; do
|
||||
if systemctl is-active --quiet "$service" 2>/dev/null; then
|
||||
systemctl restart "$service" 2>/dev/null || service "$service" restart 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Get PHP-FPM service status
|
||||
get_php_fpm_status() {
|
||||
local service_names=("php-fpm" "php7.4-fpm" "php8.0-fpm" "php8.1-fpm" "php8.2-fpm" "php8.3-fpm")
|
||||
|
||||
for service in "${service_names[@]}"; do
|
||||
if systemctl is-active --quiet "$service" 2>/dev/null; then
|
||||
systemctl status "$service"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# DRY-RUN MODE (PREVIEW CHANGES)
|
||||
# ============================================================================
|
||||
|
||||
# Preview what changes would be applied (without making them)
|
||||
preview_changes() {
|
||||
local domain="$1"
|
||||
local username="$2"
|
||||
local -a changes=("${@:3}")
|
||||
|
||||
local pool_config
|
||||
pool_config=$(find_fpm_pool_config "$username" "$domain" 2>/dev/null)
|
||||
|
||||
if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "PREVIEW: Changes that would be applied to $domain:"
|
||||
echo ""
|
||||
echo "Config file: $pool_config"
|
||||
echo ""
|
||||
|
||||
for change in "${changes[@]}"; do
|
||||
local param_name param_new_value
|
||||
param_name=$(echo "$change" | cut -d'=' -f1)
|
||||
param_new_value=$(echo "$change" | cut -d'=' -f2)
|
||||
|
||||
local current_value
|
||||
current_value=$(grep "^${param_name}\s*=" "$pool_config" 2>/dev/null | awk -F'=' '{print $2}' | tr -d ' ')
|
||||
|
||||
if [ -z "$current_value" ]; then
|
||||
echo " + $param_name = $param_new_value (NEW)"
|
||||
else
|
||||
echo " - $param_name = $current_value"
|
||||
echo " + $param_name = $param_new_value"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Find FPM pool config for a domain
|
||||
find_fpm_pool_config() {
|
||||
local username="$1"
|
||||
local domain="$2"
|
||||
|
||||
# Try using existing function if available
|
||||
if type find_fpm_pool_config_internal >/dev/null 2>&1; then
|
||||
find_fpm_pool_config_internal "$username" "$domain"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Fallback: search common locations
|
||||
local common_paths=(
|
||||
"/etc/php-fpm.d/${username}.conf"
|
||||
"/etc/php/7.4/fpm/pool.d/${username}.conf"
|
||||
"/etc/php/8.0/fpm/pool.d/${username}.conf"
|
||||
"/etc/php/8.1/fpm/pool.d/${username}.conf"
|
||||
"/etc/php/8.2/fpm/pool.d/${username}.conf"
|
||||
"/etc/php/8.3/fpm/pool.d/${username}.conf"
|
||||
)
|
||||
|
||||
for path in "${common_paths[@]}"; do
|
||||
if [ -f "$path" ]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Find FPM pool config by domain name
|
||||
find_fpm_pool_by_domain() {
|
||||
local domain="$1"
|
||||
|
||||
local owner
|
||||
owner=$(find_domain_owner "$domain")
|
||||
|
||||
if [ -n "$owner" ]; then
|
||||
find_fpm_pool_config "$owner" "$domain"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# EXPORT ALL FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
export -f init_change_tracking
|
||||
export -f log_change
|
||||
export -f get_change_history
|
||||
export -f get_changes_since
|
||||
export -f backup_domain_config
|
||||
export -f rollback_domain_config
|
||||
export -f update_pool_parameter
|
||||
export -f update_pool_parameters
|
||||
export -f apply_max_children_optimization
|
||||
export -f apply_pm_mode_optimization
|
||||
export -f apply_optimization
|
||||
export -f apply_batch_optimization
|
||||
export -f verify_applied_changes
|
||||
export -f validate_pool_config
|
||||
export -f reload_php_fpm
|
||||
export -f restart_php_fpm
|
||||
export -f get_php_fpm_status
|
||||
export -f preview_changes
|
||||
export -f find_fpm_pool_config
|
||||
export -f find_fpm_pool_by_domain
|
||||
Reference in New Issue
Block a user