17fa38f349
- Update find_fpm_pool_config in php-action-executor.sh - Add proper domain matching for cPool configs - cPanel names pool configs after the domain, not the username - Add wildcard matching as fallback - Function now successfully locates pool config files - Critical fix for single-domain optimization in Option 4 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
594 lines
17 KiB
Bash
Executable File
594 lines
17 KiB
Bash
Executable File
#!/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"
|
|
|
|
local pool_config=""
|
|
|
|
# Try cPanel paths first (most common)
|
|
# cPanel typically names pools after the domain
|
|
if [ -n "$domain" ]; then
|
|
pool_config=$(find /opt/cpanel/ea-php*/root/etc/php-fpm.d/ -name "$domain.conf" 2>/dev/null | head -1)
|
|
[ -n "$pool_config" ] && { echo "$pool_config"; return 0; }
|
|
fi
|
|
|
|
# Try username
|
|
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; }
|
|
|
|
# Try matching any domain under this user
|
|
if [ -n "$domain" ]; then
|
|
pool_config=$(find /opt/cpanel/ea-php*/root/etc/php-fpm.d/ -name "*$domain*" 2>/dev/null | head -1)
|
|
[ -n "$pool_config" ] && { echo "$pool_config"; return 0; }
|
|
fi
|
|
|
|
# Try Debian/Ubuntu paths
|
|
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
|