Files
cloud-apache-container/scripts/entrypoint-fpm.sh
T
shadowdaoandClaude Opus 5.5 eaf616eb30 fix(entrypoint): graceful docker stop + cac-fpm error_log owner on first start
Graceful stop (cac, cac-fpm, shared-httpd, and form-sender which runs cac):
entrypoint.sh, entrypoint-fpm.sh and entrypoint-shared-httpd.sh end with bash
as PID 1 tailing logs. PID 1 only receives signals it has a handler for, so
docker stop's SIGTERM was dropped; docker waited the full 10s and SIGKILLed
apache / php-fpm mid-request (exit 137). The entrypoints now trap TERM/INT and
stop each daemon they started via the new scripts/stop-services.sh: httpd
graceful-stop (WINCH), php-fpm QUIT, crond TERM, each with a bounded wait and a
TERM/KILL fallback, budgets summing to < 10s, then exit 0. The log tail is
backgrounded + `wait`ed (and shared-httpd's 60s rescan sleep likewise) so the
trap runs immediately. Log streaming to docker logs is unchanged.

cac-fpm error_log: entrypoint-fpm.sh `touch`ed logs/php-fpm/error.log as root
AFTER its chown -R, so on a site's first start the pool (customer uid) could
not open it and PHP errors went out as FastCGI stderr to shared-httpd's log
until the next restart. The file is now created and chowned to the customer
right after the logs dir is made, before php-fpm starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:44:14 -07:00

123 lines
4.8 KiB
Bash
Executable File

#!/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
# Create the pool's error_log (php_admin_value[error_log] in
# create-php-config.sh) BEFORE the chown -R below, and chown it explicitly.
# It used to be `touch`ed as root at the very end, after the chown, so on a
# site's first start the pool (customer uid) could not open it and PHP errors
# fell back to FastCGI stderr -> shared-httpd's error log until the next
# restart's chown -R fixed the owner.
touch /home/$user/logs/php-fpm/error.log
chown $user:$user /home/$user/logs/php-fpm/error.log
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
# Do NOT `chmod -R 755` the docroot here. /home/$user is the customer's
# bind-mounted site directory; a recursive 755 on every start made every file
# (wp-config.php included) world-readable AND executable, and reverted any
# tighter permissions the customer had set. Apache (shared-httpd, or the
# bundled httpd) runs as `apache`, not the customer, so it only needs to
# traverse directories to serve static files. Normalise that and nothing else:
# open the docroot itself, and add o+x to directories that lack it. Regular
# files are never touched, so execute is never added and wp-config.php is
# never loosened.
chmod 755 /home/$user
find /home/$user -type d ! -perm -o=x -exec chmod o+x {} +
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 </dev/urandom | head -c 13 ; echo '')
mysql_password=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 18 ; echo '')
mysql_db=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 6 ; echo '')
mysql -e "CREATE DATABASE devdb_"$mysql_db";"
mysql -e "CREATE USER '"$mysql_user"'@'localhost' IDENTIFIED BY '"$mysql_password"';"
mysql -e "GRANT ALL PRIVILEGES ON *.* TO '"$mysql_user"'@'localhost' WITH GRANT OPTION;"
mysql -e "FLUSH PRIVILEGES;"
echo "# User crontab for $user" > /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
# Graceful shutdown. bash is PID 1 here, and PID 1 only receives signals it
# has a handler for, so without this trap `docker stop`'s SIGTERM was dropped:
# docker waited the full 10s and SIGKILLed php-fpm mid-request (exit 137).
# php-fpm QUIT = graceful (workers finish their current request), then crond.
source /scripts/stop-services.sh
TAIL_PID=""
shutdown() {
trap - TERM INT
echo "entrypoint: received stop signal, shutting down"
stop_service php-fpm /run/php-fpm/php-fpm.pid QUIT 6 'php-fpm: master'
stop_service crond /run/crond.pid TERM 1 '^/usr/sbin/crond'
[ -n "$TAIL_PID" ] && kill "$TAIL_PID" 2>/dev/null
exit 0
}
trap shutdown TERM INT
# Tail PHP-FPM logs to docker logs. Backgrounded + `wait` (instead of a
# foreground tail) so bash can run the trap as soon as a signal arrives.
tail -f /home/$user/logs/php-fpm/* &
TAIL_PID=$!
wait "$TAIL_PID"
exit 0