diff --git a/app/Console/Server/ServerOptimizeCommand.php b/app/Console/Server/ServerOptimizeCommand.php new file mode 100644 index 00000000..5a1f88b5 --- /dev/null +++ b/app/Console/Server/ServerOptimizeCommand.php @@ -0,0 +1,169 @@ +addOption('server', null, InputOption::VALUE_REQUIRED, 'Server name'); + } + + // ---- + // Execution + // ---- + + protected function execute(InputInterface $input, OutputInterface $output): int + { + parent::execute($input, $output); + + $this->heading('Optimize Server'); + + // + // Select server & display details + // ---- + + $server = $this->selectServer(); + + if (is_int($server)) { + return $server; + } + + $this->displayServerDeets($server); + + // + // Get server info (includes hardware detection) + // ---- + + $info = $this->getServerInfo($server); + + if (is_int($info)) { + return $info; + } + + // Extract hardware info + if (!isset($info['hardware']) || !is_array($info['hardware'])) { + $this->io->error('Server hardware information not available. Run server:install first.'); + + return Command::FAILURE; + } + + $hardware = $info['hardware']; + $cpuCores = $hardware['cpu_cores'] ?? '1'; + $ramMb = $hardware['ram_mb'] ?? '512'; + $diskType = $hardware['disk_type'] ?? 'hdd'; + + /** @var string $cpuCores */ + /** @var string $ramMb */ + /** @var string $diskType */ + + $permissions = $info['permissions'] ?? 'none'; + /** @var string $permissions */ + + // + // Display hardware summary + // ---- + + $this->io->writeln([ + '', + 'Hardware Configuration:', + " CPU Cores: {$cpuCores}", + " RAM: {$ramMb}MB", + " Disk: {$diskType}", + '', + ]); + + // + // Confirm optimization + // ---- + + $confirmed = $this->io->promptConfirm( + 'Apply hardware-optimized settings to PHP-FPM and OPcache?', + default: true + ); + + if (!$confirmed) { + $this->io->info('Optimization cancelled'); + + return Command::SUCCESS; + } + + // + // Execute optimization playbook + // ---- + + $result = $this->executePlaybook( + $server, + 'server-optimize', + 'Optimizing server...', + [ + 'DEPLOYER_PERMS' => $permissions, + 'DEPLOYER_CPU_CORES' => $cpuCores, + 'DEPLOYER_RAM_MB' => $ramMb, + 'DEPLOYER_DISK_TYPE' => $diskType, + ], + true + ); + + if (is_int($result)) { + $this->io->error('Server optimization failed'); + + return $result; + } + + $this->yay('Server optimization completed successfully'); + + // + // Display applied settings + // ---- + + if (isset($result['php_fpm_settings']) && is_string($result['php_fpm_settings'])) { + $this->io->writeln([ + '', + 'Applied Settings:', + " PHP-FPM: {$result['php_fpm_settings']}", + ]); + } + + if (isset($result['opcache_settings']) && is_string($result['opcache_settings'])) { + $this->io->writeln([ + " OPcache: {$result['opcache_settings']}", + '', + ]); + } + + // + // Show command replay + // ---- + + $this->showCommandReplay('server:optimize', [ + 'server' => $server->name, + ]); + + return Command::SUCCESS; + } +} diff --git a/app/Traits/ServersTrait.php b/app/Traits/ServersTrait.php index 8ebc55fe..3e77346c 100644 --- a/app/Traits/ServersTrait.php +++ b/app/Traits/ServersTrait.php @@ -144,6 +144,38 @@ protected function displayServerInfo(array $info): void $this->io->displayDeets($deets); $this->io->writeln(''); + // Display hardware information if available + if (isset($info['hardware']) && is_array($info['hardware'])) { + $hardwareItems = []; + + if (isset($info['hardware']['cpu_cores'])) { + /** @var int|string $cpuCores */ + $cpuCores = $info['hardware']['cpu_cores']; + $coresText = $cpuCores === '1' || $cpuCores === 1 ? '1 core' : "{$cpuCores} cores"; + $hardwareItems[] = "CPU: {$coresText}"; + } + + if (isset($info['hardware']['ram_mb'])) { + /** @var int|string $ramMb */ + $ramMb = $info['hardware']['ram_mb']; + $ramGb = round((int) $ramMb / 1024, 1); + $ramText = $ramGb >= 1 ? "{$ramGb} GB" : "{$ramMb} MB"; + $hardwareItems[] = "RAM: {$ramText}"; + } + + if (isset($info['hardware']['disk_type'])) { + /** @var string $diskType */ + $diskType = $info['hardware']['disk_type']; + $diskText = strtoupper($diskType); + $hardwareItems[] = "Disk: {$diskText}"; + } + + if (count($hardwareItems) > 0) { + $this->io->displayDeets(['Hardware' => $hardwareItems]); + $this->io->writeln(''); + } + } + $services = []; // Add listening ports if any diff --git a/playbooks/server-info.sh b/playbooks/server-info.sh index fde041fa..14151ccb 100755 --- a/playbooks/server-info.sh +++ b/playbooks/server-info.sh @@ -143,22 +143,64 @@ ensure_tools() { local family=$1 perms=$2 export DEPLOYER_PERMS=$perms - # If the command is already installed, return - command -v ss > /dev/null 2>&1 && return 0 - command -v netstat > /dev/null 2>&1 && return 0 + # Check if required tools exist + command -v ss > /dev/null 2>&1 && command -v lsblk > /dev/null 2>&1 && return 0 case $family in debian) run_cmd apt-get update -q 2> /dev/null - run_cmd apt-get install -y -q iproute2 2> /dev/null + run_cmd apt-get install -y -q iproute2 util-linux 2> /dev/null ;; fedora | redhat | amazon) - run_cmd yum install -y -q iproute 2> /dev/null \ - || run_cmd dnf install -y -q iproute 2> /dev/null + run_cmd yum install -y -q iproute util-linux 2> /dev/null \ + || run_cmd dnf install -y -q iproute util-linux 2> /dev/null ;; esac } +# +# Hardware Detection +# ---- + +# +# Detect CPU core count + +detect_cpu_cores() { + nproc 2> /dev/null || echo "1" +} + +# +# Detect total system RAM in MB + +detect_ram_mb() { + free -m 2> /dev/null | awk 'NR==2 {print $2}' || echo "512" +} + +# +# Detect disk type (ssd or hdd) + +detect_disk_type() { + local disc_gran rotation + + # Primary detection: Check discard granularity (TRIM support = SSD) + # Works reliably in virtualized environments (cloud VMs) + disc_gran=$(lsblk -d -o name,disc-gran 2> /dev/null | grep -E "^[sv]da" | head -n1 | awk '{print $2}') + + # If disc-gran is non-zero (e.g., "512B"), it's an SSD + if [[ -n $disc_gran && $disc_gran != "0B" ]]; then + echo "ssd" + return + fi + + # Fallback: Check rotation flag (works for physical disks) + rotation=$(lsblk -d -o name,rota 2> /dev/null | grep -E "^[sv]da" | head -n1 | awk '{print $2}') + if [[ $rotation == "0" ]]; then + echo "ssd" + else + echo "hdd" + fi +} + # ---- # Service Metrics # ---- @@ -332,6 +374,7 @@ get_php_fpm_metrics() { main() { local distro family permissions + local cpu_cores ram_mb disk_type # # Gather basic info @@ -343,6 +386,11 @@ main() { echo "✓ Checking permissions..." permissions=$(check_permissions) + echo "✓ Detecting hardware..." + cpu_cores=$(detect_cpu_cores) + ram_mb=$(detect_ram_mb) + disk_type=$(detect_disk_type) + echo "✓ Cataloging services..." ensure_tools "$family" "$permissions" @@ -374,6 +422,10 @@ main() { distro: $distro family: $family permissions: $permissions + hardware: + cpu_cores: $cpu_cores + ram_mb: $ram_mb + disk_type: $disk_type caddy: available: $caddy_available version: ${caddy_version:-unknown} diff --git a/playbooks/server-optimize.sh b/playbooks/server-optimize.sh new file mode 100644 index 00000000..fd721b66 --- /dev/null +++ b/playbooks/server-optimize.sh @@ -0,0 +1,273 @@ +#!/usr/bin/env bash +# +# Server Optimization Playbook - Ubuntu/Debian Only +# +# Optimize PHP-FPM and OPcache based on server hardware +# ---- +# +# Required Environment Variables: +# DEPLOYER_OUTPUT_FILE - Output file path +# DEPLOYER_PERMS - Permissions: root|sudo +# DEPLOYER_CPU_CORES - Number of CPU cores +# DEPLOYER_RAM_MB - Total RAM in MB +# DEPLOYER_DISK_TYPE - Disk type: ssd|hdd +# +# Returns YAML with: +# - status: success +# - php_fpm_settings: applied settings +# - opcache_settings: applied settings +# + +set -o pipefail +export DEBIAN_FRONTEND=noninteractive + +[[ -z $DEPLOYER_OUTPUT_FILE ]] && echo "Error: DEPLOYER_OUTPUT_FILE required" && exit 1 +[[ -z $DEPLOYER_PERMS ]] && echo "Error: DEPLOYER_PERMS required" && exit 1 +[[ -z $DEPLOYER_CPU_CORES ]] && echo "Error: DEPLOYER_CPU_CORES required" && exit 1 +[[ -z $DEPLOYER_RAM_MB ]] && echo "Error: DEPLOYER_RAM_MB required" && exit 1 +[[ -z $DEPLOYER_DISK_TYPE ]] && echo "Error: DEPLOYER_DISK_TYPE required" && exit 1 +export DEPLOYER_PERMS + +# +# Helper Functions +# ---- + +run_cmd() { + if [[ $DEPLOYER_PERMS == 'root' ]]; then + "$@" + else + sudo -n "$@" + fi +} + +# +# Calculation Functions +# ---- + +# +# Calculate optimal PHP-FPM process manager settings based on RAM + +calculate_php_fpm_settings() { + local ram_mb=$1 + local avg_process_mb=40 + local max_children=$(((ram_mb * 75 / 100) / avg_process_mb)) + + # Clamp to reasonable bounds + [[ $max_children -lt 5 ]] && max_children=5 + [[ $max_children -gt 200 ]] && max_children=200 + + if ((ram_mb < 1024)); then + echo "ondemand|$max_children|10s" + elif ((ram_mb >= 8192)); then + local start_servers=$((max_children * 60 / 100)) + echo "static|$max_children|$start_servers" + else + local start_servers=$((max_children * 25 / 100)) + local min_spare=$((max_children * 15 / 100)) + local max_spare=$((max_children * 35 / 100)) + echo "dynamic|$max_children|$start_servers|$min_spare|$max_spare" + fi +} + +# +# Calculate OPcache memory settings based on hardware + +calculate_opcache_settings() { + local ram_mb=$1 + local cpu_cores=$2 + local opcache_memory jit_buffer interned_strings + + if ((ram_mb < 1024)); then + opcache_memory=64 + interned_strings=8 + jit_buffer=32 + elif ((ram_mb < 2048)); then + opcache_memory=128 + interned_strings=8 + jit_buffer=64 + elif ((ram_mb < 4096)); then + opcache_memory=256 + interned_strings=16 + jit_buffer=64 + else + opcache_memory=512 + interned_strings=16 + jit_buffer=128 + fi + + echo "$opcache_memory|$jit_buffer|$interned_strings" +} + +# +# Configuration Functions +# ---- + +# +# Apply PHP-FPM process manager optimization + +configure_php_fpm_pool() { + local ram_mb=$1 + local pool_conf="/etc/php/8.4/fpm/pool.d/www.conf" + + if ! run_cmd test -f "$pool_conf"; then + echo "Error: PHP-FPM pool config not found: $pool_conf" >&2 + exit 1 + fi + + echo "✓ Configuring PHP-FPM process manager..." + + local settings pm max_children param3 param4 param5 + IFS='|' read -r pm max_children param3 param4 param5 <<< "$(calculate_php_fpm_settings "$ram_mb")" + + # Apply process manager settings + if ! run_cmd sed -i "s/^pm = .*/pm = $pm/" "$pool_conf"; then + echo "Error: Failed to set pm mode" >&2 + exit 1 + fi + + if ! run_cmd sed -i "s/^pm.max_children = .*/pm.max_children = $max_children/" "$pool_conf"; then + echo "Error: Failed to set pm.max_children" >&2 + exit 1 + fi + + if [[ $pm == "ondemand" ]]; then + if ! run_cmd sed -i "s/^;*pm.process_idle_timeout = .*/pm.process_idle_timeout = $param3/" "$pool_conf"; then + echo "Error: Failed to set pm.process_idle_timeout" >&2 + exit 1 + fi + elif [[ $pm == "static" ]]; then + if ! run_cmd sed -i "s/^;*pm.start_servers = .*/pm.start_servers = $param3/" "$pool_conf"; then + echo "Error: Failed to set pm.start_servers" >&2 + exit 1 + fi + else # dynamic + if ! run_cmd sed -i "s/^pm.start_servers = .*/pm.start_servers = $param3/" "$pool_conf"; then + echo "Error: Failed to set pm.start_servers" >&2 + exit 1 + fi + if ! run_cmd sed -i "s/^pm.min_spare_servers = .*/pm.min_spare_servers = $param4/" "$pool_conf"; then + echo "Error: Failed to set pm.min_spare_servers" >&2 + exit 1 + fi + if ! run_cmd sed -i "s/^pm.max_spare_servers = .*/pm.max_spare_servers = $param5/" "$pool_conf"; then + echo "Error: Failed to set pm.max_spare_servers" >&2 + exit 1 + fi + fi + + if ! run_cmd sed -i "s/^;*pm.max_requests = .*/pm.max_requests = 1000/" "$pool_conf"; then + echo "Error: Failed to set pm.max_requests" >&2 + exit 1 + fi + + echo "pm=$pm|max_children=$max_children" +} + +# +# Create optimized OPcache configuration + +configure_opcache() { + local ram_mb=$1 cpu_cores=$2 disk_type=$3 + local opcache_memory jit_buffer interned_strings + + IFS='|' read -r opcache_memory jit_buffer interned_strings <<< "$(calculate_opcache_settings "$ram_mb" "$cpu_cores")" + + echo "✓ Configuring OPcache..." + + if ! cat > /tmp/99-deployer-opcache.ini << EOF; then +; Deployer PHP - OPcache Performance Configuration +; Auto-generated based on server hardware: ${ram_mb}MB RAM, ${cpu_cores} CPU cores, ${disk_type} disk + +[opcache] +; Enable OPcache +opcache.enable=1 +opcache.enable_cli=1 + +; Memory settings +opcache.memory_consumption=${opcache_memory} +opcache.interned_strings_buffer=${interned_strings} + +; File cache settings +opcache.max_accelerated_files=20000 +opcache.validate_timestamps=1 +opcache.revalidate_freq=2 +opcache.fast_shutdown=1 + +; JIT compilation (PHP 8.0+) +opcache.jit_buffer_size=${jit_buffer}M +opcache.jit=tracing +EOF + echo "Error: Failed to create OPcache config" >&2 + exit 1 + fi + + # Add SSD-specific optimizations + if [[ $disk_type == "ssd" ]]; then + if ! cat >> /tmp/99-deployer-opcache.ini << EOF; then + +; SSD optimizations +opcache.file_cache=/var/cache/php/opcache +opcache.file_cache_only=0 +EOF + echo "Error: Failed to add SSD optimizations" >&2 + exit 1 + fi + + if ! run_cmd mkdir -p /var/cache/php/opcache; then + echo "Error: Failed to create OPcache directory" >&2 + exit 1 + fi + + if ! run_cmd chown www-data:www-data /var/cache/php/opcache; then + echo "Error: Failed to set OPcache directory ownership" >&2 + exit 1 + fi + + if ! run_cmd chmod 755 /var/cache/php/opcache; then + echo "Error: Failed to set OPcache directory permissions" >&2 + exit 1 + fi + fi + + if ! run_cmd mv /tmp/99-deployer-opcache.ini /etc/php/8.4/fpm/conf.d/99-deployer-opcache.ini; then + echo "Error: Failed to install OPcache config" >&2 + exit 1 + fi + + echo "memory=${opcache_memory}M|jit=${jit_buffer}M|strings=${interned_strings}M" +} + +# +# Main Execution +# ---- + +main() { + local php_fpm_result opcache_result + + # Apply optimizations + php_fpm_result=$(configure_php_fpm_pool "$DEPLOYER_RAM_MB") + opcache_result=$(configure_opcache "$DEPLOYER_RAM_MB" "$DEPLOYER_CPU_CORES" "$DEPLOYER_DISK_TYPE") + + # Restart PHP-FPM to apply changes + echo "✓ Restarting PHP-FPM..." + if ! run_cmd systemctl restart php8.4-fpm; then + echo "Error: Failed to restart PHP-FPM" >&2 + exit 1 + fi + + # Write output + if ! cat > "$DEPLOYER_OUTPUT_FILE" << EOF; then +status: success +hardware: + cpu_cores: $DEPLOYER_CPU_CORES + ram_mb: $DEPLOYER_RAM_MB + disk_type: $DEPLOYER_DISK_TYPE +php_fpm_settings: $php_fpm_result +opcache_settings: $opcache_result +EOF + echo "Error: Failed to write output file" >&2 + exit 1 + fi +} + +main "$@"