Files
Linux-Server-Management-Too…/lib/php-action-executor.sh
T
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

581 lines
16 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"
# 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