#!/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 MODEL
# ==============================================================================================
#
#   1. Read the current pm.max_children from the PHP-FPM pool config
#   2. Already at or above the target → exit silently, no write, no restart
#   3. Otherwise rewrite the value and restart PHP-FPM via the adapter
#   4. Verify PHP-FPM came back up
#
# Runs at array start, before the WebGUI sees real load. The setting does not survive an
# unRAID update — the OS replaces the pool config — which is why this reapplies every boot
# rather than being a one-time install step.
#
# ==============================================================================================
# 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

echo "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
    echo "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