diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml index 1141d69..297f894 100644 --- a/.gitea/workflows/build-push.yaml +++ b/.gitea/workflows/build-push.yaml @@ -38,3 +38,65 @@ jobs: tags: | repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }} ${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }} + + Build-FPM-Images: + runs-on: ubuntu-latest + strategy: + matrix: + phpver: [74, 80, 81, 82, 83, 84, 85] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Gitea + uses: docker/login-action@v3 + with: + registry: repo.anhonesthost.net + username: ${{ secrets.CI_USER }} + password: ${{ secrets.CI_TOKEN }} + + - name: Build and Push FPM Image + uses: docker/build-push-action@v6 + with: + file: ./Dockerfile.fpm + platforms: linux/amd64 + push: true + build-args: | + PHPVER=${{ matrix.phpver }} + tags: | + repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }} + ${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }} + + Build-Shared-httpd: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Gitea + uses: docker/login-action@v3 + with: + registry: repo.anhonesthost.net + username: ${{ secrets.CI_USER }} + password: ${{ secrets.CI_TOKEN }} + + - name: Build and Push Shared httpd Image + uses: docker/build-push-action@v6 + with: + file: ./Dockerfile.shared-httpd + platforms: linux/amd64 + push: true + tags: | + repo.anhonesthost.net/cloud-hosting-platform/shared-httpd:latest diff --git a/Dockerfile.fpm b/Dockerfile.fpm new file mode 100644 index 0000000..1477ba5 --- /dev/null +++ b/Dockerfile.fpm @@ -0,0 +1,43 @@ +FROM almalinux/9-base +ARG PHPVER=83 + +# Install repos, update, install only needed packages (no httpd/mod_ssl), clean up in one layer +RUN dnf install -y \ + https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \ + https://rpms.remirepo.net/enterprise/remi-release-9.rpm && \ + dnf update -y && \ + dnf install -y wget procps cronie iproute postgresql-devel microdnf less git \ + nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix fcgi && \ + dnf clean all && \ + rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* + +# Copy scripts into the image and set permissions +COPY ./scripts/ /scripts/ +RUN chmod +x /scripts/* + +# Create needed dirs, install PHP, clean up (no SSL cert, no httpd) +RUN mkdir -p /run/php-fpm/ && \ + /scripts/install-php$PHPVER.sh && \ + rm -rf /tmp/* + +# Download and install wp-cli +RUN curl -L -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ + chmod +x /usr/local/bin/wp + +# Download and install Composer +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ + chmod +x /usr/local/bin/composer + +# Copy configs (PHP only, no Apache configs) +COPY ./configs/prod-php.ini /etc/php.ini +COPY ./configs/mariadb.repo /etc/yum.repos.d/ + +# Set up cron job for log rotation +RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab + +EXPOSE 9000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD SCRIPT_FILENAME=/fpm-ping SCRIPT_NAME=/fpm-ping REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q pong || exit 1 + +ENTRYPOINT [ "/scripts/entrypoint-fpm.sh" ] diff --git a/Dockerfile.shared-httpd b/Dockerfile.shared-httpd new file mode 100644 index 0000000..838394d --- /dev/null +++ b/Dockerfile.shared-httpd @@ -0,0 +1,40 @@ +FROM almalinux/9-base + +# Install Apache and minimal dependencies (no PHP at all) +RUN dnf install -y \ + https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && \ + dnf update -y && \ + dnf install -y httpd mod_ssl iproute cronie procps curl && \ + dnf clean all && \ + rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* + +# Copy scripts and set permissions +COPY ./scripts/detect-memory.sh /scripts/detect-memory.sh +COPY ./scripts/create-apache-mpm-config.sh /scripts/create-apache-mpm-config.sh +COPY ./scripts/log-rotate.sh /scripts/log-rotate.sh +COPY ./scripts/entrypoint-shared-httpd.sh /scripts/entrypoint-shared-httpd.sh +COPY ./scripts/tune-mpm.sh /scripts/tune-mpm.sh +RUN chmod +x /scripts/* + +# Generate self-signed SSL cert (same as main CAC image) +RUN openssl req -newkey rsa:2048 -nodes \ + -keyout /etc/pki/tls/private/localhost.key \ + -x509 -days 3650 -subj "/CN=localhost" \ + -out /etc/pki/tls/certs/localhost.crt + +# Copy Apache configs +COPY ./configs/remote_ip.conf /etc/httpd/conf.d/ +COPY ./configs/default-index.conf /etc/httpd/conf.d/ + +# Create vhosts directory (will be volume-mounted from host) +RUN mkdir -p /etc/httpd/conf.d/vhosts + +# Set up cron job for log rotation +RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab + +EXPOSE 80 443 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD curl -sfk https://localhost/ping || exit 1 + +ENTRYPOINT [ "/scripts/entrypoint-shared-httpd.sh" ] diff --git a/configs/shared-vhost-template.tpl b/configs/shared-vhost-template.tpl new file mode 100644 index 0000000..3ca0c57 --- /dev/null +++ b/configs/shared-vhost-template.tpl @@ -0,0 +1,39 @@ + + AllowOverride None + Require all granted + + + + Options All MultiViews + AllowOverride All + Require all granted + + + + ServerName "~~domain~~" + ~~alias_block~~ + DocumentRoot "/mnt/users/~~user~~/~~domain~~/public_html" + RewriteEngine on + RewriteCond %{SERVER_NAME} =~~domain~~ + RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] + + + + + ServerName "~~domain~~" + ~~alias_block~~ + DocumentRoot "/mnt/users/~~user~~/~~domain~~/public_html" + + SSLCertificateFile /etc/pki/tls/certs/localhost.crt + SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + + + SetHandler "proxy:fcgi://~~fpm_host~~:~~fpm_port~~" + + + DirectoryIndex index.php index.html index.htm + + ErrorLog "/var/log/httpd/~~domain~~-error.log" + CustomLog "/var/log/httpd/~~domain~~-access.log" combined + + diff --git a/scripts/create-php-config.sh b/scripts/create-php-config.sh index 5fa253e..ffdcb95 100644 --- a/scripts/create-php-config.sh +++ b/scripts/create-php-config.sh @@ -2,15 +2,28 @@ rm /etc/php-fpm.d/www.conf +FPM_LISTEN=${FPM_LISTEN:-/run/php-fpm/www.sock} + +# Determine listen directive and ownership based on socket vs TCP +if echo "$FPM_LISTEN" | grep -q '/'; then + # Unix socket mode + listen_directive="$FPM_LISTEN" + listen_owner_block="listen.owner = apache +listen.group = apache" +else + # TCP port mode + listen_directive="0.0.0.0:${FPM_LISTEN}" + listen_owner_block="" +fi + cat < /etc/php-fpm.d/$user.conf [$user] user = $user group = $user -listen = /run/php-fpm/www.sock -listen.owner = apache -listen.group = apache +listen = ${listen_directive} +${listen_owner_block} pm = ${PHP_FPM_PM} pm.max_children = ${PHP_FPM_MAX_CHILDREN} @@ -22,7 +35,12 @@ pm.start_servers = ${PHP_FPM_START_SERVERS} pm.min_spare_servers = ${PHP_FPM_MIN_SPARE} pm.max_spare_servers = ${PHP_FPM_MAX_SPARE} -slowlog = /etc/httpd/logs/error_log +; Health check endpoints +ping.path = /fpm-ping +ping.response = pong +pm.status_path = /fpm-status + +slowlog = /home/$user/logs/php-fpm/slowlog request_slowlog_timeout = 3s php_admin_value[error_log] = /home/$user/logs/php-fpm/error.log diff --git a/scripts/detect-memory.sh b/scripts/detect-memory.sh index bf7f5d7..da27045 100755 --- a/scripts/detect-memory.sh +++ b/scripts/detect-memory.sh @@ -38,6 +38,8 @@ fi CONTAINER_MEMORY_MB=$((CONTAINER_MEMORY_BYTES / 1024 / 1024)) # --- Budget calculation --- +CONTAINER_ROLE=${CONTAINER_ROLE:-combined} # combined | fpm_only | httpd_only + OS_RESERVE_MB=50 FIXED_PROCESS_MB=50 DEV_OVERHEAD_MB=0 @@ -50,63 +52,83 @@ if [ "$AVAILABLE_MB" -lt 60 ]; then AVAILABLE_MB=60 fi -PHP_BUDGET_MB=$((AVAILABLE_MB * 80 / 100)) -APACHE_BUDGET_MB=$((AVAILABLE_MB * 20 / 100)) +case "$CONTAINER_ROLE" in + fpm_only) + PHP_BUDGET_MB=$AVAILABLE_MB + APACHE_BUDGET_MB=0 + ;; + httpd_only) + PHP_BUDGET_MB=0 + APACHE_BUDGET_MB=$AVAILABLE_MB + ;; + *) + PHP_BUDGET_MB=$((AVAILABLE_MB * 80 / 100)) + APACHE_BUDGET_MB=$((AVAILABLE_MB * 20 / 100)) + ;; +esac -# --- PHP-FPM parameters --- -PHP_WORKER_ESTIMATE_MB=${PHP_WORKER_ESTIMATE_MB:-60} +# --- PHP-FPM parameters (skipped for httpd_only) --- +if [ "$CONTAINER_ROLE" != "httpd_only" ]; then + PHP_WORKER_ESTIMATE_MB=${PHP_WORKER_ESTIMATE_MB:-60} -calc_max_children=$((PHP_BUDGET_MB / PHP_WORKER_ESTIMATE_MB)) -# Floor at 2, cap at 50 -if [ "$calc_max_children" -lt 2 ]; then - calc_max_children=2 -fi -if [ "$calc_max_children" -gt 50 ]; then - calc_max_children=50 + calc_max_children=$((PHP_BUDGET_MB / PHP_WORKER_ESTIMATE_MB)) + # Floor at 2, cap at 50 + if [ "$calc_max_children" -lt 2 ]; then + calc_max_children=2 + fi + if [ "$calc_max_children" -gt 50 ]; then + calc_max_children=50 + fi + + PHP_FPM_PM=${FPM_PM:-ondemand} + PHP_FPM_MAX_CHILDREN=${FPM_MAX_CHILDREN:-$calc_max_children} + PHP_FPM_PROCESS_IDLE_TIMEOUT=${FPM_PROCESS_IDLE_TIMEOUT:-10s} + PHP_FPM_MAX_REQUESTS=${FPM_MAX_REQUESTS:-500} + + # Dynamic mode fallbacks (used if user overrides FPM_PM=dynamic) + PHP_FPM_START_SERVERS=${FPM_START_SERVERS:-2} + PHP_FPM_MIN_SPARE=${FPM_MIN_SPARE_SERVERS:-1} + PHP_FPM_MAX_SPARE=${FPM_MAX_SPARE_SERVERS:-3} fi -PHP_FPM_PM=${FPM_PM:-ondemand} -PHP_FPM_MAX_CHILDREN=${FPM_MAX_CHILDREN:-$calc_max_children} -PHP_FPM_PROCESS_IDLE_TIMEOUT=${FPM_PROCESS_IDLE_TIMEOUT:-10s} -PHP_FPM_MAX_REQUESTS=${FPM_MAX_REQUESTS:-500} +# --- Apache MPM parameters (skipped for fpm_only) --- +if [ "$CONTAINER_ROLE" != "fpm_only" ]; then + # ServerLimit: roughly 1 process per ~25 workers, floor 2, cap 16 + calc_server_limit=$((APACHE_BUDGET_MB / 30)) + if [ "$calc_server_limit" -lt 2 ]; then + calc_server_limit=2 + fi + if [ "$calc_server_limit" -gt 16 ]; then + calc_server_limit=16 + fi -# Dynamic mode fallbacks (used if user overrides FPM_PM=dynamic) -PHP_FPM_START_SERVERS=${FPM_START_SERVERS:-2} -PHP_FPM_MIN_SPARE=${FPM_MIN_SPARE_SERVERS:-1} -PHP_FPM_MAX_SPARE=${FPM_MAX_SPARE_SERVERS:-3} + # MaxRequestWorkers: ServerLimit * ThreadsPerChild (25) + calc_max_request_workers=$((calc_server_limit * 25)) + if [ "$calc_max_request_workers" -gt 400 ]; then + calc_max_request_workers=400 + fi -# --- Apache MPM parameters --- -# ServerLimit: roughly 1 process per ~25 workers, floor 2, cap 16 -calc_server_limit=$((APACHE_BUDGET_MB / 30)) -if [ "$calc_server_limit" -lt 2 ]; then - calc_server_limit=2 + # StartServers: 1 for ≤1GB, 2 for larger + calc_start_servers=1 + if [ "$CONTAINER_MEMORY_MB" -gt 1024 ]; then + calc_start_servers=2 + fi + + APACHE_START_SERVERS=${APACHE_START_SERVERS:-$calc_start_servers} + APACHE_SERVER_LIMIT=${APACHE_SERVER_LIMIT:-$calc_server_limit} + APACHE_MAX_REQUEST_WORKERS=${APACHE_MAX_REQUEST_WORKERS:-$calc_max_request_workers} + APACHE_MIN_SPARE_THREADS=${APACHE_MIN_SPARE_THREADS:-5} + APACHE_MAX_SPARE_THREADS=${APACHE_MAX_SPARE_THREADS:-15} + APACHE_MAX_CONNECTIONS_PER_CHILD=${APACHE_MAX_CONNECTIONS_PER_CHILD:-3000} fi -if [ "$calc_server_limit" -gt 16 ]; then - calc_server_limit=16 -fi - -# MaxRequestWorkers: ServerLimit * ThreadsPerChild (25) -calc_max_request_workers=$((calc_server_limit * 25)) -if [ "$calc_max_request_workers" -gt 400 ]; then - calc_max_request_workers=400 -fi - -# StartServers: 1 for ≤1GB, 2 for larger -calc_start_servers=1 -if [ "$CONTAINER_MEMORY_MB" -gt 1024 ]; then - calc_start_servers=2 -fi - -APACHE_START_SERVERS=${APACHE_START_SERVERS:-$calc_start_servers} -APACHE_SERVER_LIMIT=${APACHE_SERVER_LIMIT:-$calc_server_limit} -APACHE_MAX_REQUEST_WORKERS=${APACHE_MAX_REQUEST_WORKERS:-$calc_max_request_workers} -APACHE_MIN_SPARE_THREADS=${APACHE_MIN_SPARE_THREADS:-5} -APACHE_MAX_SPARE_THREADS=${APACHE_MAX_SPARE_THREADS:-15} -APACHE_MAX_CONNECTIONS_PER_CHILD=${APACHE_MAX_CONNECTIONS_PER_CHILD:-3000} # --- Export all variables --- -export CONTAINER_MEMORY_MB -export PHP_FPM_PM PHP_FPM_MAX_CHILDREN PHP_FPM_PROCESS_IDLE_TIMEOUT PHP_FPM_MAX_REQUESTS -export PHP_FPM_START_SERVERS PHP_FPM_MIN_SPARE PHP_FPM_MAX_SPARE -export APACHE_START_SERVERS APACHE_SERVER_LIMIT APACHE_MAX_REQUEST_WORKERS -export APACHE_MIN_SPARE_THREADS APACHE_MAX_SPARE_THREADS APACHE_MAX_CONNECTIONS_PER_CHILD +export CONTAINER_ROLE CONTAINER_MEMORY_MB +if [ "$CONTAINER_ROLE" != "httpd_only" ]; then + export PHP_FPM_PM PHP_FPM_MAX_CHILDREN PHP_FPM_PROCESS_IDLE_TIMEOUT PHP_FPM_MAX_REQUESTS + export PHP_FPM_START_SERVERS PHP_FPM_MIN_SPARE PHP_FPM_MAX_SPARE +fi +if [ "$CONTAINER_ROLE" != "fpm_only" ]; then + export APACHE_START_SERVERS APACHE_SERVER_LIMIT APACHE_MAX_REQUEST_WORKERS + export APACHE_MIN_SPARE_THREADS APACHE_MAX_SPARE_THREADS APACHE_MAX_CONNECTIONS_PER_CHILD +fi diff --git a/scripts/entrypoint-fpm.sh b/scripts/entrypoint-fpm.sh new file mode 100755 index 0000000..ae2d445 --- /dev/null +++ b/scripts/entrypoint-fpm.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +if [ -z "$PHPVER" ]; then + PHPVER="83"; +fi + +if [ -z "$environment" ]; then + environment="PROD" +fi + +# Default to FPM-only role +export CONTAINER_ROLE="fpm_only" +export FPM_LISTEN=${FPM_LISTEN:-9000} + +adduser -u $uid $user + +mkdir -p /home/$user/public_html +mkdir -p /home/$user/logs/php-fpm + +ln -sf /home/$user/logs/php-fpm /var/log/php-fpm + +source /scripts/detect-memory.sh +echo "Container memory: ${CONTAINER_MEMORY_MB}MB | PHP-FPM pm=${PHP_FPM_PM} max_children=${PHP_FPM_MAX_CHILDREN} | Listen=${FPM_LISTEN}" + +/scripts/create-php-config.sh + +mkdir -p /run/php-fpm/ +/usr/sbin/php-fpm -y /etc/php-fpm.conf +chown -R $user:$user /home/$user +chmod -R 755 /home/$user + +if [[ $environment == 'DEV' ]]; then + echo "Starting Dev Deployment (FPM-only mode)" + mkdir -p /home/$user/_db_backups + if ! command -v microdnf &> /dev/null; then + echo "microdnf not found, installing with dnf..." + dnf install -y microdnf && dnf clean all + fi + microdnf install -y MariaDB-server MariaDB-client memcached + sed -r -i 's/session.save_path="memcache:11211/session.save_path="localhost:11211/' /etc/php.ini + nohup mysqld -umysql & + if [ ! -f /home/$user/mysql_creds ]; then + echo "Give MySQL a chance to finish starting..." + sleep 10 + mysql_user=$(tr -dc A-Za-z0-9 /home/$user/crontab + echo "*/15 * * * * /scripts/mysql-backup.sh $user devdb_$mysql_db" >> /home/$user/crontab + chown $user:$user /home/$user/crontab + echo "MySQL User: "$mysql_user > /home/$user/mysql_creds + echo "MySQL Password: "$mysql_password >> /home/$user/mysql_creds + echo "MySQL Database: devdb_"$mysql_db >> /home/$user/mysql_creds + cat /home/$user/mysql_creds + fi + /usr/bin/memcached -d -u $user +fi + +if [[ $environment == 'PROD' ]]; then + if [ -f /etc/php.d/50-memcached.ini ]; then + sed -r -i 's/;session.save_path="localhost:11211/session.save_path="memcache:11211/' /etc/php.d/50-memcached.ini + fi +fi + +# Set up user crontab +if [ ! -f /home/$user/crontab ]; then + echo "# User crontab for $user" > /home/$user/crontab + echo "# Add your cron jobs here" >> /home/$user/crontab + echo "# Example: */5 * * * * /home/$user/scripts/my-script.sh" >> /home/$user/crontab + chown $user:$user /home/$user/crontab +fi + +# Load user crontab +crontab -u $user /home/$user/crontab + +/usr/sbin/crond + +# Tail PHP-FPM logs (becomes PID 1 process) +touch /home/$user/logs/php-fpm/error.log +tail -f /home/$user/logs/php-fpm/* + +exit 0 diff --git a/scripts/entrypoint-shared-httpd.sh b/scripts/entrypoint-shared-httpd.sh new file mode 100755 index 0000000..c63b377 --- /dev/null +++ b/scripts/entrypoint-shared-httpd.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +export CONTAINER_ROLE="httpd_only" + +if [ -z "$environment" ]; then + environment="PROD" +fi + +# Generate self-signed SSL cert if not already present +if [ ! -f /etc/pki/tls/certs/localhost.crt ]; then + openssl req -newkey rsa:2048 -nodes \ + -keyout /etc/pki/tls/private/localhost.key \ + -x509 -days 3650 -subj "/CN=localhost" \ + -out /etc/pki/tls/certs/localhost.crt +fi + +# Create log directory +mkdir -p /var/log/httpd + +# Remove default configs that conflict +rm -f /etc/httpd/conf.d/userdir.conf + +# Configure RemoteIP for Docker network +docker_network=$(ip addr show | grep eth0 | grep inet | awk -F " " '{print $2}') +if [ -n "$docker_network" ]; then + echo "RemoteIPInternalProxy $docker_network" >> /etc/httpd/conf.d/remoteip.conf +fi + +# Detect memory and calculate Apache MPM tuning +source /scripts/detect-memory.sh +echo "Container memory: ${CONTAINER_MEMORY_MB}MB | Apache workers=${APACHE_MAX_REQUEST_WORKERS} | Role=${CONTAINER_ROLE}" + +# Generate MPM tuning config +/scripts/create-apache-mpm-config.sh + +# Write SSL global config (matches standalone CAC behavior) +cat <<'EOF' > /etc/httpd/conf.d/ssl-global.conf +Listen 443 https +SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog +SSLSessionCache shmcb:/run/httpd/sslcache(512000) +SSLSessionCacheTimeout 300 +SSLCryptoDevice builtin +EOF + +# Disable the default ssl.conf if present (we use per-vhost SSL) +if [ -f /etc/httpd/conf.d/ssl.conf ]; then + mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.bak +fi + +# Ensure vhosts directory exists and is included +mkdir -p /etc/httpd/conf.d/vhosts +if ! grep -q 'IncludeOptional conf.d/vhosts/' /etc/httpd/conf/httpd.conf; then + echo 'IncludeOptional conf.d/vhosts/*.conf' >> /etc/httpd/conf/httpd.conf +fi + +# Start Apache +/usr/sbin/httpd -k start + +# Start cron for log rotation +/usr/sbin/crond + +# Tail Apache logs (becomes PID 1 process) +touch /var/log/httpd/error_log +tail -f /var/log/httpd/* + +exit 0 diff --git a/scripts/tune-mpm.sh b/scripts/tune-mpm.sh new file mode 100755 index 0000000..796b035 --- /dev/null +++ b/scripts/tune-mpm.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Hot-adjust Apache MPM Event settings and graceful reload. +# Usage: tune-mpm.sh [--max-workers N] [--server-limit N] [--start-servers N] +# [--min-spare-threads N] [--max-spare-threads N] +# [--max-connections-per-child N] + +set -euo pipefail + +# Read current values from the config as defaults +CONFIG_FILE="/etc/httpd/conf.d/mpm-tuning.conf" + +if [ ! -f "$CONFIG_FILE" ]; then + echo "Error: $CONFIG_FILE not found. Run detect-memory.sh first." + exit 1 +fi + +# Parse current values from config +current_start=$(grep -oP 'StartServers\s+\K\d+' "$CONFIG_FILE" 2>/dev/null || echo "1") +current_min_spare=$(grep -oP 'MinSpareThreads\s+\K\d+' "$CONFIG_FILE" 2>/dev/null || echo "5") +current_max_spare=$(grep -oP 'MaxSpareThreads\s+\K\d+' "$CONFIG_FILE" 2>/dev/null || echo "15") +current_max_workers=$(grep -oP 'MaxRequestWorkers\s+\K\d+' "$CONFIG_FILE" 2>/dev/null || echo "50") +current_server_limit=$(grep -oP 'ServerLimit\s+\K\d+' "$CONFIG_FILE" 2>/dev/null || echo "2") +current_max_conn=$(grep -oP 'MaxConnectionsPerChild\s+\K\d+' "$CONFIG_FILE" 2>/dev/null || echo "3000") + +# Parse arguments +START_SERVERS=$current_start +MIN_SPARE_THREADS=$current_min_spare +MAX_SPARE_THREADS=$current_max_spare +MAX_REQUEST_WORKERS=$current_max_workers +SERVER_LIMIT=$current_server_limit +MAX_CONNECTIONS_PER_CHILD=$current_max_conn + +while [[ $# -gt 0 ]]; do + case $1 in + --max-workers) MAX_REQUEST_WORKERS="$2"; shift 2 ;; + --server-limit) SERVER_LIMIT="$2"; shift 2 ;; + --start-servers) START_SERVERS="$2"; shift 2 ;; + --min-spare-threads) MIN_SPARE_THREADS="$2"; shift 2 ;; + --max-spare-threads) MAX_SPARE_THREADS="$2"; shift 2 ;; + --max-connections-per-child) MAX_CONNECTIONS_PER_CHILD="$2"; shift 2 ;; + --help|-h) + echo "Usage: $0 [--max-workers N] [--server-limit N] [--start-servers N]" + echo " [--min-spare-threads N] [--max-spare-threads N]" + echo " [--max-connections-per-child N]" + echo "" + echo "Current values:" + echo " StartServers: $current_start" + echo " MinSpareThreads: $current_min_spare" + echo " MaxSpareThreads: $current_max_spare" + echo " MaxRequestWorkers: $current_max_workers" + echo " ServerLimit: $current_server_limit" + echo " MaxConnectionsPerChild: $current_max_conn" + exit 0 + ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac +done + +# Write updated config +cat < "$CONFIG_FILE" + + StartServers ${START_SERVERS} + MinSpareThreads ${MIN_SPARE_THREADS} + MaxSpareThreads ${MAX_SPARE_THREADS} + ThreadLimit 64 + ThreadsPerChild 25 + MaxRequestWorkers ${MAX_REQUEST_WORKERS} + ServerLimit ${SERVER_LIMIT} + MaxConnectionsPerChild ${MAX_CONNECTIONS_PER_CHILD} + +EOF + +echo "MPM config updated:" +echo " StartServers=$START_SERVERS ServerLimit=$SERVER_LIMIT MaxRequestWorkers=$MAX_REQUEST_WORKERS" +echo " MinSpareThreads=$MIN_SPARE_THREADS MaxSpareThreads=$MAX_SPARE_THREADS MaxConnectionsPerChild=$MAX_CONNECTIONS_PER_CHILD" + +# Graceful reload +/usr/sbin/httpd -k graceful +echo "Apache graceful reload triggered."