diff --git a/app/Traits/ServersTrait.php b/app/Traits/ServersTrait.php index 8129cfa4..8ebc55fe 100644 --- a/app/Traits/ServersTrait.php +++ b/app/Traits/ServersTrait.php @@ -161,6 +161,156 @@ protected function displayServerInfo(array $info): void $this->io->displayDeets(['Services' => $services]); $this->io->writeln(''); + + // Display Caddy information if available + if (isset($info['caddy']) && is_array($info['caddy']) && ($info['caddy']['available'] ?? false) === true) { + $caddyItems = []; + + if (isset($info['caddy']['version']) && $info['caddy']['version'] !== 'unknown') { + /** @var string $version */ + $version = $info['caddy']['version']; + $caddyItems[] = 'Version: '.$version; + } + + if (isset($info['caddy']['uptime_seconds'])) { + /** @var int|string|float $rawUptime */ + $rawUptime = $info['caddy']['uptime_seconds']; + /** @var int $uptimeSeconds */ + $uptimeSeconds = (int) $rawUptime; + $caddyItems[] = 'Uptime: '.$this->formatUptime($uptimeSeconds); + } + + if (isset($info['caddy']['total_requests'])) { + /** @var int|string|float $rawTotalReq */ + $rawTotalReq = $info['caddy']['total_requests']; + /** @var int $totalReq */ + $totalReq = (int) $rawTotalReq; + $caddyItems[] = 'Total Requests: '.number_format($totalReq); + } + + if (isset($info['caddy']['active_requests'])) { + /** @var int|string $activeRequests */ + $activeRequests = $info['caddy']['active_requests']; + $caddyItems[] = 'Active Requests: '.$activeRequests; + } + + if (isset($info['caddy']['memory_mb']) && $info['caddy']['memory_mb'] !== '0') { + /** @var string $memoryMb */ + $memoryMb = $info['caddy']['memory_mb']; + $caddyItems[] = 'Memory: '.$memoryMb.' MB'; + } + + if (count($caddyItems) > 0) { + $this->io->displayDeets(['Caddy' => $caddyItems]); + $this->io->writeln(''); + } + } + + // Display PHP-FPM information if available + if (isset($info['php_fpm']) && is_array($info['php_fpm']) && ($info['php_fpm']['available'] ?? false) === true) { + $phpFpmItems = []; + + if (isset($info['php_fpm']['pool']) && $info['php_fpm']['pool'] !== 'unknown') { + /** @var string $pool */ + $pool = $info['php_fpm']['pool']; + $phpFpmItems[] = 'Pool: '.$pool; + } + + if (isset($info['php_fpm']['process_manager']) && $info['php_fpm']['process_manager'] !== 'unknown') { + /** @var string $processManager */ + $processManager = $info['php_fpm']['process_manager']; + $phpFpmItems[] = 'Process Manager: '.$processManager; + } + + if (isset($info['php_fpm']['active_processes'])) { + /** @var int|string $activeProcesses */ + $activeProcesses = $info['php_fpm']['active_processes']; + $phpFpmItems[] = 'Active: '.$activeProcesses.' processes'; + } + + if (isset($info['php_fpm']['idle_processes'])) { + /** @var int|string $idleProcesses */ + $idleProcesses = $info['php_fpm']['idle_processes']; + $phpFpmItems[] = 'Idle: '.$idleProcesses.' processes'; + } + + if (isset($info['php_fpm']['total_processes'])) { + /** @var int|string $totalProcesses */ + $totalProcesses = $info['php_fpm']['total_processes']; + $phpFpmItems[] = 'Total: '.$totalProcesses.' processes'; + } + + if (isset($info['php_fpm']['listen_queue'])) { + /** @var int|string|float $rawQueue */ + $rawQueue = $info['php_fpm']['listen_queue']; + /** @var int $queue */ + $queue = (int) $rawQueue; + $queueDisplay = $queue > 0 ? "{$queue} waiting" : '0 waiting'; + $phpFpmItems[] = 'Queue: '.$queueDisplay; + } + + if (isset($info['php_fpm']['accepted_conn'])) { + /** @var int|string|float $rawAccepted */ + $rawAccepted = $info['php_fpm']['accepted_conn']; + /** @var int $accepted */ + $accepted = (int) $rawAccepted; + $phpFpmItems[] = 'Accepted: '.number_format($accepted); + } + + if (isset($info['php_fpm']['max_children_reached'])) { + /** @var int|string|float $rawMaxChildren */ + $rawMaxChildren = $info['php_fpm']['max_children_reached']; + /** @var int $maxChildren */ + $maxChildren = (int) $rawMaxChildren; + if ($maxChildren > 0) { + $phpFpmItems[] = "Max Children Reached: {$maxChildren}"; + } + } + + if (isset($info['php_fpm']['slow_requests'])) { + /** @var int|string|float $rawSlowReqs */ + $rawSlowReqs = $info['php_fpm']['slow_requests']; + /** @var int $slowReqsInt */ + $slowReqsInt = (int) $rawSlowReqs; + if ($slowReqsInt > 0) { + $slowReqs = number_format($slowReqsInt); + $phpFpmItems[] = "Slow Requests: {$slowReqs}"; + } + } + + if (count($phpFpmItems) > 0) { + $this->io->displayDeets(['PHP-FPM' => $phpFpmItems]); + $this->io->writeln(''); + } + } + } + + /** + * Format uptime seconds into human-readable string. + */ + private function formatUptime(int $seconds): string + { + if ($seconds < 60) { + return "{$seconds}s"; + } + + if ($seconds < 3600) { + $minutes = floor($seconds / 60); + + return "{$minutes}m"; + } + + if ($seconds < 86400) { + $hours = floor($seconds / 3600); + $minutes = floor(($seconds % 3600) / 60); + + return "{$hours}h {$minutes}m"; + } + + $days = floor($seconds / 86400); + $hours = floor(($seconds % 86400) / 3600); + + return "{$days}d {$hours}h"; } // diff --git a/playbooks/demo-site.sh b/playbooks/demo-site.sh index e6802659..0852d37e 100644 --- a/playbooks/demo-site.sh +++ b/playbooks/demo-site.sh @@ -3,10 +3,11 @@ # # Demo Site Setup Playbook - Ubuntu/Debian Only # -# Provision demo site (requires deployer user pre-configured) +# Provision demo site (requires deployer user and Caddy structure pre-configured) # ---- # # This playbook only supports Ubuntu and Debian distributions (debian family). +# Requires server-install to have run first (creates /etc/caddy/conf.d/sites/ directory). # # Required Environment Variables: # DEPLOYER_OUTPUT_FILE - Output file path @@ -16,7 +17,7 @@ # - status: success # - demo_site_path: /home/deployer/demo/public # - deployer_user: existing -# - caddy_configured: true +# - demo_site_configured: true # set -o pipefail @@ -26,7 +27,7 @@ export DEBIAN_FRONTEND=noninteractive [[ -z $DEPLOYER_PERMS ]] && echo "Error: DEPLOYER_PERMS required" && exit 1 export DEPLOYER_PERMS -# +# ---- # Helpers # ---- @@ -41,8 +42,16 @@ run_cmd() { fi } -# +# ---- # Setup Functions +# ---- + +# +# Prerequisites +# ---- + +# +# Verify deployer user and home directory exist require_deployer_user() { if ! id -u deployer > /dev/null 2>&1; then @@ -56,6 +65,13 @@ require_deployer_user() { fi } +# +# Demo Site Setup +# ---- + +# +# Create demo site directory structure and files + setup_demo_site() { echo "✓ Setting up demo site..." @@ -106,8 +122,15 @@ setup_demo_site() { fi } -configure_caddy() { - echo "✓ Configuring Caddy..." +# +# Caddy Configuration +# ---- + +# +# Configure Caddy for demo site + +configure_demo_site() { + echo "✓ Configuring demo site..." # PHP-FPM socket path (debian family) local php_fpm_socket='/run/php/php8.4-fpm.sock' @@ -125,15 +148,8 @@ configure_caddy() { exit 1 fi - # Create Caddyfile with logging - using the simplest PHP configuration - if ! run_cmd tee /etc/caddy/Caddyfile > /dev/null <<- EOF; then - { - log { - output file /var/log/caddy/access.log - format json - } - } - + # Create demo.caddy - demo site configuration + if ! run_cmd tee /etc/caddy/conf.d/sites/demo.caddy > /dev/null <<- EOF; then # Demo site - HTTP only (can't get HTTPS cert for IP address) http://:80 { root * /home/deployer/demo/public @@ -148,11 +164,11 @@ configure_caddy() { format json } - # This single line handles everything: PHP files, index.php routing, and static files - php_fastcgi unix/${php_fpm_socket} + # This single line handles everything: PHP files, index.php routing, and static files + php_fastcgi unix/${php_fpm_socket} } EOF - echo "Error: Failed to create Caddyfile" >&2 + echo "Error: Failed to create demo.caddy" >&2 exit 1 fi @@ -177,7 +193,7 @@ configure_caddy() { fi } -# +# ---- # Main Execution # ---- @@ -185,14 +201,14 @@ main() { # Execute setup tasks require_deployer_user setup_demo_site - configure_caddy + configure_demo_site # Write output YAML if ! cat > "$DEPLOYER_OUTPUT_FILE" <<- EOF; then status: success demo_site_path: /home/deployer/demo/public deployer_user: existing - caddy_configured: true + demo_site_configured: true EOF echo "Error: Failed to write output file" >&2 exit 1 diff --git a/playbooks/server-info.sh b/playbooks/server-info.sh index a67e1757..fde041fa 100755 --- a/playbooks/server-info.sh +++ b/playbooks/server-info.sh @@ -2,7 +2,7 @@ # # Gather Server Information # ---- -# This playbook detects distribution, family, permissions, and listening services. +# This playbook detects distribution, family, permissions, listening services, Caddy metrics, and PHP-FPM metrics. # # Required Environment Variables: # DEPLOYER_OUTPUT_FILE - Output file path (provided automatically) @@ -11,6 +11,8 @@ # - distro: ubuntu|debian|fedora|centos|rocky|alma|rhel|amazon|unknown # - family: debian|fedora|redhat|amazon|unknown # - permissions: root|sudo|none +# - caddy: Caddy metrics (available, version, sites_count, domains, uptime_seconds, active_requests, total_requests, memory_mb) +# - php_fpm: PHP-FPM metrics (available, pool, process_manager, uptime_seconds, accepted_conn, listen_queue, idle_processes, active_processes, total_processes, max_children_reached, slow_requests) # - ports: map of port numbers to process names set -o pipefail @@ -22,9 +24,16 @@ if [[ -z $DEPLOYER_OUTPUT_FILE ]]; then exit 1 fi +# ---- +# Detection Functions +# ---- + # -# Detect Linux Distribution +# Distribution Detection # ---- + +# +# Detect Linux Distribution # Returns: exact distribution name (ubuntu|debian|fedora|centos|rocky|alma|rhel|amazon|unknown) detect_distro() { @@ -62,7 +71,6 @@ detect_distro() { # # Detect Distribution Family -# ---- # Returns: debian|fedora|redhat|amazon|unknown detect_family() { @@ -88,8 +96,11 @@ detect_family() { } # -# Check User Permissions +# Permission Detection # ---- + +# +# Check User Permissions # Returns: root|sudo|none check_permissions() { @@ -102,10 +113,17 @@ check_permissions() { fi } +# ---- +# Helper Functions +# ---- + # -# Execute Command with Appropriate Permissions +# Command Execution # ---- +# +# Execute command with appropriate permissions + run_cmd() { if [[ $DEPLOYER_PERMS == 'root' ]]; then "$@" @@ -115,9 +133,12 @@ run_cmd() { } # -# Ensure Required Tools are Installed +# Tool Installation # ---- +# +# Ensure required networking tools are installed + ensure_tools() { local family=$1 perms=$2 export DEPLOYER_PERMS=$perms @@ -138,10 +159,17 @@ ensure_tools() { esac } +# ---- +# Service Metrics +# ---- + # -# Get All Listening Services +# Listening Services # ---- +# +# Get all listening services and ports + get_listening_services() { local port process @@ -179,6 +207,126 @@ get_listening_services() { } # +# Caddy Metrics +# ---- + +# +# Query Caddy admin API and extract metrics + +get_caddy_metrics() { + # Check if Caddy admin API is available on port 2019 + if ! curl -sf --max-time 2 http://localhost:2019/config/ > /dev/null 2>&1; then + return 0 + fi + + # Get version + local version + version=$(caddy version 2> /dev/null | head -n1 | awk '{print $1}') + [[ -z $version ]] && version="unknown" + + # Get config to count sites and extract domains + local config sites_count domains + config=$(curl -sf --max-time 2 http://localhost:2019/config/apps/http/servers 2> /dev/null || echo "{}") + + # Count configured sites (count server entries) + sites_count=$(echo "$config" | grep -c '"listen"' 2> /dev/null) + [[ -z $sites_count ]] && sites_count="0" + + # Extract listening addresses/domains (simplified - gets host matchers) + domains=$(echo "$config" | grep -o '"host":\s*\["[^"]*"\]' | sed 's/"host":\s*\["\([^"]*\)"\]/\1/g' | tr '\n' ',' | sed 's/,$//' || echo "") + + # Get uptime from process (Caddy process start time) + local uptime_seconds caddy_pid + if caddy_pid=$(pgrep -x caddy 2> /dev/null | head -n1); then + uptime_seconds=$(ps -p "$caddy_pid" -o etimes= 2> /dev/null | tr -d ' ') + [[ -z $uptime_seconds ]] && uptime_seconds="0" + else + uptime_seconds="0" + fi + + # Get basic metrics from admin API + local metrics active_requests total_requests + metrics=$(curl -sf --max-time 2 http://localhost:2019/metrics 2> /dev/null || echo "") + + # Parse Prometheus metrics for useful stats + # Active requests: sum of caddy_http_requests_in_flight (across all handlers) + active_requests=$(echo "$metrics" | grep '^caddy_http_requests_in_flight{' | awk '{sum+=$2} END {print sum+0}') + [[ -z $active_requests ]] && active_requests="0" + + # Total requests: sum across all handlers + total_requests=$(echo "$metrics" | grep '^caddy_http_requests_total{' | awk '{sum+=$2} END {print sum+0}') + [[ -z $total_requests ]] && total_requests="0" + + # Memory usage (process RSS in MB) + local memory_mb + if [[ -n $caddy_pid ]]; then + memory_mb=$(ps -p "$caddy_pid" -o rss= 2> /dev/null | awk '{printf "%.1f", $1/1024}') + [[ -z $memory_mb ]] && memory_mb="0" + else + memory_mb="0" + fi + + # Output as tab-separated values (tabs can't appear in version/domain strings) + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$version" "$sites_count" "$domains" "$uptime_seconds" "$active_requests" "$total_requests" "$memory_mb" +} + +# +# PHP-FPM Metrics +# ---- + +# +# Query PHP-FPM status page and extract metrics + +get_php_fpm_metrics() { + # Check if PHP-FPM status endpoint is available on localhost:9001 + if ! curl -sf --max-time 2 http://localhost:9001/fpm-status > /dev/null 2>&1; then + return 0 + fi + + # Get status in JSON format + local status + status=$(curl -sf --max-time 2 'http://localhost:9001/fpm-status?json' 2> /dev/null || echo "{}") + + # Parse JSON fields using grep/sed (simple parsing without jq dependency) + local pool process_manager start_since accepted_conn + local listen_queue idle_processes active_processes total_processes + local max_children_reached slow_requests + + pool=$(echo "$status" | grep -o '"pool":"[^"]*"' | cut -d'"' -f4) + [[ -z $pool ]] && pool="www" + + process_manager=$(echo "$status" | grep -o '"process manager":"[^"]*"' | cut -d'"' -f4) + [[ -z $process_manager ]] && process_manager="unknown" + + start_since=$(echo "$status" | grep -o '"start since":[0-9]*' | awk -F: '{print $2}') + [[ -z $start_since ]] && start_since="0" + + accepted_conn=$(echo "$status" | grep -o '"accepted conn":[0-9]*' | awk -F: '{print $2}') + [[ -z $accepted_conn ]] && accepted_conn="0" + + listen_queue=$(echo "$status" | grep -o '"listen queue":[0-9]*' | awk -F: '{print $2}') + [[ -z $listen_queue ]] && listen_queue="0" + + idle_processes=$(echo "$status" | grep -o '"idle processes":[0-9]*' | awk -F: '{print $2}') + [[ -z $idle_processes ]] && idle_processes="0" + + active_processes=$(echo "$status" | grep -o '"active processes":[0-9]*' | awk -F: '{print $2}') + [[ -z $active_processes ]] && active_processes="0" + + total_processes=$(echo "$status" | grep -o '"total processes":[0-9]*' | awk -F: '{print $2}') + [[ -z $total_processes ]] && total_processes="0" + + max_children_reached=$(echo "$status" | grep -o '"max children reached":[0-9]*' | awk -F: '{print $2}') + [[ -z $max_children_reached ]] && max_children_reached="0" + + slow_requests=$(echo "$status" | grep -o '"slow requests":[0-9]*' | awk -F: '{print $2}') + [[ -z $slow_requests ]] && slow_requests="0" + + # Output as tab-separated values (tabs can't appear in pool/pm names) + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$pool" "$process_manager" "$start_since" "$accepted_conn" "$listen_queue" "$idle_processes" "$active_processes" "$total_processes" "$max_children_reached" "$slow_requests" +} + +# ---- # Main Execution # ---- @@ -198,6 +346,27 @@ main() { echo "✓ Cataloging services..." ensure_tools "$family" "$permissions" + echo "✓ Checking Caddy status..." + local caddy_metrics caddy_available="false" + local caddy_version caddy_sites caddy_domains caddy_uptime caddy_active_req caddy_total_req caddy_memory + caddy_metrics=$(get_caddy_metrics) + + if [[ -n $caddy_metrics ]]; then + caddy_available="true" + IFS=$'\t' read -r caddy_version caddy_sites caddy_domains caddy_uptime caddy_active_req caddy_total_req caddy_memory <<< "$caddy_metrics" + fi + + echo "✓ Checking PHP-FPM status..." + local php_fpm_metrics php_fpm_available="false" + local php_fpm_pool php_fpm_pm php_fpm_uptime php_fpm_accepted php_fpm_queue + local php_fpm_idle php_fpm_active php_fpm_total php_fpm_max_children php_fpm_slow + php_fpm_metrics=$(get_php_fpm_metrics) + + if [[ -n $php_fpm_metrics ]]; then + php_fpm_available="true" + IFS=$'\t' read -r php_fpm_pool php_fpm_pm php_fpm_uptime php_fpm_accepted php_fpm_queue php_fpm_idle php_fpm_active php_fpm_total php_fpm_max_children php_fpm_slow <<< "$php_fpm_metrics" + fi + # # Output YAML to file @@ -205,6 +374,27 @@ main() { distro: $distro family: $family permissions: $permissions + caddy: + available: $caddy_available + version: ${caddy_version:-unknown} + sites_count: ${caddy_sites:-0} + domains: ${caddy_domains:-} + uptime_seconds: ${caddy_uptime:-0} + active_requests: ${caddy_active_req:-0} + total_requests: ${caddy_total_req:-0} + memory_mb: ${caddy_memory:-0} + php_fpm: + available: $php_fpm_available + pool: ${php_fpm_pool:-www} + process_manager: ${php_fpm_pm:-unknown} + uptime_seconds: ${php_fpm_uptime:-0} + accepted_conn: ${php_fpm_accepted:-0} + listen_queue: ${php_fpm_queue:-0} + idle_processes: ${php_fpm_idle:-0} + active_processes: ${php_fpm_active:-0} + total_processes: ${php_fpm_total:-0} + max_children_reached: ${php_fpm_max_children:-0} + slow_requests: ${php_fpm_slow:-0} ports: EOF echo "Error: Failed to write $DEPLOYER_OUTPUT_FILE" >&2 diff --git a/playbooks/server-install.sh b/playbooks/server-install.sh index 4c24195b..a743a6f3 100644 --- a/playbooks/server-install.sh +++ b/playbooks/server-install.sh @@ -35,10 +35,14 @@ export DEBIAN_FRONTEND=noninteractive [[ -z $DEPLOYER_SERVER_NAME ]] && echo "Error: DEPLOYER_SERVER_NAME required" && exit 1 export DEPLOYER_PERMS -# +# ---- # Helpers # ---- +# +# Permission Management +# ---- + # # Execute command with appropriate permissions @@ -50,6 +54,10 @@ run_cmd() { fi } +# +# Package Management +# ---- + # # Wait for dpkg lock to be released @@ -126,10 +134,14 @@ apt_get_with_retry() { return 1 } -# +# ---- # Installation Functions # ---- +# +# Repository Setup +# ---- + # # Setup distribution-specific repositories @@ -183,6 +195,13 @@ setup_repositories() { esac } +# +# Package Installation +# ---- + +# +# Install all required packages (Caddy, PHP, Git, system utilities) + install_all_packages() { echo "✓ Installing all packages..." @@ -270,6 +289,12 @@ install_all_packages() { exit 1 fi + # Enable PHP-FPM status page + if ! run_cmd sed -i 's/^;pm.status_path = .*/pm.status_path = \/fpm-status/' /etc/php/8.4/fpm/pool.d/www.conf; then + echo "Error: Failed to enable PHP-FPM status page" >&2 + exit 1 + fi + if ! systemctl is-enabled --quiet php8.4-fpm 2> /dev/null; then if ! run_cmd systemctl enable --quiet php8.4-fpm; then echo "Error: Failed to enable PHP-FPM service" >&2 @@ -284,6 +309,9 @@ install_all_packages() { fi } +# +# Install Bun runtime + install_bun() { if command -v bun > /dev/null 2>&1; then echo "✓ Bun already installed" @@ -299,6 +327,69 @@ install_bun() { fi } +# +# Caddy Configuration +# ---- + +# +# Setup Caddy configuration structure + +setup_caddy_structure() { + echo "✓ Setting up Caddy configuration structure..." + + # Create directory structure + if ! run_cmd mkdir -p /etc/caddy/conf.d/sites; then + echo "Error: Failed to create Caddy config directories" >&2 + exit 1 + fi + + # Create main Caddyfile with global settings and imports + if ! run_cmd tee /etc/caddy/Caddyfile > /dev/null <<- 'EOF'; then + { + metrics + + log { + output file /var/log/caddy/access.log + format json + } + } + + # Import localhost-only endpoints (monitoring, status pages) + import conf.d/localhost.caddy + + # Import all site configurations + import conf.d/sites/*.caddy + EOF + echo "Error: Failed to create main Caddyfile" >&2 + exit 1 + fi + + # Create localhost.caddy - monitoring endpoints only accessible via localhost + if ! run_cmd tee /etc/caddy/conf.d/localhost.caddy > /dev/null <<- 'EOF'; then + # PHP-FPM status endpoint - localhost only (not accessible from internet) + http://localhost:9001 { + handle { + reverse_proxy unix//run/php/php8.4-fpm.sock { + transport fastcgi { + env SCRIPT_FILENAME /fpm-status + env SCRIPT_NAME /fpm-status + } + } + } + } + EOF + echo "Error: Failed to create localhost.caddy" >&2 + exit 1 + fi +} + +# +# Deployer User Setup +# ---- + +# +# Ensure deployer user exists + ensure_deployer_user() { if id -u deployer > /dev/null 2>&1; then echo "✓ Deployer user already exists" @@ -312,6 +403,9 @@ ensure_deployer_user() { fi } +# +# Configure group memberships for file access + configure_deployer_groups() { # Add caddy user to deployer group so it can access deployer's files if ! id -nG caddy 2> /dev/null | grep -qw deployer; then @@ -354,6 +448,9 @@ configure_deployer_groups() { fi } +# +# Setup deploy user with proper home directory and permissions + setup_deploy_user() { ensure_deployer_user @@ -385,6 +482,13 @@ setup_deploy_user() { configure_deployer_groups } +# +# Deploy Key Setup +# ---- + +# +# Generate SSH deploy key for git operations + setup_deploy_key() { echo "✓ Setting up deploy key..." @@ -440,6 +544,9 @@ setup_deploy_key() { fi } +# +# Ensure proper permissions on deploy directories + setup_deploy_directories() { if ! run_cmd test -d /home/deployer; then echo "Error: Deployer home directory missing" >&2 @@ -480,6 +587,13 @@ setup_deploy_directories() { fi } +# +# Validation +# ---- + +# +# Validate PHP version meets minimum requirements + validate_php_version() { local php_version php_version=$(php -r "echo PHP_VERSION;" 2> /dev/null || echo "unknown") @@ -502,7 +616,7 @@ validate_php_version() { echo "✓ PHP $php_version installed (meets minimum 8.3)" } -# +# ---- # Main Execution # ---- @@ -512,6 +626,7 @@ main() { # Execute installation tasks install_all_packages install_bun + setup_caddy_structure validate_php_version setup_deploy_key setup_deploy_directories @@ -534,6 +649,7 @@ main() { deploy_public_key: $deploy_public_key tasks_completed: - install_caddy + - setup_caddy_structure - install_php - install_extensions - configure_php_fpm