mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
`tini` does not wait for child processes to close, so all processes will be killed immediately. This is why the container stops so fast. This fix makes the `init` script listen and handle terminate signals. It also ensures that child processes are shut down in reverse order with proper waiting for completion.
196 lines
5.3 KiB
Bash
Executable File
196 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -o errexit # treat errors as fatal
|
|
set -o nounset # treat unset variables as an error
|
|
set -o pipefail # treat errors in pipes as fatal
|
|
shopt -s inherit_errexit # inherit errexit
|
|
|
|
# make it possible to disable the inotify watcher process
|
|
ENABLE_RESCAN_ON_FILESYSTEM_CHANGE="${ENABLE_RESCAN_ON_FILESYSTEM_CHANGE:="true"}"
|
|
|
|
# Set INIT_DEBUG to false if not set by docker env
|
|
# (this env var is currently undocumented and usually just needed for development purposes)
|
|
INIT_DEBUG="${INIT_DEBUG:="false"}"
|
|
|
|
# print debug log output if enabled
|
|
debug_log() {
|
|
if [[ ${INIT_DEBUG} == "true" ]]; then
|
|
echo "DEBUG: [init][$(date +"%Y-%m-%d %T")]" "${@}" || true
|
|
fi
|
|
}
|
|
|
|
# print debug log output if enabled
|
|
info_log() {
|
|
echo "INFO: [init][$(date +"%Y-%m-%d %T")]" "${@}" || true
|
|
}
|
|
|
|
# print error log output if enabled
|
|
error_log() {
|
|
echo "ERROR: [init][$(date +"%Y-%m-%d %T")]" "${@}" || true
|
|
exit 1
|
|
}
|
|
|
|
wait_for_gunicorn_socket() {
|
|
info_log "waiting for gunicorn socket file..."
|
|
while [[ ! -S /tmp/gunicorn.sock ]]; do
|
|
sleep 1
|
|
done
|
|
info_log "gunicorn socket file found"
|
|
}
|
|
|
|
# function that runs or main process and creates a corresponding PID file,
|
|
start_bin_gunicorn() {
|
|
# cleanup potentially leftover socket
|
|
rm /tmp/gunicorn.sock -f
|
|
|
|
# commands to start our main application and store its PID to check for crashes
|
|
info_log "starting gunicorn"
|
|
gunicorn \
|
|
--access-logfile - \
|
|
--error-logfile - \
|
|
--worker-class uvicorn.workers.UvicornWorker \
|
|
--bind=0.0.0.0:5000 \
|
|
--bind=unix:/tmp/gunicorn.sock \
|
|
--pid=/tmp/gunicorn.pid \
|
|
--forwarded-allow-ips="*" \
|
|
--workers "${GUNICORN_WORKERS:=2}" \
|
|
main:app &
|
|
}
|
|
|
|
# Commands to start nginx (handling PID creation internally)
|
|
start_bin_nginx() {
|
|
wait_for_gunicorn_socket
|
|
|
|
info_log "starting nginx"
|
|
if [[ ${EUID} -ne 0 ]]; then
|
|
nginx
|
|
else
|
|
# if container runs as root, drop permissions
|
|
nginx -g 'user romm;'
|
|
fi
|
|
}
|
|
|
|
start_bin_valkey-server() {
|
|
info_log "starting valkey-server"
|
|
# Check if /usr/local/etc/valkey/valkey.conf exists and use it if so
|
|
if [[ -f /usr/local/etc/valkey/valkey.conf ]]; then
|
|
valkey-server /usr/local/etc/valkey/valkey.conf &
|
|
else
|
|
valkey-server --dir /redis-data &
|
|
fi
|
|
VALKEY_PID=$!
|
|
echo "${VALKEY_PID}" >/tmp/valkey-server.pid
|
|
}
|
|
|
|
# function that runs our independent python scripts and creates corresponding PID files,
|
|
start_python() {
|
|
SCRIPT="${1}"
|
|
info_log "starting ${SCRIPT}.py"
|
|
python3 "${SCRIPT}.py" &
|
|
WATCHER_PID=$!
|
|
echo "${WATCHER_PID}" >"/tmp/${SCRIPT}.pid"
|
|
}
|
|
|
|
watchdog_process_pid() {
|
|
TYPE=$1
|
|
PROCESS=$2
|
|
if [[ -f "/tmp/${PROCESS}.pid" ]]; then
|
|
# check if the pid we last wrote to our state file is actually active
|
|
PID=$(cat "/tmp/${PROCESS}.pid") || true
|
|
if [[ -d "/proc/${PID}" ]]; then
|
|
debug_log "${PROCESS} still running, no need to start"
|
|
else
|
|
if [[ ${TYPE} == "bin" ]]; then
|
|
start_bin_"${PROCESS}"
|
|
elif [[ ${TYPE} == "python" ]]; then
|
|
start_python "${PROCESS}"
|
|
fi
|
|
fi
|
|
else
|
|
if [[ ${TYPE} == "bin" ]]; then
|
|
start_bin_"${PROCESS}"
|
|
elif [[ ${TYPE} == "python" ]]; then
|
|
start_python "${PROCESS}"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
stop_process_pid() {
|
|
PROCESS=$1
|
|
if [[ -f "/tmp/${PROCESS}.pid" ]]; then
|
|
PID=$(cat "/tmp/${PROCESS}.pid") || true
|
|
if [[ -d "/proc/${PID}" ]]; then
|
|
info_log "stopping ${PROCESS}"
|
|
kill "${PID}" || true
|
|
# wait for process exit
|
|
while [ -e "/proc/${PID}" ]; do sleep 0.1; done
|
|
fi
|
|
fi
|
|
}
|
|
|
|
shutdown() {
|
|
# shutdown in reverse order
|
|
stop_process_pid scheduler
|
|
stop_process_pid worker
|
|
stop_process_pid watcher
|
|
stop_process_pid nginx
|
|
stop_process_pid gunicorn
|
|
stop_process_pid valkey-server
|
|
}
|
|
|
|
# switch to backend directory
|
|
cd /backend || { error_log "/backend directory doesn't seem to exist"; }
|
|
|
|
info_log "Starting up, please wait..."
|
|
|
|
# setup trap handler
|
|
exited=0
|
|
trap 'exited=1 && shutdown' SIGINT SIGTERM EXIT
|
|
|
|
# clear any leftover PID files
|
|
rm /tmp/*.pid -f
|
|
|
|
# function definition done, lets start our main loop
|
|
while ! ((exited)); do
|
|
# Start Valkey server if we dont have a corresponding PID file
|
|
# and REDIS_HOST is not set (which would mean we're using an external Redis/Valkey)
|
|
if [[ -z ${REDIS_HOST:=""} ]]; then
|
|
watchdog_process_pid bin valkey-server
|
|
fi
|
|
|
|
# Run needed database migrations on startup,
|
|
# but only if it was not successful since the last full docker container start
|
|
if [[ ${ALEMBIC_SUCCESS:="false"} == "false" ]]; then
|
|
if alembic upgrade head; then
|
|
debug_log "database schema migrations succeeded"
|
|
ALEMBIC_SUCCESS="true"
|
|
else
|
|
error_log "Something went horribly wrong with our database"
|
|
fi
|
|
else
|
|
debug_log "database schema already upgraded during current container lifecycle"
|
|
fi
|
|
|
|
# Start gunicorn if we dont have a corresponding PID file
|
|
watchdog_process_pid bin gunicorn
|
|
|
|
# Start nginx if we dont have a corresponding PID file
|
|
watchdog_process_pid bin nginx
|
|
|
|
# only start the watcher.py if we actually want to use the rescan on fs change feature
|
|
if [[ ${ENABLE_RESCAN_ON_FILESYSTEM_CHANGE} == "true" ]]; then
|
|
# Start watcher if we dont have a corresponding PID file
|
|
watchdog_process_pid python watcher
|
|
fi
|
|
|
|
# Start background worker processes
|
|
debug_log "Starting worker and scheduler"
|
|
# Start worker if we dont have a corresponding PID file
|
|
watchdog_process_pid python worker
|
|
# Start scheduler if we dont have a corresponding PID file
|
|
watchdog_process_pid python scheduler
|
|
|
|
# check for died processes every 5 seconds
|
|
sleep 5
|
|
done
|