#!/bin/bash # ============================================================================================== # ============================= PHP-FPM Max Children =========================================== # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Raises PHP-FPM pm.max_children to prevent WebGUI slowdowns under load. Run # once at array start via ARRAY_START_SCRIPTS. Idempotent — silent when the # value is already correct, no restart on clean boot. # # unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very # low (4–8). Under load — multiple users, Docker operations, heavy dashboard # usage — all PHP workers saturate and new requests queue. The WebGUI becomes # slow or unresponsive. # # PHP_MAX_CHILDREN=250 is appropriate for 128GB RAM: ~2MB per worker = ~500MB # total. Too high wastes RAM; too low causes slowdowns. # # ============================================================================================== # DESIGN PRINCIPLES # ============================================================================================== # # Idempotent Content Check # Reads the current pm.max_children value before writing. If already at # target → silent exit, no PHP-FPM restart. Restarting PHP-FPM unnecessarily # disrupts active WebGUI sessions on every boot. # # Pattern Match Before Write # Verifies the sed pattern finds pm.max_children in the config before # applying any change. Prevents silent failures where sed succeeds but # writes nothing because the key was missing or commented out. # # Apply Sequence # 1. Read current pm.max_children from PHP_CONF # 2. If already at target → exit silently # 3. Verify sed pattern matches before writing # 4. Apply sed replacement # 5. Restart PHP-FPM via rc.php-fpm # 6. Verify PHP-FPM process running after restart # 7. Read back config to confirm value applied # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # Root Required # Writing to /etc/php83/ requires root. # # Single Instance Lock # acquire_lock prevents concurrent runs at array start. # # Process Verify # Confirms PHP-FPM running after restart — errors if it failed to start. # # Config Verify # Reads back config after restart to confirm the value was actually applied. # # Silent on Success # Runs every boot — no noise when already correct. # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # master.conf # # PHP_MAX_CHILDREN # Target pm.max_children value. (default: 250) # # PHP_CONF # Path to PHP-FPM www.conf. (default: /etc/php83/php-fpm.d/www.conf) # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # php_fpm_max_children.sh # Read current value. Update and restart PHP-FPM only if changed. Silent if correct. # # php_fpm_max_children.sh --dry-run # Show current vs target value. No config write or restart. # # php_fpm_max_children.sh --status # Show current pm.max_children, target, and PHP-FPM process state. # # php_fpm_max_children.sh --log # Verbose output showing idempotent check, config write, and restart result. # # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../../../load_config.sh" parse_args "$@" # ============================================================================================== # ━━━ Setup ━━━ # ============================================================================================== if [[ "$EUID" -ne 0 ]]; then error "Must be run as root — writing system config requires root" exit 1 fi platform_require_cmd \ "/usr/local/emhttp/plugins/dynamix/scripts/notify" \ "" "" \ "unRAID notify script" || warn "unRAID notify script not found — native notifications disabled" validate_int PHP_MAX_CHILDREN "$PHP_MAX_CHILDREN" require_var PHP_CONF acquire_lock detect_hosts [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY PHP-FPM STATUS ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_GEAR Config file: $PHP_CONF" echo "$ICON_PHP Target: pm.max_children = $PHP_MAX_CHILDREN" echo "" if [[ -f "$PHP_CONF" ]]; then CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | \ awk '{print $NF}') if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then echo " $ICON_SUCCESS Current: pm.max_children = $CURRENT_VAL (correct ✅)" else echo " $ICON_WARN Current: pm.max_children = ${CURRENT_VAL:-not set} (would update)" fi else echo " $ICON_ERROR Config file not found: $PHP_CONF" fi echo "" if pgrep -f "php-fpm" >/dev/null 2>&1; then FPM_COUNT=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?") echo " $ICON_SUCCESS PHP-FPM: running ($FPM_COUNT worker(s))" else echo " $ICON_ERROR PHP-FPM: NOT running" fi echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ PHP-FPM Config ━━━ # ============================================================================================== START=$(date +%s) if [[ ! -f "$PHP_CONF" ]]; then error "PHP config file not found: $PHP_CONF" notify "PHP-FPM config not found on $(hostname) ($MY_ID) — $PHP_CONF missing" \ "PHP-FPM" "warning" exit 1 fi # ── Idempotent check ───────────────────────────────────────────────────────────────────────── CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}') if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then echo "pm.max_children already $PHP_MAX_CHILDREN ✅" exit 0 fi warn "pm.max_children: ${CURRENT_VAL:-not set} → $PHP_MAX_CHILDREN" # ── Verify pattern exists before writing ───────────────────────────────────────────────────── if ! grep -qE "^pm\.max_children" "$PHP_CONF" 2>/dev/null; then error "pm.max_children not found in $PHP_CONF — cannot apply" error "Add 'pm.max_children = $PHP_MAX_CHILDREN' to $PHP_CONF manually" notify "PHP-FPM pm.max_children not found in config on $(hostname) ($MY_ID)" \ "PHP-FPM" "warning" exit 1 fi if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would set pm.max_children = $PHP_MAX_CHILDREN in $PHP_CONF" warn "DRY RUN — would restart PHP-FPM" exit 0 fi # ── Apply setting ───────────────────────────────────────────────────────────────────────────── log "Applying pm.max_children = $PHP_MAX_CHILDREN..." if ! sed -i "s/^pm\.max_children.*/pm.max_children = $PHP_MAX_CHILDREN/" "$PHP_CONF"; then error "Failed to update $PHP_CONF" notify "PHP-FPM config update failed on $(hostname) ($MY_ID)" \ "PHP-FPM" "warning" exit 1 fi log "Config updated" # ── Restart PHP-FPM ────────────────────────────────────────────────────────────────────────── log "Restarting PHP-FPM..." if ! platform_restart_service php-fpm; then error "PHP-FPM restart command failed" notify "PHP-FPM restart failed on $(hostname) ($MY_ID)" \ "PHP-FPM" "warning" exit 1 fi sleep 3 # Allow PHP-FPM workers to initialise # ── Verify process running ──────────────────────────────────────────────────────────────────── if ! pgrep -f "php-fpm" >/dev/null 2>&1; then error "PHP-FPM not running after restart — WebGUI may be broken" notify "PHP-FPM failed to start after config update on $(hostname) ($MY_ID)" \ "PHP-FPM" "warning" exit 1 fi # ── Verify config reflects target ──────────────────────────────────────────────────────────── APPLIED_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}') if [[ "${APPLIED_VAL:-0}" -ne "$PHP_MAX_CHILDREN" ]]; then warn "Config reads pm.max_children = ${APPLIED_VAL:-unknown} — expected $PHP_MAX_CHILDREN" warn "Check $PHP_CONF manually" else log "Verified: pm.max_children = $APPLIED_VAL ✅" fi FPM_WORKERS=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?") log "$ICON_PHP Workers running: $FPM_WORKERS" END=$(date +%s) # ============================================================================================== # ━━━ Summary ━━━ # ============================================================================================== echo "" echo "━━━━━ $ICON_SUMMARY PHP-FPM SUMMARY ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_GEAR Config file: $PHP_CONF" echo "$ICON_PHP Applied: pm.max_children = $PHP_MAX_CHILDREN" echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" echo "" echo "$ICON_DONE Status: done ✅" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" exit 0