#!/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