144 lines
3.7 KiB
Bash
144 lines
3.7 KiB
Bash
#!/bin/sh
|
|
|
|
set -eu
|
|
|
|
# Share template paths with the Nginx image's envsubst entrypoint.
|
|
readonly TEMPLATE_DIR="${NGINX_ENVSUBST_TEMPLATE_DIR:-/etc/nginx/templates}"
|
|
readonly TEMPLATE_SUFFIX="${NGINX_ENVSUBST_TEMPLATE_SUFFIX:-.template}"
|
|
readonly OUTPUT_DIR="${NGINX_ENVSUBST_OUTPUT_DIR:-/etc/nginx}"
|
|
readonly ENVSUBST_SCRIPT="${NGINX_RELOAD_ENVSUBST_SCRIPT:-/docker-entrypoint.d/20-envsubst-on-templates.sh}"
|
|
readonly MANAGED_DIRS="conf.d snippets"
|
|
readonly LOCK_FILE="${NGINX_RELOAD_LOCK_FILE:-/tmp/nginx-reload.lock}"
|
|
|
|
acquire_lock() {
|
|
if ! command -v flock >/dev/null 2>&1; then
|
|
echo "Error: flock is required to serialize configuration reloads." >&2
|
|
return 1
|
|
fi
|
|
|
|
# Keep FD 9 open through rollback and cleanup; never unlink the lock file.
|
|
exec 9>"$LOCK_FILE"
|
|
if ! flock -n 9; then
|
|
echo "Error: cannot acquire reload lock; another reload may be running: $LOCK_FILE" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
is_managed_template() {
|
|
for managed_dir in $MANAGED_DIRS; do
|
|
case "$1" in
|
|
"$managed_dir"/*) return 0 ;;
|
|
esac
|
|
done
|
|
return 1
|
|
}
|
|
|
|
validate_templates() {
|
|
if [ ! -d "$TEMPLATE_DIR" ]; then
|
|
echo "Error: template directory does not exist: $TEMPLATE_DIR" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -x "$ENVSUBST_SCRIPT" ]; then
|
|
echo "Error: envsubst script is not executable: $ENVSUBST_SCRIPT" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Collect first so a failed find cannot be masked by a pipeline.
|
|
templates="$(find "$TEMPLATE_DIR" -follow -type f -name "*$TEMPLATE_SUFFIX" -print)"
|
|
printf '%s\n' "$templates" | while IFS= read -r template; do
|
|
[ -n "$template" ] || continue
|
|
relative="${template#"$TEMPLATE_DIR"/}"
|
|
if ! is_managed_template "$relative"; then
|
|
echo "Error: unmanaged template path: $relative" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
backup_config() {
|
|
for dir in $MANAGED_DIRS; do
|
|
if [ -d "$OUTPUT_DIR/$dir" ]; then
|
|
cp -a "$OUTPUT_DIR/$dir" "$1/$dir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
generate_config() {
|
|
for dir in $MANAGED_DIRS; do
|
|
if [ -d "$OUTPUT_DIR/$dir" ]; then
|
|
outputs="$(find "$OUTPUT_DIR/$dir" -type f -print)"
|
|
printf '%s\n' "$outputs" | while IFS= read -r output; do
|
|
[ -n "$output" ] || continue
|
|
relative="${output#"$OUTPUT_DIR"/}"
|
|
if [ ! -f "$TEMPLATE_DIR/$relative$TEMPLATE_SUFFIX" ]; then
|
|
echo "Removing stale generated file: $relative"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
rm -rf "$OUTPUT_DIR/$dir"
|
|
mkdir -p "$OUTPUT_DIR/$dir"
|
|
done
|
|
NGINX_ENVSUBST_TEMPLATE_DIR="$TEMPLATE_DIR" \
|
|
NGINX_ENVSUBST_TEMPLATE_SUFFIX="$TEMPLATE_SUFFIX" \
|
|
NGINX_ENVSUBST_OUTPUT_DIR="$OUTPUT_DIR" \
|
|
"$ENVSUBST_SCRIPT"
|
|
}
|
|
|
|
restore_config() {
|
|
restore_failed=0
|
|
for dir in $MANAGED_DIRS; do
|
|
if ! rm -rf "$OUTPUT_DIR/$dir"; then
|
|
restore_failed=1
|
|
continue
|
|
fi
|
|
if [ -d "$1/$dir" ]; then
|
|
cp -a "$1/$dir" "$OUTPUT_DIR/$dir" || restore_failed=1
|
|
fi
|
|
done
|
|
[ "$restore_failed" -eq 0 ]
|
|
}
|
|
|
|
finish() {
|
|
finish_status="$1"
|
|
trap - EXIT
|
|
trap '' HUP INT TERM
|
|
|
|
if [ "$2" = modifying ]; then
|
|
echo "Reload failed. Restoring previous generated configuration." >&2
|
|
[ "$finish_status" -ne 0 ] || finish_status=1
|
|
if ! restore_config "$3"; then
|
|
echo "Error: configuration rollback incomplete. Backup preserved at: $3" >&2
|
|
exit "$finish_status"
|
|
fi
|
|
fi
|
|
|
|
if ! rm -rf "$3"; then
|
|
echo "Error: cannot remove configuration backup: $3" >&2
|
|
[ "$finish_status" -ne 0 ] || finish_status=1
|
|
fi
|
|
exit "$finish_status"
|
|
}
|
|
|
|
main() {
|
|
acquire_lock
|
|
validate_templates
|
|
backup_dir="$(mktemp -d /tmp/nginx-reload.XXXXXX)"
|
|
transaction_state=backing_up
|
|
trap 'finish "$?" "$transaction_state" "$backup_dir"' EXIT
|
|
trap 'exit 129' HUP
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
|
|
backup_config "$backup_dir"
|
|
# Only a complete backup permits mutation and subsequent restoration.
|
|
transaction_state=modifying
|
|
generate_config
|
|
nginx -t
|
|
nginx -s reload
|
|
transaction_state=committed
|
|
}
|
|
|
|
main "$@"
|