feat(server): add server:optimize command with hardware-based PHP tuning - #78
feat(server): add server:optimize command with hardware-based PHP tuning#78loadinglucian wants to merge 1 commit into
Conversation
- Add hardware detection to server-info playbook (CPU cores, RAM, disk type) - Create server:optimize command that applies optimal PHP-FPM and OPcache settings - Add server-optimize playbook with intelligent process manager selection - Support dynamic/ondemand/static PM modes based on RAM availability - Include SSD optimizations and JIT compilation tuning
WalkthroughThe PR adds server hardware detection and optimization capabilities. It introduces a new Changes
Sequence DiagramsequenceDiagram
actor User
participant ServerOptimizeCommand as server:optimize<br/>Command
participant ServersTrait as ServersTrait<br/>(getServerInfo)
participant ServerInfoPlaybook as playbooks/<br/>server-info.sh
participant OptimizePlaybook as playbooks/<br/>server-optimize.sh
User->>ServerOptimizeCommand: Execute command
ServerOptimizeCommand->>ServerOptimizeCommand: Display header & select server
ServerOptimizeCommand->>ServersTrait: Get server info
ServersTrait->>ServerInfoPlaybook: Execute playbook
ServerInfoPlaybook->>ServerInfoPlaybook: Detect hardware<br/>(CPU, RAM, disk)
ServerInfoPlaybook-->>ServersTrait: Return server info + hardware
ServersTrait-->>ServerOptimizeCommand: Return validated info
ServerOptimizeCommand->>User: Display hardware configuration
ServerOptimizeCommand->>User: Prompt for optimization approval
alt User Confirms
ServerOptimizeCommand->>OptimizePlaybook: Execute with hardware env vars
OptimizePlaybook->>OptimizePlaybook: Calculate PHP-FPM settings
OptimizePlaybook->>OptimizePlaybook: Calculate OPcache settings
OptimizePlaybook->>OptimizePlaybook: Apply configurations
OptimizePlaybook->>OptimizePlaybook: Restart PHP-FPM
OptimizePlaybook-->>ServerOptimizeCommand: Return results
ServerOptimizeCommand->>User: Display success + applied settings
else User Declines
ServerOptimizeCommand->>User: Cancel gracefully
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/Console/Server/ServerOptimizeCommand.php(1 hunks)app/Traits/ServersTrait.php(1 hunks)playbooks/server-info.sh(4 hunks)playbooks/server-optimize.sh(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php
📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)
**/*.php: Eliminate single-use methods: inline if a method is called only once
Cache computed values: initialize expensive calculations in the constructor
Avoid method call overhead: prefer direct property access when appropriate
Files:
app/Console/Server/ServerOptimizeCommand.phpapp/Traits/ServersTrait.php
🧠 Learnings (1)
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Extract complex orchestration shared by multiple Commands into dedicated Services
Applied to files:
app/Console/Server/ServerOptimizeCommand.php
🧬 Code graph analysis (3)
playbooks/server-optimize.sh (1)
playbooks/server-info.sh (1)
run_cmd(127-133)
app/Traits/ServersTrait.php (1)
app/Services/IOService.php (3)
info(474-477)displayDeets(543-566)writeln(463-469)
playbooks/server-info.sh (3)
playbooks/server-optimize.sh (1)
run_cmd(35-41)playbooks/demo-site.sh (1)
run_cmd(37-43)playbooks/server-install.sh (1)
run_cmd(49-55)
🪛 Shellcheck (0.11.0)
playbooks/server-optimize.sh
[warning] 119-119: settings appears unused. Verify use (or export if used externally).
(SC2034)
🔇 Additional comments (9)
app/Traits/ServersTrait.php (1)
147-177: LGTM! Hardware information display is well-implemented.The hardware detection and formatting logic is solid:
- Defensive checks ensure the hardware array exists before processing
- Type juggling between int/string is handled correctly
- RAM conversion logic appropriately displays GB when >= 1, MB otherwise
- Consistent formatting matches existing patterns in the codebase
app/Console/Server/ServerOptimizeCommand.php (1)
69-73: LGTM! Good error handling for missing hardware data.The defensive check ensures hardware information is available before proceeding, with a clear error message guiding users to run
server:installfirst.playbooks/server-info.sh (4)
146-147: LGTM! Tool check correctly updated for new requirements.Adding
lsblkto the tool check is appropriate since the newdetect_disk_type()function depends on it.
168-170: LGTM! CPU core detection is straightforward and reliable.Using
nprocwith a sensible fallback is the standard approach for detecting CPU cores.
175-177: LGTM! RAM detection is correctly implemented.The
free -mcommand with awk parsing is standard and reliable for extracting total system RAM.
182-202: LGTM! Disk type detection is well-designed with appropriate fallbacks.The two-tier detection strategy is solid:
- Primary:
disc-gran(TRIM support) works reliably in virtualized/cloud environments- Fallback:
rotationflag works for physical disksThe regex pattern
^[sv]dacorrectly matches the first vda or sda device. While technically it could match "vdaa" or "sdaa", the predictable space-separated format oflsblkoutput makes this a non-issue in practice.playbooks/server-optimize.sh (3)
50-70: LGTM! PHP-FPM calculation logic is sound.The process manager selection strategy is well-designed:
ondemandfor memory-constrained servers (< 1 GB)staticfor high-memory servers (>= 8 GB)dynamicfor mid-range serversThe 40 MB average process size and 75% RAM allocation are reasonable defaults, with appropriate bounds checking (5-200 children).
75-99: LGTM! OPcache settings scale appropriately with available RAM.The tiered configuration provides sensible defaults for different server sizes, with JIT buffer sizes that scale proportionally to OPcache memory.
205-230: LGTM! SSD-specific optimizations are well-implemented.The conditional SSD optimizations are appropriate:
- Creates a dedicated file cache directory
- Sets correct ownership (
www-data:www-data) and permissions (755)- Only enables file cache when SSD is detected
| $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([ | ||
| '', | ||
| '<fg=cyan>Hardware Configuration:</>', | ||
| " CPU Cores: <fg=yellow>{$cpuCores}</>", | ||
| " RAM: <fg=yellow>{$ramMb}MB</>", | ||
| " Disk: <fg=yellow>{$diskType}</>", | ||
| '', | ||
| ]); |
There was a problem hiding this comment.
Remove duplicate hardware display.
The hardware information is already displayed by getServerInfo() at line 62, which calls displayServerInfo() in ServersTrait. The second display at lines 91-98 shows the same information in a different format, creating redundant console output.
Apply this diff to remove the duplicate display:
}
- //
- // Display hardware summary
- // ----
-
- $this->io->writeln([
- '',
- '<fg=cyan>Hardware Configuration:</>',
- " CPU Cores: <fg=yellow>{$cpuCores}</>",
- " RAM: <fg=yellow>{$ramMb}MB</>",
- " Disk: <fg=yellow>{$diskType}</>",
- '',
- ]);
-
//
// Confirm optimization
// ----📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $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([ | |
| '', | |
| '<fg=cyan>Hardware Configuration:</>', | |
| " CPU Cores: <fg=yellow>{$cpuCores}</>", | |
| " RAM: <fg=yellow>{$ramMb}MB</>", | |
| " Disk: <fg=yellow>{$diskType}</>", | |
| '', | |
| ]); | |
| $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 */ | |
| // | |
| // Confirm optimization | |
| // ---- |
🤖 Prompt for AI Agents
In app/Console/Server/ServerOptimizeCommand.php around lines 62 to 98, the
hardware summary is being printed a second time (duplicate of displayServerInfo
invoked by getServerInfo()). Remove the redundant console output block that
writes the hardware summary (the $this->io->writeln([...]) section) so only the
original display from getServerInfo() remains; ensure any related empty lines or
comments are cleaned up to preserve formatting and return behavior.
| 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" | ||
| } |
There was a problem hiding this comment.
Hardcoded PHP version limits flexibility.
The path /etc/php/8.4/fpm/pool.d/www.conf hardcodes PHP 8.4, which will break when the PHP version changes. This same issue appears on line 232.
Consider one of these approaches:
Option 1: Detect installed PHP version
configure_php_fpm_pool() {
local ram_mb=$1
local php_version
php_version=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;")
local pool_conf="/etc/php/${php_version}/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
# ... rest of function
}Option 2: Pass PHP version as environment variable
configure_php_fpm_pool() {
local ram_mb=$1
local php_version=${DEPLOYER_PHP_VERSION:-8.4}
local pool_conf="/etc/php/${php_version}/fpm/pool.d/www.conf"
# ... rest of function
}🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 119-119: settings appears unused. Verify use (or export if used externally).
(SC2034)
🤖 Prompt for AI Agents
In playbooks/server-optimize.sh around lines 108 to 164 the PHP-FPM pool path is
hardcoded to /etc/php/8.4/fpm/pool.d/www.conf which will break for other PHP
versions; change the function to build pool_conf dynamically by obtaining the
installed PHP version (e.g., run php to read
PHP_MAJOR_VERSION.PHP_MINOR_VERSION) or use a DEPLOYER_PHP_VERSION environment
variable with a sensible default, assign
pool_conf="/etc/php/${php_version}/fpm/pool.d/www.conf", keep the existing
file-exists check and sed updates, and apply the same dynamic-path change for
the other hardcoded occurrence around line 232 so both places use the resolved
php_version with fallback and error handling.
|
|
||
| echo "✓ Configuring PHP-FPM process manager..." | ||
|
|
||
| local settings pm max_children param3 param4 param5 |
There was a problem hiding this comment.
Remove unused variable declaration.
The settings variable is declared but never used. The actual parsing happens directly into separate variables on line 120.
Apply this diff:
- local settings pm max_children param3 param4 param5
+ local pm max_children param3 param4 param5📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| local settings pm max_children param3 param4 param5 | |
| local pm max_children param3 param4 param5 |
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 119-119: settings appears unused. Verify use (or export if used externally).
(SC2034)
🤖 Prompt for AI Agents
In playbooks/server-optimize.sh around line 119, the variable list declares an
unused `settings`; remove `settings` from the local declaration so only used
variables remain (e.g., change `local settings pm max_children param3 param4
param5` to `local pm max_children param3 param4 param5`) to eliminate the unused
variable warning and keep the declaration accurate.
Summary by CodeRabbit