55e1111ec0
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
1084 lines
38 KiB
Bash
Executable File
1084 lines
38 KiB
Bash
Executable File
#!/bin/bash
|
|
# PHP & Server Performance Optimizer
|
|
# Interactive tool for analyzing and optimizing PHP-FPM configurations
|
|
# Part of Server Toolkit - Phase 3: Main Interactive Script
|
|
|
|
# Source required libraries
|
|
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; }
|
|
|
|
# Color codes (using safe echo -e)
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
MAGENTA='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
WHITE='\033[1;37m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Safe color echo function
|
|
cecho() {
|
|
echo -e "$@"
|
|
}
|
|
|
|
# ============================================================================
|
|
# BANNER & DISPLAY FUNCTIONS
|
|
# ============================================================================
|
|
|
|
show_banner() {
|
|
clear
|
|
cecho "${CYAN}╔══════════════════════════════════════════════════════════════════════╗${NC}"
|
|
cecho "${CYAN}║${WHITE} PHP & SERVER PERFORMANCE OPTIMIZER ${CYAN}║${NC}"
|
|
cecho "${CYAN}╚══════════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
}
|
|
|
|
show_main_menu() {
|
|
cecho "${WHITE}${BOLD}MAIN MENU${NC}"
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
echo ""
|
|
cecho " ${GREEN}1${NC}) Analyze Single Domain"
|
|
cecho " ${GREEN}2${NC}) Analyze All Domains (Server-Wide)"
|
|
cecho " ${GREEN}3${NC}) Quick Health Check (All Domains)"
|
|
cecho " ${GREEN}4${NC}) Optimize Domain PHP Settings"
|
|
cecho " ${GREEN}5${NC}) Optimize Server-Wide PHP Settings"
|
|
cecho " ${GREEN}6${NC}) View OPcache Statistics"
|
|
cecho " ${GREEN}7${NC}) View PHP-FPM Process Stats"
|
|
cecho " ${GREEN}8${NC}) Check for Configuration Issues"
|
|
cecho " ${GREEN}9${NC}) Check Server Memory Capacity (OOM Risk)"
|
|
echo ""
|
|
cecho " ${YELLOW}b${NC}) Backup Current Configurations"
|
|
cecho " ${YELLOW}r${NC}) Restore from Backup"
|
|
echo ""
|
|
cecho " ${RED}q${NC}) Quit"
|
|
echo ""
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
}
|
|
|
|
# ============================================================================
|
|
# DOMAIN SELECTION
|
|
# ============================================================================
|
|
|
|
select_domain() {
|
|
local action="${1:-analyze}"
|
|
|
|
cecho "${WHITE}${BOLD}SELECT DOMAIN${NC}"
|
|
echo ""
|
|
|
|
# Get all users with domains
|
|
local users
|
|
users=$(list_all_users)
|
|
|
|
if [ -z "$users" ]; then
|
|
cecho "${RED}ERROR: No users found on system${NC}"
|
|
read -p "Press Enter to continue..."
|
|
return 1
|
|
fi
|
|
|
|
# Build domain list
|
|
declare -a domains
|
|
declare -A domain_to_user
|
|
|
|
while IFS= read -r username; do
|
|
local user_domains
|
|
user_domains=$(get_user_domains "$username")
|
|
|
|
while IFS= read -r domain; do
|
|
[ -z "$domain" ] && continue
|
|
domains+=("$domain")
|
|
domain_to_user["$domain"]="$username"
|
|
done <<< "$user_domains"
|
|
done <<< "$users"
|
|
|
|
if [ ${#domains[@]} -eq 0 ]; then
|
|
cecho "${RED}ERROR: No domains found on system${NC}"
|
|
read -p "Press Enter to continue..."
|
|
return 1
|
|
fi
|
|
|
|
# Display numbered list
|
|
cecho "${CYAN}Available domains:${NC}"
|
|
echo ""
|
|
|
|
local index=1
|
|
for domain in "${domains[@]}"; do
|
|
local username="${domain_to_user[$domain]}"
|
|
local php_version
|
|
php_version=$(detect_php_version_for_domain "$domain" 2>/dev/null || echo "unknown")
|
|
|
|
printf " ${GREEN}%-3d${NC}) %-40s ${CYAN}[${username}]${NC} ${YELLOW}(${php_version})${NC}\n" "$index" "$domain"
|
|
index=$((index + 1))
|
|
done
|
|
|
|
echo ""
|
|
read -p "Select domain number (or 'q' to cancel): " selection
|
|
|
|
if [[ "$selection" == "q" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#domains[@]} ]; then
|
|
cecho "${RED}Invalid selection${NC}"
|
|
sleep 2
|
|
return 1
|
|
fi
|
|
|
|
# Return selected domain and username
|
|
local selected_domain="${domains[$((selection - 1))]}"
|
|
local selected_user="${domain_to_user[$selected_domain]}"
|
|
|
|
echo "$selected_domain|$selected_user"
|
|
return 0
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 1: ANALYZE SINGLE DOMAIN
|
|
# ============================================================================
|
|
|
|
analyze_single_domain() {
|
|
show_banner
|
|
|
|
local selection
|
|
selection=$(select_domain "analyze")
|
|
|
|
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}Analyzing: ${GREEN}$domain${WHITE} (user: ${CYAN}$username${WHITE})${NC}"
|
|
echo ""
|
|
|
|
# Run comprehensive analysis
|
|
analyze_domain_php "$username" "$domain"
|
|
|
|
echo ""
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 2: ANALYZE ALL DOMAINS
|
|
# ============================================================================
|
|
|
|
analyze_all_domains() {
|
|
show_banner
|
|
cecho "${WHITE}${BOLD}SERVER-WIDE ANALYSIS${NC}"
|
|
echo ""
|
|
cecho "${YELLOW}This will analyze PHP configuration for ALL domains...${NC}"
|
|
echo ""
|
|
read -p "Continue? (y/n): " confirm
|
|
|
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
return
|
|
fi
|
|
|
|
show_banner
|
|
cecho "${WHITE}${BOLD}Analyzing all domains...${NC}"
|
|
echo ""
|
|
|
|
# Get all users
|
|
local users
|
|
users=$(list_all_users)
|
|
|
|
local total_domains=0
|
|
local domains_with_issues=0
|
|
|
|
while IFS= read -r username; do
|
|
local user_domains
|
|
user_domains=$(get_user_domains "$username")
|
|
|
|
while IFS= read -r domain; do
|
|
[ -z "$domain" ] && continue
|
|
|
|
total_domains=$((total_domains + 1))
|
|
|
|
cecho "${CYAN}[$total_domains] Analyzing: ${WHITE}$domain${NC}"
|
|
|
|
# Detect issues
|
|
local issues
|
|
issues=$(detect_php_config_issues "$username" "$domain")
|
|
|
|
# Count critical/high severity issues
|
|
local critical_count
|
|
critical_count=$(echo "$issues" | grep -c "CRITICAL\|HIGH" || echo "0")
|
|
|
|
if [ "$critical_count" -gt 0 ]; then
|
|
domains_with_issues=$((domains_with_issues + 1))
|
|
cecho " ${RED}✗ $critical_count critical/high severity issues detected${NC}"
|
|
|
|
# Show issues
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ -z "$issue_type" ] && continue
|
|
|
|
if [[ "$severity" == "CRITICAL" ]] || [[ "$severity" == "HIGH" ]]; then
|
|
cecho " ${RED}[$severity]${NC} $issue_type: $message"
|
|
fi
|
|
done <<< "$issues"
|
|
else
|
|
cecho " ${GREEN}✓ No critical issues${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
done <<< "$user_domains"
|
|
done <<< "$users"
|
|
|
|
# Summary
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
cecho "${WHITE}${BOLD}SUMMARY${NC}"
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
cecho " Total domains analyzed: ${WHITE}$total_domains${NC}"
|
|
cecho " Domains with issues: ${RED}$domains_with_issues${NC}"
|
|
cecho " Domains healthy: ${GREEN}$((total_domains - domains_with_issues))${NC}"
|
|
echo ""
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 3: QUICK HEALTH CHECK
|
|
# ============================================================================
|
|
|
|
quick_health_check() {
|
|
show_banner
|
|
cecho "${WHITE}${BOLD}QUICK HEALTH CHECK${NC}"
|
|
echo ""
|
|
|
|
# Get all users
|
|
local users
|
|
users=$(list_all_users)
|
|
|
|
declare -A issue_counts
|
|
issue_counts["CRITICAL"]=0
|
|
issue_counts["HIGH"]=0
|
|
issue_counts["MEDIUM"]=0
|
|
issue_counts["LOW"]=0
|
|
|
|
local total_domains=0
|
|
|
|
while IFS= read -r username; do
|
|
local user_domains
|
|
user_domains=$(get_user_domains "$username")
|
|
|
|
while IFS= read -r domain; do
|
|
[ -z "$domain" ] && continue
|
|
|
|
total_domains=$((total_domains + 1))
|
|
|
|
# Detect issues
|
|
local issues
|
|
issues=$(detect_php_config_issues "$username" "$domain")
|
|
|
|
# Count by severity
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ -z "$severity" ] && continue
|
|
|
|
issue_counts["$severity"]=$((issue_counts["$severity"] + 1))
|
|
done <<< "$issues"
|
|
done <<< "$user_domains"
|
|
done <<< "$users"
|
|
|
|
# Display results
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
cecho "${WHITE}${BOLD}HEALTH CHECK RESULTS${NC}"
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
cecho " Total Domains: ${WHITE}$total_domains${NC}"
|
|
echo ""
|
|
cecho " ${RED}CRITICAL${NC} issues: ${issue_counts["CRITICAL"]}"
|
|
cecho " ${YELLOW}HIGH${NC} issues: ${issue_counts["HIGH"]}"
|
|
cecho " ${BLUE}MEDIUM${NC} issues: ${issue_counts["MEDIUM"]}"
|
|
cecho " ${GREEN}LOW${NC} issues: ${issue_counts["LOW"]}"
|
|
echo ""
|
|
|
|
# Overall health score
|
|
local health_score
|
|
health_score=$((100 - (issue_counts["CRITICAL"] * 20) - (issue_counts["HIGH"] * 10) - (issue_counts["MEDIUM"] * 5) - (issue_counts["LOW"] * 2)))
|
|
[ "$health_score" -lt 0 ] && health_score=0
|
|
|
|
if [ "$health_score" -ge 90 ]; then
|
|
cecho " Overall Health: ${GREEN}${BOLD}$health_score/100 - EXCELLENT${NC}"
|
|
elif [ "$health_score" -ge 70 ]; then
|
|
cecho " Overall Health: ${YELLOW}${BOLD}$health_score/100 - GOOD${NC}"
|
|
elif [ "$health_score" -ge 50 ]; then
|
|
cecho " Overall Health: ${YELLOW}${BOLD}$health_score/100 - FAIR${NC}"
|
|
else
|
|
cecho " Overall Health: ${RED}${BOLD}$health_score/100 - POOR${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 4: OPTIMIZE DOMAIN
|
|
# ============================================================================
|
|
|
|
optimize_domain() {
|
|
show_banner
|
|
|
|
local selection
|
|
selection=$(select_domain "optimize")
|
|
|
|
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}OPTIMIZE: ${GREEN}$domain${NC}"
|
|
echo ""
|
|
|
|
# Detect issues
|
|
cecho "${YELLOW}Detecting issues...${NC}"
|
|
local issues
|
|
issues=$(detect_php_config_issues "$username" "$domain")
|
|
|
|
# Display issues
|
|
local has_issues=false
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ -z "$issue_type" ] && continue
|
|
|
|
if [ "$issue_type" != "NONE" ]; then
|
|
has_issues=true
|
|
|
|
case "$severity" in
|
|
CRITICAL)
|
|
cecho "${RED}[CRITICAL]${NC} $message"
|
|
;;
|
|
HIGH)
|
|
cecho "${YELLOW}[HIGH]${NC} $message"
|
|
;;
|
|
MEDIUM)
|
|
cecho "${BLUE}[MEDIUM]${NC} $message"
|
|
;;
|
|
LOW)
|
|
cecho "${GREEN}[LOW]${NC} $message"
|
|
;;
|
|
esac
|
|
|
|
cecho " ${CYAN}→${NC} $recommendation"
|
|
echo ""
|
|
fi
|
|
done <<< "$issues"
|
|
|
|
if [ "$has_issues" = false ]; then
|
|
cecho "${GREEN}${BOLD}✓ No issues detected - configuration is optimal!${NC}"
|
|
echo ""
|
|
read -p "Press Enter to continue..."
|
|
return
|
|
fi
|
|
|
|
# Get optimization recommendations
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
cecho "${WHITE}${BOLD}RECOMMENDED OPTIMIZATIONS${NC}"
|
|
echo ""
|
|
|
|
# Calculate optimal max_children
|
|
local optimal_result
|
|
optimal_result=$(calculate_optimal_max_children "$username" 1024)
|
|
local recommended_max_children reason
|
|
recommended_max_children=$(echo "$optimal_result" | cut -d'|' -f1)
|
|
reason=$(echo "$optimal_result" | cut -d'|' -f2)
|
|
|
|
# Get current max_children
|
|
local pool_config
|
|
pool_config=$(find_fpm_pool_config "$username")
|
|
|
|
if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then
|
|
local current_max_children
|
|
current_max_children=$(grep "^pm.max_children" "$pool_config" | awk -F'=' '{print $2}' | tr -d ' ')
|
|
|
|
if [ -n "$current_max_children" ] && [ "$recommended_max_children" -ne "$current_max_children" ]; then
|
|
cecho "${GREEN}1.${NC} Adjust ${BOLD}pm.max_children${NC} from ${RED}$current_max_children${NC} to ${GREEN}$recommended_max_children${NC}"
|
|
cecho " Reason: $reason"
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
# Check OPcache
|
|
local opcache_status
|
|
opcache_status=$(analyze_opcache_effectiveness "$username")
|
|
local status hit_rate opcache_rec
|
|
status=$(echo "$opcache_status" | cut -d'|' -f1)
|
|
hit_rate=$(echo "$opcache_status" | cut -d'|' -f2)
|
|
opcache_rec=$(echo "$opcache_status" | cut -d'|' -f5)
|
|
|
|
if [ "$status" = "DISABLED" ]; then
|
|
cecho "${GREEN}2.${NC} ${BOLD}Enable OPcache${NC} for 40-70% performance boost"
|
|
echo ""
|
|
elif (( $(echo "$hit_rate < 90" | bc -l 2>/dev/null || echo "0") )); then
|
|
cecho "${GREEN}2.${NC} ${BOLD}Increase opcache.memory_consumption${NC} (current hit rate: ${hit_rate}%)"
|
|
echo ""
|
|
fi
|
|
|
|
echo ""
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
cecho "${YELLOW}${BOLD}WARNING:${NC} ${YELLOW}Automatic optimization not yet implemented${NC}"
|
|
cecho "${YELLOW}Please apply the above recommendations manually${NC}"
|
|
echo ""
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 6: VIEW OPCACHE STATISTICS
|
|
# ============================================================================
|
|
|
|
view_opcache_stats() {
|
|
show_banner
|
|
|
|
local selection
|
|
selection=$(select_domain "opcache")
|
|
|
|
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}OPCACHE STATISTICS: ${GREEN}$domain${NC}"
|
|
echo ""
|
|
|
|
# Get OPcache stats
|
|
local stats
|
|
stats=$(get_opcache_stats "$username")
|
|
|
|
if [ -z "$stats" ]; then
|
|
cecho "${RED}Unable to retrieve OPcache statistics${NC}"
|
|
echo ""
|
|
read -p "Press Enter to continue..."
|
|
return
|
|
fi
|
|
|
|
# Parse and display
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
cecho "${WHITE}${BOLD}OPCACHE STATUS${NC}"
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
local enabled
|
|
enabled=$(check_opcache_enabled "$username")
|
|
|
|
if [ "$enabled" = "1" ]; then
|
|
cecho " Status: ${GREEN}ENABLED${NC}"
|
|
else
|
|
cecho " Status: ${RED}DISABLED${NC}"
|
|
echo ""
|
|
read -p "Press Enter to continue..."
|
|
return
|
|
fi
|
|
|
|
# Display stats
|
|
while IFS='=' read -r key value; do
|
|
[ -z "$key" ] && continue
|
|
|
|
case "$key" in
|
|
memory_usage_mb)
|
|
cecho " Memory Used: ${WHITE}${value}MB${NC}"
|
|
;;
|
|
hits)
|
|
cecho " Cache Hits: ${GREEN}$value${NC}"
|
|
;;
|
|
misses)
|
|
cecho " Cache Misses: ${YELLOW}$value${NC}"
|
|
;;
|
|
num_cached_scripts)
|
|
cecho " Cached Scripts: ${WHITE}$value${NC}"
|
|
;;
|
|
max_cached_keys)
|
|
cecho " Max Cached Keys: ${WHITE}$value${NC}"
|
|
;;
|
|
wasted_memory_mb)
|
|
cecho " Wasted Memory: ${RED}${value}MB${NC}"
|
|
;;
|
|
esac
|
|
done <<< "$stats"
|
|
|
|
# Calculate and display hit rate
|
|
local hit_rate
|
|
hit_rate=$(calculate_opcache_hit_rate "$username")
|
|
echo ""
|
|
cecho " Hit Rate: ${GREEN}${BOLD}${hit_rate}%${NC}"
|
|
|
|
# Recommendation
|
|
echo ""
|
|
if (( $(echo "$hit_rate < 90" | bc -l 2>/dev/null || echo "0") )); then
|
|
cecho " ${YELLOW}⚠ Hit rate below 90% - Consider increasing opcache.memory_consumption${NC}"
|
|
else
|
|
cecho " ${GREEN}✓ Hit rate is excellent${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 7: VIEW PHP-FPM PROCESS STATS
|
|
# ============================================================================
|
|
|
|
view_fpm_stats() {
|
|
show_banner
|
|
|
|
local selection
|
|
selection=$(select_domain "fpm")
|
|
|
|
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}PHP-FPM PROCESS STATISTICS: ${GREEN}$domain${NC}"
|
|
echo ""
|
|
|
|
# Get process stats
|
|
local memory_stats
|
|
memory_stats=$(calculate_memory_per_process "$username")
|
|
|
|
local avg_kb process_count total_mb
|
|
avg_kb=$(echo "$memory_stats" | cut -d'|' -f1)
|
|
process_count=$(echo "$memory_stats" | cut -d'|' -f2)
|
|
total_mb=$(echo "$memory_stats" | cut -d'|' -f3)
|
|
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
cecho "${WHITE}${BOLD}CURRENT RESOURCE USAGE${NC}"
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
if [ "$process_count" -eq 0 ]; then
|
|
cecho "${YELLOW}No active PHP-FPM processes found${NC}"
|
|
echo ""
|
|
read -p "Press Enter to continue..."
|
|
return
|
|
fi
|
|
|
|
cecho " Active Processes: ${WHITE}$process_count${NC}"
|
|
cecho " Avg Memory/Process: ${WHITE}$((avg_kb / 1024))MB${NC} (${avg_kb}KB)"
|
|
cecho " Total Memory: ${WHITE}${total_mb}MB${NC}"
|
|
echo ""
|
|
|
|
# Get pool config
|
|
local pool_config
|
|
pool_config=$(find_fpm_pool_config "$username")
|
|
|
|
if [ -n "$pool_config" ] && [ -f "$pool_config" ]; then
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
cecho "${WHITE}${BOLD}POOL CONFIGURATION${NC}"
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
echo ""
|
|
|
|
local pool_settings
|
|
pool_settings=$(parse_fpm_pool_config "$pool_config")
|
|
|
|
# Display key settings
|
|
while IFS='=' read -r key value; do
|
|
[ -z "$key" ] && continue
|
|
cecho " $key: ${WHITE}$value${NC}"
|
|
done <<< "$pool_settings"
|
|
fi
|
|
|
|
# Calculate optimal max_children
|
|
echo ""
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
cecho "${WHITE}${BOLD}RECOMMENDATION${NC}"
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
echo ""
|
|
|
|
local optimal_result
|
|
optimal_result=$(calculate_optimal_max_children "$username" 1024)
|
|
local recommended reason
|
|
recommended=$(echo "$optimal_result" | cut -d'|' -f1)
|
|
reason=$(echo "$optimal_result" | cut -d'|' -f2)
|
|
|
|
cecho " Optimal pm.max_children: ${GREEN}${BOLD}$recommended${NC}"
|
|
cecho " Reason: $reason"
|
|
|
|
echo ""
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 8: CHECK FOR ISSUES
|
|
# ============================================================================
|
|
|
|
check_config_issues() {
|
|
show_banner
|
|
|
|
local selection
|
|
selection=$(select_domain "check")
|
|
|
|
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}CONFIGURATION ISSUES: ${GREEN}$domain${NC}"
|
|
echo ""
|
|
|
|
# Detect issues
|
|
local issues
|
|
issues=$(detect_php_config_issues "$username" "$domain")
|
|
|
|
# Display by severity
|
|
local has_critical=false
|
|
local has_high=false
|
|
local has_medium=false
|
|
local has_low=false
|
|
|
|
# Check for each severity level
|
|
echo "$issues" | grep -q "CRITICAL" && has_critical=true
|
|
echo "$issues" | grep -q "HIGH" && has_high=true
|
|
echo "$issues" | grep -q "MEDIUM" && has_medium=true
|
|
echo "$issues" | grep -q "LOW" && has_low=true
|
|
|
|
# Display CRITICAL
|
|
if [ "$has_critical" = true ]; then
|
|
cecho "${RED}${BOLD}CRITICAL ISSUES:${NC}"
|
|
echo ""
|
|
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ "$severity" != "CRITICAL" ] && continue
|
|
|
|
cecho " ${RED}●${NC} $message"
|
|
cecho " ${CYAN}→${NC} $recommendation"
|
|
echo ""
|
|
done <<< "$issues"
|
|
fi
|
|
|
|
# Display HIGH
|
|
if [ "$has_high" = true ]; then
|
|
cecho "${YELLOW}${BOLD}HIGH PRIORITY ISSUES:${NC}"
|
|
echo ""
|
|
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ "$severity" != "HIGH" ] && continue
|
|
|
|
cecho " ${YELLOW}●${NC} $message"
|
|
cecho " ${CYAN}→${NC} $recommendation"
|
|
echo ""
|
|
done <<< "$issues"
|
|
fi
|
|
|
|
# Display MEDIUM
|
|
if [ "$has_medium" = true ]; then
|
|
cecho "${BLUE}${BOLD}MEDIUM PRIORITY ISSUES:${NC}"
|
|
echo ""
|
|
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ "$severity" != "MEDIUM" ] && continue
|
|
|
|
cecho " ${BLUE}●${NC} $message"
|
|
cecho " ${CYAN}→${NC} $recommendation"
|
|
echo ""
|
|
done <<< "$issues"
|
|
fi
|
|
|
|
# Display LOW
|
|
if [ "$has_low" = true ]; then
|
|
cecho "${GREEN}${BOLD}LOW PRIORITY ISSUES:${NC}"
|
|
echo ""
|
|
|
|
while IFS='|' read -r issue_type severity message recommendation; do
|
|
[ "$severity" != "LOW" ] && continue
|
|
|
|
cecho " ${GREEN}●${NC} $message"
|
|
cecho " ${CYAN}→${NC} $recommendation"
|
|
echo ""
|
|
done <<< "$issues"
|
|
fi
|
|
|
|
# No issues
|
|
if [ "$has_critical" = false ] && [ "$has_high" = false ] && [ "$has_medium" = false ] && [ "$has_low" = false ]; then
|
|
cecho "${GREEN}${BOLD}✓ No configuration issues detected!${NC}"
|
|
cecho "${GREEN}Configuration appears to be optimal.${NC}"
|
|
echo ""
|
|
fi
|
|
|
|
read -p "Press Enter to continue..."
|
|
}
|
|
|
|
# ============================================================================
|
|
# OPTION 9: CHECK SERVER MEMORY CAPACITY
|
|
# ============================================================================
|
|
|
|
check_server_memory_capacity() {
|
|
show_banner
|
|
cecho "${WHITE}${BOLD}SERVER MEMORY CAPACITY CHECK${NC}"
|
|
echo ""
|
|
cecho "${YELLOW}This checks if all PHP-FPM pools hitting max_children would cause OOM...${NC}"
|
|
echo ""
|
|
|
|
# Run capacity analysis
|
|
cecho "${CYAN}Analyzing PHP-FPM memory requirements...${NC}"
|
|
echo ""
|
|
|
|
local result
|
|
result=$(calculate_server_memory_capacity 2>&1)
|
|
|
|
# Parse result (main output is last line)
|
|
local main_result
|
|
main_result=$(echo "$result" | tail -1)
|
|
|
|
local total_required total_ram percentage status details
|
|
total_required=$(echo "$main_result" | cut -d'|' -f1)
|
|
total_ram=$(echo "$main_result" | cut -d'|' -f2)
|
|
percentage=$(echo "$main_result" | cut -d'|' -f3)
|
|
status=$(echo "$main_result" | cut -d'|' -f4)
|
|
|
|
# Display summary
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
cecho "${WHITE}${BOLD}MEMORY CAPACITY ANALYSIS${NC}"
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
cecho " Total Server RAM: ${WHITE}${total_ram}MB${NC}"
|
|
cecho " Required if ALL pools at max_children: ${WHITE}${total_required}MB${NC}"
|
|
cecho " Percentage of RAM: ${WHITE}${percentage}%${NC}"
|
|
echo ""
|
|
|
|
# Display status with color
|
|
case "$status" in
|
|
CRITICAL)
|
|
cecho " Status: ${RED}${BOLD}CRITICAL - HIGH OOM RISK!${NC}"
|
|
cecho ""
|
|
cecho " ${RED}WARNING: If all PHP-FPM pools hit their max_children limit,${NC}"
|
|
cecho " ${RED}the server will likely run out of memory and kill processes!${NC}"
|
|
;;
|
|
WARNING)
|
|
cecho " Status: ${YELLOW}${BOLD}WARNING - MODERATE OOM RISK${NC}"
|
|
cecho ""
|
|
cecho " ${YELLOW}CAUTION: Memory usage is high. Some pools may need reduction.${NC}"
|
|
;;
|
|
CAUTION)
|
|
cecho " Status: ${YELLOW}${BOLD}CAUTION - WATCH MEMORY USAGE${NC}"
|
|
cecho ""
|
|
cecho " ${YELLOW}Memory usage is elevated but manageable.${NC}"
|
|
;;
|
|
HEALTHY)
|
|
cecho " Status: ${GREEN}${BOLD}HEALTHY - LOW OOM RISK${NC}"
|
|
cecho ""
|
|
cecho " ${GREEN}Memory allocation appears safe.${NC}"
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
# Ask if user wants detailed breakdown
|
|
read -p "Show detailed per-user breakdown? (y/n): " show_details
|
|
|
|
if [[ "$show_details" =~ ^[Yy]$ ]]; then
|
|
echo ""
|
|
cecho "${WHITE}${BOLD}PER-USER BREAKDOWN${NC}"
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
echo ""
|
|
|
|
# Get details from stderr of previous call
|
|
local details_output
|
|
details_output=$(echo "$result" | head -n -1)
|
|
|
|
printf "%-20s %12s %12s %12s\n" "USER" "MAX_CHILDREN" "AVG/PROCESS" "MAX_MEMORY"
|
|
printf "%-20s %12s %12s %12s\n" "--------------------" "------------" "------------" "------------"
|
|
|
|
while IFS='|' read -r username max_children avg_mb pool_max_mb; do
|
|
[ -z "$username" ] && continue
|
|
printf "%-20s %12s %12s %12s\n" "$username" "$max_children" "$avg_mb" "$pool_max_mb"
|
|
done <<< "$details_output"
|
|
|
|
echo ""
|
|
fi
|
|
|
|
# Ask if user wants balanced recommendations
|
|
echo ""
|
|
read -p "Calculate balanced memory allocation recommendations? (y/n): " show_recommendations
|
|
|
|
if [[ "$show_recommendations" =~ ^[Yy]$ ]]; then
|
|
echo ""
|
|
cecho "${WHITE}${BOLD}BALANCED MEMORY ALLOCATION RECOMMENDATIONS${NC}"
|
|
cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}"
|
|
echo ""
|
|
cecho "${YELLOW}Calculating optimal max_children based on traffic...${NC}"
|
|
echo ""
|
|
|
|
local recommendations
|
|
recommendations=$(calculate_balanced_memory_allocation 2>/dev/null)
|
|
|
|
# Display header
|
|
local header
|
|
header=$(echo "$recommendations" | head -1)
|
|
echo "$header" | awk -F'|' '{printf "%-15s %8s %10s %12s %12s %15s %20s\n", $1, $2, $3, $4, $5, $6, $7}'
|
|
echo "--------------------------------------------------------------------------------------------------------"
|
|
|
|
# Display recommendations
|
|
echo "$recommendations" | tail -n +2 | while IFS='|' read -r user current_max avg_mb traffic_rpm recommended_max allocated_mb reason; do
|
|
[ -z "$user" ] && continue
|
|
|
|
# Color code reason
|
|
if [[ "$reason" == *"REDUCE"* ]]; then
|
|
color="${RED}"
|
|
elif [[ "$reason" == *"INCREASE"* ]]; then
|
|
color="${GREEN}"
|
|
else
|
|
color="${WHITE}"
|
|
fi
|
|
|
|
printf "%-15s %8s %10s %12s ${color}%12s${NC} %15s %20s\n" "$user" "$current_max" "$avg_mb" "$traffic_rpm" "$recommended_max" "$allocated_mb" "$reason"
|
|
done
|
|
|
|
echo ""
|
|
cecho "${YELLOW}NOTE: These are recommendations based on proportional traffic.${NC}"
|
|
cecho "${YELLOW}Actual needs may vary. Always test changes carefully.${NC}"
|
|
echo ""
|
|
fi
|
|
|
|
echo ""
|
|
cecho "${CYAN}═══════════════════════════════════════════════════════════════════${NC}"
|
|
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
|
|
# ============================================================================
|
|
|
|
main() {
|
|
# Detect system
|
|
detect_system
|
|
|
|
# Check if running as root
|
|
if [ "$EUID" -ne 0 ]; then
|
|
cecho "${RED}ERROR: This script must be run as root${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Check for PHP-FPM
|
|
if ! is_using_php_fpm; then
|
|
cecho "${YELLOW}WARNING: PHP-FPM not detected. Some features may not work.${NC}"
|
|
read -p "Press Enter to continue anyway..."
|
|
fi
|
|
|
|
# Main loop
|
|
while true; do
|
|
show_banner
|
|
show_main_menu
|
|
|
|
read -p "Select option: " choice
|
|
|
|
case "$choice" in
|
|
1)
|
|
analyze_single_domain
|
|
;;
|
|
2)
|
|
analyze_all_domains
|
|
;;
|
|
3)
|
|
quick_health_check
|
|
;;
|
|
4)
|
|
optimize_domain
|
|
;;
|
|
5)
|
|
cecho "${YELLOW}Server-wide optimization not yet implemented${NC}"
|
|
sleep 2
|
|
;;
|
|
6)
|
|
view_opcache_stats
|
|
;;
|
|
7)
|
|
view_fpm_stats
|
|
;;
|
|
8)
|
|
check_config_issues
|
|
;;
|
|
9)
|
|
check_server_memory_capacity
|
|
;;
|
|
b)
|
|
backup_configurations
|
|
;;
|
|
r)
|
|
restore_configurations
|
|
;;
|
|
q|Q)
|
|
cecho "${GREEN}Exiting PHP Optimizer...${NC}"
|
|
exit 0
|
|
;;
|
|
*)
|
|
cecho "${RED}Invalid option${NC}"
|
|
sleep 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Run main
|
|
main "$@"
|