13d7054aa1
- 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>
609 lines
18 KiB
Bash
Executable File
609 lines
18 KiB
Bash
Executable File
#!/bin/bash
|
|
# PHP-FPM UI Module
|
|
# Handles all user interface: menus, prompts, displays, formatting
|
|
# Part of PHP Optimizer - Phase 3 Refactoring
|
|
|
|
# ============================================================================
|
|
# COLOR CODES & DISPLAY UTILITIES
|
|
# ============================================================================
|
|
|
|
# Define color codes (must be done first)
|
|
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 "$@"
|
|
}
|
|
|
|
# Print a separator line
|
|
print_separator() {
|
|
local char="${1:-─}"
|
|
cecho "${CYAN}$(printf '%0.s%s' {1..73} <<< "$char")${NC}"
|
|
}
|
|
|
|
# Print a visual section header
|
|
print_header() {
|
|
local title="$1"
|
|
echo ""
|
|
cecho "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${NC}"
|
|
printf "${CYAN}║${NC} %-71s ${CYAN}║${NC}\n" "${title}"
|
|
cecho "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# BANNER DISPLAY
|
|
# ============================================================================
|
|
|
|
show_banner() {
|
|
clear
|
|
cecho "${CYAN}╔══════════════════════════════════════════════════════════════════════╗${NC}"
|
|
cecho "${CYAN}║${WHITE} PHP & SERVER PERFORMANCE OPTIMIZER ${CYAN}║${NC}"
|
|
cecho "${CYAN}╚══════════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAIN MENU
|
|
# ============================================================================
|
|
|
|
show_main_menu() {
|
|
cecho "${WHITE}${BOLD}MAIN MENU${NC}"
|
|
print_separator
|
|
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}0${NC}) Exit"
|
|
echo ""
|
|
print_separator
|
|
}
|
|
|
|
# Get menu selection from user with validation
|
|
get_main_menu_choice() {
|
|
while true; do
|
|
read -p "Select option (0-9, b, r): " choice
|
|
|
|
if ! [[ "$choice" =~ ^([0-9]|[bBrR])$ ]]; then
|
|
echo ""
|
|
cecho "${RED}Invalid choice. Please enter 0-9, b, or r${NC}"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
echo "${choice,,}" # Return lowercase
|
|
break
|
|
done
|
|
}
|
|
|
|
# ============================================================================
|
|
# DOMAIN SELECTION
|
|
# ============================================================================
|
|
|
|
# Select a single domain from all available domains
|
|
select_domain() {
|
|
local action="${1:-analyze}"
|
|
|
|
cecho "${WHITE}${BOLD}SELECT DOMAIN${NC}"
|
|
echo ""
|
|
|
|
# Use php-scanner if available, otherwise use direct functions
|
|
local domains
|
|
local -A domain_to_user
|
|
|
|
if type enumerate_all_domains >/dev/null 2>&1; then
|
|
# Use new php-scanner module for enumeration
|
|
all_domains=$(enumerate_all_domains)
|
|
|
|
while IFS= read -r domain; do
|
|
[ -z "$domain" ] && continue
|
|
|
|
local owner
|
|
owner=$(find_domain_owner "$domain")
|
|
[ -z "$owner" ] && owner="unknown"
|
|
|
|
domain_to_user["$domain"]="$owner"
|
|
done <<< "$all_domains"
|
|
else
|
|
# Fallback to direct enumeration using sourced functions
|
|
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
|
|
|
|
declare -a domains_arr
|
|
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_arr+=("$domain")
|
|
domain_to_user["$domain"]="$username"
|
|
done <<< "$user_domains"
|
|
done <<< "$users"
|
|
fi
|
|
|
|
# Convert associative array keys to indexed array
|
|
declare -a domains_list
|
|
for domain in "${!domain_to_user[@]}"; do
|
|
domains_list+=("$domain")
|
|
done
|
|
|
|
# Sort domains alphabetically
|
|
IFS=$'\n' read -rd '' -a domains_list <<<"$(printf '%s\n' "${domains_list[@]}" | sort)"
|
|
|
|
if [ ${#domains_list[@]} -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 (${#domains_list[@]} total):${NC}"
|
|
echo ""
|
|
|
|
local index=1
|
|
for domain in "${domains_list[@]}"; do
|
|
local username="${domain_to_user[$domain]}"
|
|
local php_version="unknown"
|
|
|
|
if type detect_php_version_for_domain >/dev/null 2>&1; then
|
|
php_version=$(detect_php_version_for_domain "$username" "$domain" 2>/dev/null || echo "unknown")
|
|
fi
|
|
|
|
printf " ${GREEN}%-3d${NC}) %-40s ${CYAN}[${username}]${NC} ${YELLOW}(${php_version})${NC}\n" "$index" "$domain"
|
|
index=$((index + 1))
|
|
done
|
|
|
|
echo ""
|
|
print_separator
|
|
|
|
# Validate domain selection with retry loop
|
|
while true; do
|
|
read -p "Select domain number (or 'q' to cancel): " selection
|
|
|
|
if [[ "$selection" == "q" || "$selection" == "Q" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#domains_list[@]} ]; then
|
|
echo ""
|
|
cecho "${RED}Invalid selection. Please enter a number 1-${#domains_list[@]}${NC}"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
break
|
|
done
|
|
|
|
# Return selected domain and username
|
|
local selected_domain="${domains_list[$((selection - 1))]}"
|
|
local selected_user="${domain_to_user[$selected_domain]}"
|
|
|
|
echo "$selected_domain|$selected_user"
|
|
return 0
|
|
}
|
|
|
|
# Select multiple domains for batch operations
|
|
select_multiple_domains() {
|
|
local mode="${1:-all}" # all, pattern, filtered, user
|
|
|
|
cecho "${WHITE}${BOLD}SELECT DOMAINS (BATCH)${NC}"
|
|
echo ""
|
|
|
|
case "$mode" in
|
|
all)
|
|
cecho "${CYAN}Using ALL domains on server${NC}"
|
|
enumerate_all_domains
|
|
;;
|
|
pattern)
|
|
cecho "${CYAN}Filter by pattern (e.g., *.example.com):${NC}"
|
|
read -p "Enter pattern: " pattern
|
|
filter_domains_by_name "$pattern"
|
|
;;
|
|
user)
|
|
cecho "${CYAN}Filter by user/account:${NC}"
|
|
local users
|
|
users=$(enumerate_all_accounts)
|
|
|
|
local -a accounts_list
|
|
while IFS= read -r user; do
|
|
accounts_list+=("$user")
|
|
done <<< "$users"
|
|
|
|
local index=1
|
|
for user in "${accounts_list[@]}"; do
|
|
echo " $index) $user"
|
|
index=$((index + 1))
|
|
done
|
|
|
|
read -p "Select user number: " user_choice
|
|
if [[ "$user_choice" =~ ^[0-9]+$ ]] && [ "$user_choice" -ge 1 ] && [ "$user_choice" -le ${#accounts_list[@]} ]; then
|
|
enumerate_user_domains "${accounts_list[$((user_choice - 1))]}"
|
|
fi
|
|
;;
|
|
traffic)
|
|
cecho "${CYAN}Filter by minimum concurrent requests:${NC}"
|
|
read -p "Enter minimum concurrent requests (default: 100): " min_requests
|
|
min_requests=${min_requests:-100}
|
|
filter_domains_by_traffic "$min_requests" "above"
|
|
;;
|
|
needs_optimization)
|
|
cecho "${CYAN}Showing domains that need optimization...${NC}"
|
|
filter_domains_by_optimization_status "needs_optimization"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================================================
|
|
# SELECTION MENUS
|
|
# ============================================================================
|
|
|
|
# Show options for optimization selection
|
|
show_optimization_menu() {
|
|
echo ""
|
|
cecho "${WHITE}${BOLD}OPTIMIZATION OPTIONS${NC}"
|
|
print_separator
|
|
echo ""
|
|
cecho " ${GREEN}1${NC}) Adjust PM Mode (static/dynamic/ondemand)"
|
|
cecho " ${GREEN}2${NC}) Adjust pm.max_children"
|
|
cecho " ${GREEN}3${NC}) Adjust pm.min_spare_servers"
|
|
cecho " ${GREEN}4${NC}) Adjust pm.max_spare_servers"
|
|
cecho " ${GREEN}5${NC}) Apply All Recommendations"
|
|
echo ""
|
|
cecho " ${RED}0${NC}) Cancel"
|
|
echo ""
|
|
print_separator
|
|
}
|
|
|
|
get_optimization_choice() {
|
|
while true; do
|
|
read -p "Select option (0-5): " choice
|
|
|
|
if ! [[ "$choice" =~ ^[0-5]$ ]]; then
|
|
echo ""
|
|
cecho "${RED}Invalid choice. Please enter 0-5${NC}"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
echo "$choice"
|
|
break
|
|
done
|
|
}
|
|
|
|
# Show apply options menu
|
|
show_apply_menu() {
|
|
echo ""
|
|
cecho "${WHITE}${BOLD}APPLY CHANGES${NC}"
|
|
print_separator
|
|
echo ""
|
|
cecho " ${GREEN}1${NC}) Apply changes now"
|
|
cecho " ${GREEN}2${NC}) Show dry-run preview"
|
|
cecho " ${GREEN}3${NC}) Save recommendation to file"
|
|
echo ""
|
|
cecho " ${RED}0${NC}) Discard changes"
|
|
echo ""
|
|
print_separator
|
|
}
|
|
|
|
get_apply_choice() {
|
|
while true; do
|
|
read -p "Select option (0-3): " choice
|
|
|
|
if ! [[ "$choice" =~ ^[0-3]$ ]]; then
|
|
echo ""
|
|
cecho "${RED}Invalid choice. Please enter 0-3${NC}"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
echo "$choice"
|
|
break
|
|
done
|
|
}
|
|
|
|
# ============================================================================
|
|
# BACKUP/RESTORE MENUS
|
|
# ============================================================================
|
|
|
|
# Show backup selection menu
|
|
show_backup_menu() {
|
|
local backup_dir="${1:-.}"
|
|
|
|
echo ""
|
|
cecho "${WHITE}${BOLD}BACKUP CONFIGURATIONS${NC}"
|
|
echo ""
|
|
cecho "${CYAN}Available backups:${NC}"
|
|
echo ""
|
|
|
|
local backups
|
|
backups=$(find "$backup_dir" -maxdepth 1 -name "php-config-*.tar.gz" -type f 2>/dev/null | sort -r)
|
|
|
|
if [ -z "$backups" ]; then
|
|
cecho "${YELLOW}No backups found${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local index=1
|
|
declare -a backup_files
|
|
while IFS= read -r backup_file; do
|
|
[ -z "$backup_file" ] && continue
|
|
backup_files+=("$backup_file")
|
|
|
|
local timestamp
|
|
timestamp=$(stat -f %Sm -t "%Y-%m-%d %H:%M:%S" "$backup_file" 2>/dev/null || stat -c %y "$backup_file" 2>/dev/null | cut -d' ' -f1-2)
|
|
|
|
printf " ${GREEN}%-3d${NC}) ${CYAN}%s${NC}\n" "$index" "$(basename "$backup_file") - $timestamp"
|
|
index=$((index + 1))
|
|
done <<< "$backups"
|
|
|
|
echo ""
|
|
print_separator
|
|
|
|
while true; do
|
|
read -p "Select backup number (or 'q' to cancel): " selection
|
|
|
|
if [[ "$selection" == "q" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#backup_files[@]} ]; then
|
|
echo ""
|
|
cecho "${RED}Invalid selection. Please enter 1-${#backup_files[@]}${NC}"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
break
|
|
done
|
|
|
|
echo "${backup_files[$((selection - 1))]}"
|
|
return 0
|
|
}
|
|
|
|
# ============================================================================
|
|
# RESULT DISPLAY FUNCTIONS
|
|
# ============================================================================
|
|
|
|
# Display domain analysis results with formatting
|
|
display_domain_analysis() {
|
|
local domain="$1"
|
|
local analysis_output="$2"
|
|
|
|
print_header "Analysis Results for $domain"
|
|
|
|
cecho "$analysis_output"
|
|
|
|
echo ""
|
|
print_separator
|
|
}
|
|
|
|
# Display optimization results
|
|
display_optimization_results() {
|
|
local domain="$1"
|
|
local old_settings="$2"
|
|
local new_settings="$3"
|
|
|
|
print_header "Optimization Results for $domain"
|
|
|
|
cecho "${CYAN}Current Settings:${NC}"
|
|
cecho "$old_settings" | sed 's/^/ /'
|
|
|
|
echo ""
|
|
cecho "${GREEN}Recommended Settings:${NC}"
|
|
cecho "$new_settings" | sed 's/^/ /'
|
|
|
|
echo ""
|
|
print_separator
|
|
}
|
|
|
|
# Display comparison results (old vs new)
|
|
display_comparison() {
|
|
local title="$1"
|
|
local old_result="$2"
|
|
local new_result="$3"
|
|
|
|
print_header "$title"
|
|
|
|
cecho "${YELLOW}Legacy Algorithm:${NC}"
|
|
cecho "$old_result" | sed 's/^/ /'
|
|
|
|
echo ""
|
|
cecho "${GREEN}Improved Algorithm:${NC}"
|
|
cecho "$new_result" | sed 's/^/ /'
|
|
|
|
echo ""
|
|
print_separator
|
|
}
|
|
|
|
# Display progress bar for long operations
|
|
display_progress() {
|
|
local current="$1"
|
|
local total="$2"
|
|
local label="${3:-Progress}"
|
|
|
|
if [ -z "$total" ] || [ "$total" -eq 0 ]; then
|
|
return 0
|
|
fi
|
|
|
|
local percent=$((current * 100 / total))
|
|
local filled=$((percent / 5))
|
|
local empty=$((20 - filled))
|
|
|
|
printf "${label}: [%-20s] %3d%% (%d/%d)\r" \
|
|
"$(printf '#%.0s' $(seq 1 $filled))$(printf ' %.0s' $(seq 1 $empty))" \
|
|
"$percent" "$current" "$total"
|
|
}
|
|
|
|
# Display a spinner for indeterminate progress
|
|
display_spinner() {
|
|
local message="$1"
|
|
local pid="$2"
|
|
|
|
local -a spinner=( '⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏' )
|
|
|
|
while kill -0 "$pid" 2>/dev/null; do
|
|
for frame in "${spinner[@]}"; do
|
|
printf "\r${message} ${frame}"
|
|
sleep 0.1
|
|
done
|
|
done
|
|
|
|
printf "\r${message} ✓\n"
|
|
}
|
|
|
|
# ============================================================================
|
|
# CONFIRMATION DIALOGS
|
|
# ============================================================================
|
|
|
|
# Ask user for yes/no confirmation (from common-functions.sh)
|
|
confirm() {
|
|
local prompt="${1:-Continue?}"
|
|
local response
|
|
|
|
cecho "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
read -p "$prompt (y/n): " response
|
|
cecho "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
|
|
[[ "$response" =~ ^[yY]([eE][sS])?$ ]]
|
|
}
|
|
|
|
# Confirm operation with domain list preview
|
|
confirm_batch_operation() {
|
|
local action="$1"
|
|
local domain_list="$2"
|
|
local domain_count="${3:-1}"
|
|
|
|
echo ""
|
|
print_separator
|
|
cecho "${YELLOW}${BOLD}WARNING: About to $action on $domain_count domain(s)${NC}"
|
|
print_separator
|
|
echo ""
|
|
|
|
cecho "${CYAN}Affected domains:${NC}"
|
|
echo "$domain_list" | sed 's/^/ /'
|
|
|
|
echo ""
|
|
|
|
if ! confirm "Continue?"; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ============================================================================
|
|
# ERROR & STATUS MESSAGES
|
|
# ============================================================================
|
|
|
|
# Display error message
|
|
show_error() {
|
|
local message="$1"
|
|
echo ""
|
|
cecho "${RED}${BOLD}ERROR:${NC} $message"
|
|
echo ""
|
|
}
|
|
|
|
# Display warning message
|
|
show_warning() {
|
|
local message="$1"
|
|
echo ""
|
|
cecho "${YELLOW}${BOLD}WARNING:${NC} $message"
|
|
echo ""
|
|
}
|
|
|
|
# Display success message
|
|
show_success() {
|
|
local message="$1"
|
|
echo ""
|
|
cecho "${GREEN}${BOLD}SUCCESS:${NC} $message"
|
|
echo ""
|
|
}
|
|
|
|
# Display info message
|
|
show_info() {
|
|
local message="$1"
|
|
echo ""
|
|
cecho "${CYAN}${BOLD}INFO:${NC} $message"
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# UTILITY DISPLAY FUNCTIONS
|
|
# ============================================================================
|
|
|
|
# Show a key-value pair nicely formatted
|
|
show_setting() {
|
|
local label="$1"
|
|
local value="$2"
|
|
local color="${3:-$CYAN}"
|
|
|
|
printf " ${color}%-30s${NC}: %s\n" "$label" "$value"
|
|
}
|
|
|
|
# Show a list of items with numbering
|
|
show_numbered_list() {
|
|
local -a items=("$@")
|
|
local index=1
|
|
|
|
for item in "${items[@]}"; do
|
|
printf " ${GREEN}%-3d${NC}) %s\n" "$index" "$item"
|
|
index=$((index + 1))
|
|
done
|
|
}
|
|
|
|
# ============================================================================
|
|
# EXPORT ALL FUNCTIONS
|
|
# ============================================================================
|
|
|
|
export -f cecho
|
|
export -f print_separator
|
|
export -f print_header
|
|
export -f show_banner
|
|
export -f show_main_menu
|
|
export -f get_main_menu_choice
|
|
export -f select_domain
|
|
export -f select_multiple_domains
|
|
export -f show_optimization_menu
|
|
export -f get_optimization_choice
|
|
export -f show_apply_menu
|
|
export -f get_apply_choice
|
|
export -f show_backup_menu
|
|
export -f display_domain_analysis
|
|
export -f display_optimization_results
|
|
export -f display_comparison
|
|
export -f display_progress
|
|
export -f display_spinner
|
|
export -f confirm
|
|
export -f confirm_batch_operation
|
|
export -f show_error
|
|
export -f show_warning
|
|
export -f show_success
|
|
export -f show_info
|
|
export -f show_setting
|
|
export -f show_numbered_list
|