Files
cloud-apache-container/scripts/stop-services.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

55 lines
2.3 KiB
Bash

#!/usr/bin/env bash
## stop-services.sh — sourced by the bash PID-1 entrypoints (entrypoint.sh,
## entrypoint-fpm.sh, entrypoint-shared-httpd.sh). Not executed directly.
##
## Why this exists: those entrypoints end with bash as PID 1 tailing logs.
## The kernel never delivers a signal to PID 1 unless PID 1 installed a handler
## for it, so `docker stop`'s SIGTERM was silently dropped. Docker waited the
## full stop timeout (10s) and then SIGKILLed the whole container, killing
## apache / php-fpm mid-request (exit 137). The entrypoints now trap TERM/INT
## and call stop_service for each daemon they started, then exit 0.
##
## Budget: docker's default stop timeout is 10s. Callers pass per-service
## timeouts that sum to < 10s so the graceful path always completes before
## docker gives up and SIGKILLs.
# A pid counts as alive if it exists and is not a zombie. bash (PID 1) reaps
# the daemons it adopted, but between exit and reap a zombie still answers
# `kill -0`, so check /proc state too.
_svc_alive() {
[ -n "$1" ] || return 1
kill -0 "$1" 2>/dev/null || return 1
! grep -qs '^State:[[:space:]]*Z' "/proc/$1/status"
}
# stop_service NAME PIDFILE GRACEFUL_SIGNAL GRACE_SECONDS [PGREP_PATTERN]
# Sends GRACEFUL_SIGNAL, waits up to GRACE_SECONDS for the process to exit,
# then sends TERM and waits 1s more, then KILL. Pid comes from PIDFILE, or
# from `pgrep -o -f PGREP_PATTERN` when the pidfile is missing/stale.
stop_service() {
local name=$1 pidfile=$2 sig=$3 grace=$4 pat=${5:-} pid="" i
[ -r "$pidfile" ] && pid=$(tr -dc '0-9' < "$pidfile")
if ! _svc_alive "$pid" && [ -n "$pat" ]; then
pid=$(pgrep -o -f "$pat" 2>/dev/null)
fi
if ! _svc_alive "$pid"; then
echo "entrypoint: $name not running"
return 0
fi
echo "entrypoint: stopping $name (pid $pid) with SIG$sig"
kill -s "$sig" "$pid" 2>/dev/null
for ((i = 0; i < grace * 10; i++)); do
_svc_alive "$pid" || { echo "entrypoint: $name stopped"; return 0; }
sleep 0.1
done
echo "entrypoint: $name still running after ${grace}s, sending SIGTERM"
kill -TERM "$pid" 2>/dev/null
for ((i = 0; i < 10; i++)); do
_svc_alive "$pid" || { echo "entrypoint: $name stopped"; return 0; }
sleep 0.1
done
echo "entrypoint: $name still running, sending SIGKILL"
kill -KILL "$pid" 2>/dev/null
return 0
}