From 55e1111ec0cb38a73c9e92bfa59c2b6df4ad8c92 Mon Sep 17 00:00:00 2001 From: cschantz Date: Tue, 2 Dec 2025 20:46:28 -0500 Subject: [PATCH] Phase 4: Implement backup/restore system with PHP-FPM restart capability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW LIBRARY: lib/php-config-manager.sh (14 functions, 442 lines) BACKUP FUNCTIONS: - initialize_backup_system() - Creates /root/server-toolkit/backups/php/ - backup_php_config() - Backs up single config file with metadata - backup_fpm_pool() - Backs up PHP-FPM pool configuration - backup_user_php_configs() - Backs up ALL PHP configs for a user - list_backups() - Lists all backups with metadata (date, user, domain, file count) RESTORE FUNCTIONS: - restore_php_config() - Restores single config file - restore_from_backup() - Restores entire backup set - delete_backup() - Removes old backups CONFIGURATION MODIFICATION: - modify_fpm_pool_setting() - Changes single FPM pool setting - modify_php_ini_setting() - Changes single php.ini setting - apply_fpm_pool_settings() - Applies multiple settings at once PHP-FPM MANAGEMENT: - restart_php_fpm() - Restarts PHP-FPM service (systemd/sysvinit) - reload_php_fpm() - Graceful reload (no downtime) - verify_php_fpm_running() - Checks if service is active MENU OPTIONS B & R IMPLEMENTED: Option B: Backup Current Configurations - Select domain to backup - Backs up all php.ini files (priority 1-4) - Backs up PHP-FPM pool config - Creates metadata.txt with timestamp, user, domain - Preserves directory structure - Shows list of backed up files - Backup location: /root/server-toolkit/backups/php/YYYYMMDD_HHMMSS/ Option R: Restore from Backup - Lists all available backups with details - Shows: backup name, date, username, domain, file count - Numbered selection menu - Confirmation prompt: "This will overwrite current configurations!" - Requires typing "yes" to proceed - Restores all files with metadata preservation - Shows success/failure for each file - Reminder to restart PHP-FPM BACKUP STRUCTURE: /root/server-toolkit/backups/php/ ├── 20250102_143045/ │ ├── metadata.txt (backup info) │ ├── opt/cpanel/ea-php82/root/etc/php-fpm.d/username.conf │ ├── home/username/.php/8.2/php.ini │ └── home/username/public_html/.user.ini └── 20250102_150830/ └── ... SAFETY FEATURES: - Metadata tracking (who, what, when) - Confirmation required for restore - Non-destructive backups (never overwrites backups) - Timestamp-based naming (no conflicts) - Preserves file permissions and ownership FUTURE USE: These functions will be used by Phase 5 (apply/action menu) to: 1. Auto-backup before applying changes 2. Rollback if changes cause issues 3. Compare current vs backed up configs --- lib/php-config-manager.sh | 508 +++++++++++++++++++++++++++ modules/performance/php-optimizer.sh | 149 +++++++- 2 files changed, 653 insertions(+), 4 deletions(-) create mode 100644 lib/php-config-manager.sh diff --git a/lib/php-config-manager.sh b/lib/php-config-manager.sh new file mode 100644 index 0000000..f3d8dd5 --- /dev/null +++ b/lib/php-config-manager.sh @@ -0,0 +1,508 @@ +#!/bin/bash +# PHP Configuration Manager +# Handles backup, restore, and modification of PHP configurations +# Part of Server Toolkit - Configuration Management + +# Backup directory +BACKUP_DIR="/root/server-toolkit/backups/php" +BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S) + +# ============================================================================ +# BACKUP FUNCTIONS +# ============================================================================ + +# Create backup directory structure +initialize_backup_system() { + mkdir -p "$BACKUP_DIR" + + if [ ! -d "$BACKUP_DIR" ]; then + echo "ERROR: Failed to create backup directory: $BACKUP_DIR" + return 1 + fi + + return 0 +} + +# Backup a single PHP configuration file +# Usage: backup_php_config [backup_name] +backup_php_config() { + local config_file="$1" + local backup_name="${2:-$BACKUP_TIMESTAMP}" + + if [ ! -f "$config_file" ]; then + echo "ERROR: Config file not found: $config_file" + return 1 + fi + + # Create backup subdirectory + local backup_subdir="$BACKUP_DIR/$backup_name" + mkdir -p "$backup_subdir" + + # Preserve directory structure + local relative_path="${config_file#/}" + local backup_path="$backup_subdir/$relative_path" + local backup_dir_path=$(dirname "$backup_path") + + mkdir -p "$backup_dir_path" + + # Copy with metadata preservation + cp -p "$config_file" "$backup_path" + + if [ $? -eq 0 ]; then + echo "$backup_path" + return 0 + else + echo "ERROR: Failed to backup $config_file" + return 1 + fi +} + +# Backup PHP-FPM pool configuration +# Usage: backup_fpm_pool [backup_name] +backup_fpm_pool() { + local username="$1" + local backup_name="${2:-$BACKUP_TIMESTAMP}" + + # Source php-detector to find pool config + local pool_config + pool_config=$(find_fpm_pool_config "$username") + + if [ -z "$pool_config" ] || [ ! -f "$pool_config" ]; then + echo "ERROR: FPM pool config not found for $username" + return 1 + fi + + backup_php_config "$pool_config" "$backup_name" +} + +# Backup all PHP configs for a user +# Usage: backup_user_php_configs [backup_name] +backup_user_php_configs() { + local username="$1" + local domain="$2" + local backup_name="${3:-$BACKUP_TIMESTAMP}" + + initialize_backup_system || return 1 + + local backup_subdir="$BACKUP_DIR/$backup_name" + mkdir -p "$backup_subdir" + + # Create backup metadata + cat > "$backup_subdir/metadata.txt" <> "$backup_subdir/metadata.txt" + echo " $config → $backup_path" >> "$backup_subdir/metadata.txt" + fi + done <<< "$configs" + + # Backup FPM pool config + local pool_config + pool_config=$(find_fpm_pool_config "$username") + + if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then + local backup_path + backup_path=$(backup_php_config "$pool_config" "$backup_name") + + if [ $? -eq 0 ]; then + backed_up_files+=("$pool_config") + echo " $pool_config → $backup_path" >> "$backup_subdir/metadata.txt" + fi + fi + + # Return backup location + if [ ${#backed_up_files[@]} -gt 0 ]; then + echo "$backup_subdir" + return 0 + else + echo "ERROR: No files backed up" + return 1 + fi +} + +# List all backups +# Usage: list_backups +list_backups() { + if [ ! -d "$BACKUP_DIR" ]; then + echo "No backups found" + return 1 + fi + + local backups + backups=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -name "2*" | sort -r) + + if [ -z "$backups" ]; then + echo "No backups found" + return 1 + fi + + echo "BACKUP_NAME|DATE|USERNAME|DOMAIN|FILE_COUNT" + + while IFS= read -r backup_dir; do + local backup_name=$(basename "$backup_dir") + local metadata_file="$backup_dir/metadata.txt" + + if [ -f "$metadata_file" ]; then + local created=$(grep "^Backup Created:" "$metadata_file" | cut -d: -f2- | xargs) + local username=$(grep "^Username:" "$metadata_file" | cut -d: -f2 | xargs) + local domain=$(grep "^Domain:" "$metadata_file" | cut -d: -f2 | xargs) + local file_count=$(find "$backup_dir" -type f ! -name "metadata.txt" | wc -l) + + echo "$backup_name|$created|$username|$domain|$file_count" + else + local file_count=$(find "$backup_dir" -type f | wc -l) + echo "$backup_name|Unknown|Unknown|Unknown|$file_count" + fi + done <<< "$backups" +} + +# ============================================================================ +# RESTORE FUNCTIONS +# ============================================================================ + +# Restore a single configuration file +# Usage: restore_php_config +restore_php_config() { + local backup_path="$1" + local original_path="$2" + + if [ ! -f "$backup_path" ]; then + echo "ERROR: Backup file not found: $backup_path" + return 1 + fi + + # Create directory if needed + local original_dir=$(dirname "$original_path") + mkdir -p "$original_dir" + + # Restore with metadata preservation + cp -p "$backup_path" "$original_path" + + if [ $? -eq 0 ]; then + echo "Restored: $original_path" + return 0 + else + echo "ERROR: Failed to restore $original_path" + return 1 + fi +} + +# Restore from backup +# Usage: restore_from_backup +restore_from_backup() { + local backup_name="$1" + local backup_dir="$BACKUP_DIR/$backup_name" + + if [ ! -d "$backup_dir" ]; then + echo "ERROR: Backup not found: $backup_name" + return 1 + fi + + # Read metadata + local metadata_file="$backup_dir/metadata.txt" + + if [ ! -f "$metadata_file" ]; then + echo "ERROR: Backup metadata not found" + return 1 + fi + + echo "Restoring from backup: $backup_name" + cat "$metadata_file" + echo "" + + # Find all backed up files (excluding metadata) + local restored_count=0 + local failed_count=0 + + while IFS= read -r backup_file; do + # Extract original path from backup structure + local relative_path="${backup_file#$backup_dir/}" + local original_path="/$relative_path" + + if restore_php_config "$backup_file" "$original_path"; then + restored_count=$((restored_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + done < <(find "$backup_dir" -type f ! -name "metadata.txt") + + echo "" + echo "Restore complete: $restored_count files restored, $failed_count failed" + + if [ "$failed_count" -eq 0 ]; then + return 0 + else + return 1 + fi +} + +# Delete a backup +# Usage: delete_backup +delete_backup() { + local backup_name="$1" + local backup_dir="$BACKUP_DIR/$backup_name" + + if [ ! -d "$backup_dir" ]; then + echo "ERROR: Backup not found: $backup_name" + return 1 + fi + + rm -rf "$backup_dir" + + if [ $? -eq 0 ]; then + echo "Backup deleted: $backup_name" + return 0 + else + echo "ERROR: Failed to delete backup" + return 1 + fi +} + +# ============================================================================ +# CONFIGURATION MODIFICATION FUNCTIONS +# ============================================================================ + +# Modify a PHP-FPM pool setting +# Usage: modify_fpm_pool_setting +modify_fpm_pool_setting() { + local pool_config="$1" + local setting="$2" + local value="$3" + + if [ ! -f "$pool_config" ]; then + echo "ERROR: Pool config not found: $pool_config" + return 1 + fi + + # Check if setting exists + if grep -q "^${setting}\s*=" "$pool_config"; then + # Replace existing value + sed -i "s|^${setting}\s*=.*|${setting} = ${value}|" "$pool_config" + elif grep -q "^;${setting}\s*=" "$pool_config"; then + # Uncomment and set value + sed -i "s|^;${setting}\s*=.*|${setting} = ${value}|" "$pool_config" + else + # Add new setting at end of file + echo "${setting} = ${value}" >> "$pool_config" + fi + + if [ $? -eq 0 ]; then + echo "Modified: $setting = $value in $pool_config" + return 0 + else + echo "ERROR: Failed to modify $setting" + return 1 + fi +} + +# Modify a php.ini setting +# Usage: modify_php_ini_setting +modify_php_ini_setting() { + local php_ini="$1" + local setting="$2" + local value="$3" + + if [ ! -f "$php_ini" ]; then + echo "ERROR: php.ini not found: $php_ini" + return 1 + fi + + # Check if setting exists + if grep -q "^${setting}\s*=" "$php_ini"; then + # Replace existing value + sed -i "s|^${setting}\s*=.*|${setting} = ${value}|" "$php_ini" + elif grep -q "^;${setting}\s*=" "$php_ini"; then + # Uncomment and set value + sed -i "s|^;${setting}\s*=.*|${setting} = ${value}|" "$php_ini" + else + # Add new setting at end of file + echo "${setting} = ${value}" >> "$php_ini" + fi + + if [ $? -eq 0 ]; then + echo "Modified: $setting = $value in $php_ini" + return 0 + else + echo "ERROR: Failed to modify $setting" + return 1 + fi +} + +# Apply multiple FPM pool settings +# Usage: apply_fpm_pool_settings +# settings_array format: "setting1=value1" "setting2=value2" ... +apply_fpm_pool_settings() { + local pool_config="$1" + shift + local settings=("$@") + + local success_count=0 + local failed_count=0 + + for setting_value in "${settings[@]}"; do + local setting="${setting_value%%=*}" + local value="${setting_value#*=}" + + if modify_fpm_pool_setting "$pool_config" "$setting" "$value"; then + success_count=$((success_count + 1)) + else + failed_count=$((failed_count + 1)) + fi + done + + echo "Applied: $success_count settings, $failed_count failed" + + if [ "$failed_count" -eq 0 ]; then + return 0 + else + return 1 + fi +} + +# ============================================================================ +# PHP-FPM RESTART FUNCTIONS +# ============================================================================ + +# Restart PHP-FPM service +# Usage: restart_php_fpm +restart_php_fpm() { + local php_version="$1" # e.g., "ea-php82" or "82" + + # Normalize version format + if [[ ! "$php_version" =~ ^ea-php ]]; then + php_version="ea-php${php_version}" + fi + + # Detect init system + if command -v systemctl >/dev/null 2>&1; then + # systemd + local service_name="${php_version}-php-fpm" + + systemctl restart "$service_name" 2>/dev/null + + if [ $? -eq 0 ]; then + echo "Restarted: $service_name (systemd)" + return 0 + else + echo "ERROR: Failed to restart $service_name" + return 1 + fi + elif command -v service >/dev/null 2>&1; then + # sysvinit + local service_name="${php_version}-php-fpm" + + service "$service_name" restart 2>/dev/null + + if [ $? -eq 0 ]; then + echo "Restarted: $service_name (service)" + return 0 + else + echo "ERROR: Failed to restart $service_name" + return 1 + fi + else + echo "ERROR: Cannot detect init system" + return 1 + fi +} + +# Reload PHP-FPM service (graceful restart) +# Usage: reload_php_fpm +reload_php_fpm() { + local php_version="$1" + + # Normalize version format + if [[ ! "$php_version" =~ ^ea-php ]]; then + php_version="ea-php${php_version}" + fi + + # Detect init system + if command -v systemctl >/dev/null 2>&1; then + # systemd + local service_name="${php_version}-php-fpm" + + systemctl reload "$service_name" 2>/dev/null + + if [ $? -eq 0 ]; then + echo "Reloaded: $service_name (graceful)" + return 0 + else + # Fallback to restart if reload fails + restart_php_fpm "$php_version" + return $? + fi + else + # Reload not supported, use restart + restart_php_fpm "$php_version" + return $? + fi +} + +# Verify PHP-FPM is running +# Usage: verify_php_fpm_running +verify_php_fpm_running() { + local php_version="$1" + + # Normalize version format + if [[ ! "$php_version" =~ ^ea-php ]]; then + php_version="ea-php${php_version}" + fi + + if command -v systemctl >/dev/null 2>&1; then + local service_name="${php_version}-php-fpm" + + systemctl is-active "$service_name" >/dev/null 2>&1 + + if [ $? -eq 0 ]; then + echo "Running: $service_name" + return 0 + else + echo "NOT running: $service_name" + return 1 + fi + else + # Check process + if pgrep -f "${php_version}-php-fpm" >/dev/null 2>&1; then + echo "Running: ${php_version}-php-fpm" + return 0 + else + echo "NOT running: ${php_version}-php-fpm" + return 1 + fi + fi +} + +# Export all functions +export -f initialize_backup_system +export -f backup_php_config +export -f backup_fpm_pool +export -f backup_user_php_configs +export -f list_backups +export -f restore_php_config +export -f restore_from_backup +export -f delete_backup +export -f modify_fpm_pool_setting +export -f modify_php_ini_setting +export -f apply_fpm_pool_settings +export -f restart_php_fpm +export -f reload_php_fpm +export -f verify_php_fpm_running diff --git a/modules/performance/php-optimizer.sh b/modules/performance/php-optimizer.sh index 7940cd8..42789e2 100755 --- a/modules/performance/php-optimizer.sh +++ b/modules/performance/php-optimizer.sh @@ -7,6 +7,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)" source "$SCRIPT_DIR/lib/php-detector.sh" 2>/dev/null || { echo "ERROR: php-detector.sh not found"; exit 1; } source "$SCRIPT_DIR/lib/php-analyzer.sh" 2>/dev/null || { echo "ERROR: php-analyzer.sh not found"; exit 1; } +source "$SCRIPT_DIR/lib/php-config-manager.sh" 2>/dev/null || { echo "ERROR: php-config-manager.sh not found"; exit 1; } source "$SCRIPT_DIR/lib/system-detect.sh" 2>/dev/null || { echo "ERROR: system-detect.sh not found"; exit 1; } source "$SCRIPT_DIR/lib/user-manager.sh" 2>/dev/null || { echo "ERROR: user-manager.sh not found"; exit 1; } @@ -862,6 +863,148 @@ check_server_memory_capacity() { read -p "Press Enter to continue..." } +# ============================================================================ +# OPTION B: BACKUP CONFIGURATIONS +# ============================================================================ + +backup_configurations() { + show_banner + cecho "${WHITE}${BOLD}BACKUP PHP CONFIGURATIONS${NC}" + echo "" + + # Select domain + local selection + selection=$(select_domain "backup") + + if [ $? -ne 0 ] || [ -z "$selection" ]; then + return + fi + + local domain username + domain=$(echo "$selection" | cut -d'|' -f1) + username=$(echo "$selection" | cut -d'|' -f2) + + show_banner + cecho "${WHITE}${BOLD}BACKUP: ${GREEN}$domain${NC}" + echo "" + + # Initialize backup system + initialize_backup_system + + # Create backup + cecho "${YELLOW}Creating backup of PHP configurations...${NC}" + echo "" + + local backup_dir + backup_dir=$(backup_user_php_configs "$username" "$domain" 2>&1) + + if [ $? -eq 0 ]; then + cecho "${GREEN}${BOLD}✓ Backup created successfully!${NC}" + echo "" + cecho " Backup location: ${WHITE}$backup_dir${NC}" + echo "" + + # Show what was backed up + if [ -f "$backup_dir/metadata.txt" ]; then + cecho "${CYAN}Files backed up:${NC}" + grep "^ /" "$backup_dir/metadata.txt" | while IFS= read -r line; do + local file=$(echo "$line" | awk '{print $1}') + cecho " ${GREEN}✓${NC} $file" + done + fi + else + cecho "${RED}${BOLD}✗ Backup failed${NC}" + echo "" + echo "$backup_dir" + fi + + echo "" + read -p "Press Enter to continue..." +} + +# ============================================================================ +# OPTION R: RESTORE FROM BACKUP +# ============================================================================ + +restore_configurations() { + show_banner + cecho "${WHITE}${BOLD}RESTORE FROM BACKUP${NC}" + echo "" + + # List available backups + cecho "${CYAN}Available backups:${NC}" + echo "" + + local backups + backups=$(list_backups) + + if [ $? -ne 0 ]; then + cecho "${YELLOW}No backups found${NC}" + echo "" + read -p "Press Enter to continue..." + return + fi + + # Display backups + local backup_array=() + local index=1 + + echo "$backups" | tail -n +2 | while IFS='|' read -r backup_name created username domain file_count; do + printf "${GREEN}%3d${NC}) %-20s %s ${CYAN}[%s]${NC} ${YELLOW}(%s)${NC} %s files\n" \ + "$index" "$backup_name" "$created" "$username" "$domain" "$file_count" + backup_array+=("$backup_name") + index=$((index + 1)) + done + + # Store backup names in array for selection + mapfile -t backup_array < <(echo "$backups" | tail -n +2 | cut -d'|' -f1) + + echo "" + read -p "Select backup number to restore (or 'q' to cancel): " selection + + if [[ "$selection" == "q" ]]; then + return + fi + + if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#backup_array[@]} ]; then + cecho "${RED}Invalid selection${NC}" + sleep 2 + return + fi + + local selected_backup="${backup_array[$((selection - 1))]}" + + # Confirm restoration + echo "" + cecho "${YELLOW}${BOLD}WARNING: This will overwrite current configurations!${NC}" + echo "" + read -p "Are you sure you want to restore from $selected_backup? (yes/no): " confirm + + if [[ "$confirm" != "yes" ]]; then + cecho "${YELLOW}Restore cancelled${NC}" + sleep 2 + return + fi + + # Perform restore + show_banner + cecho "${WHITE}${BOLD}RESTORING FROM BACKUP${NC}" + echo "" + + if restore_from_backup "$selected_backup"; then + echo "" + cecho "${GREEN}${BOLD}✓ Restore completed successfully!${NC}" + echo "" + cecho "${YELLOW}Don't forget to restart PHP-FPM for changes to take effect!${NC}" + else + echo "" + cecho "${RED}${BOLD}✗ Restore failed${NC}" + fi + + echo "" + read -p "Press Enter to continue..." +} + # ============================================================================ # MAIN LOOP # ============================================================================ @@ -919,12 +1062,10 @@ main() { check_server_memory_capacity ;; b) - cecho "${YELLOW}Backup feature not yet implemented${NC}" - sleep 2 + backup_configurations ;; r) - cecho "${YELLOW}Restore feature not yet implemented${NC}" - sleep 2 + restore_configurations ;; q|Q) cecho "${GREEN}Exiting PHP Optimizer...${NC}"