fix: make nginx configuration reload transactional
This commit is contained in:
1 parent
87c4e5b2a9
commit
02953a98ad
2 files changed
+132
-97
No files matched your search
@@ -0,0 +1,130 @@
|
||||
#!/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"
|
||||
|
||||
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
|
||||
if [ "$finish_status" -eq 0 ] && [ "$2" = committed ]; then
|
||||
echo "Nginx configuration reloaded successfully."
|
||||
fi
|
||||
exit "$finish_status"
|
||||
}
|
||||
|
||||
main() {
|
||||
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 "$@"
|
||||
+2
-97
@@ -1,102 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
|
||||
readonly SERVICE="nginx"
|
||||
|
||||
if ! docker compose exec -T "$SERVICE" true >/dev/null 2>&1; then
|
||||
echo "Error: nginx container is not running." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker compose exec -T "$SERVICE" sh <<'EOF'
|
||||
set -eu
|
||||
|
||||
TEMPLATE_DIR="/etc/nginx/templates"
|
||||
OUTPUT_DIR="/etc/nginx"
|
||||
ENVSUBST_SCRIPT="/docker-entrypoint.d/20-envsubst-on-templates.sh"
|
||||
MANAGED_DIRS="conf.d snippets"
|
||||
|
||||
if [ ! -d "$TEMPLATE_DIR" ]; then
|
||||
echo "Error: template directory does not exist: $TEMPLATE_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$ENVSUBST_SCRIPT" ]; then
|
||||
echo "Error: envsubst script is not executable: $ENVSUBST_SCRIPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only allow templates in directories managed by this script.
|
||||
find "$TEMPLATE_DIR" -type f -name '*.template' -print | while IFS= read -r template; do
|
||||
relative="${template#"$TEMPLATE_DIR"/}"
|
||||
|
||||
case "$relative" in
|
||||
conf.d/* | snippets/*)
|
||||
;;
|
||||
*)
|
||||
echo "Error: unmanaged template path: $relative" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
backup_dir="$(mktemp -d /tmp/nginx-reload.XXXXXX)"
|
||||
committed=0
|
||||
|
||||
rollback() {
|
||||
status=$?
|
||||
|
||||
if [ "$committed" -eq 0 ]; then
|
||||
echo "Reload failed. Restoring previous generated configuration." >&2
|
||||
|
||||
for dir in $MANAGED_DIRS; do
|
||||
rm -rf "$OUTPUT_DIR/$dir"
|
||||
|
||||
if [ -d "$backup_dir/$dir" ]; then
|
||||
cp -a "$backup_dir/$dir" "$OUTPUT_DIR/$dir"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
rm -rf "$backup_dir"
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap rollback EXIT HUP INT TERM
|
||||
|
||||
for dir in $MANAGED_DIRS; do
|
||||
if [ -d "$OUTPUT_DIR/$dir" ]; then
|
||||
cp -a "$OUTPUT_DIR/$dir" "$backup_dir/$dir"
|
||||
fi
|
||||
done
|
||||
|
||||
for dir in $MANAGED_DIRS; do
|
||||
if [ -d "$OUTPUT_DIR/$dir" ]; then
|
||||
find "$OUTPUT_DIR/$dir" -type f -print | while IFS= read -r output; do
|
||||
relative="${output#"$OUTPUT_DIR"/}"
|
||||
|
||||
if [ ! -f "$TEMPLATE_DIR/$relative.template" ]; then
|
||||
echo "Removing stale generated file: $relative"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
rm -rf "$OUTPUT_DIR/$dir"
|
||||
mkdir -p "$OUTPUT_DIR/$dir"
|
||||
done
|
||||
|
||||
"$ENVSUBST_SCRIPT"
|
||||
|
||||
nginx -t
|
||||
|
||||
committed=1
|
||||
trap - EXIT HUP INT TERM
|
||||
rm -rf "$backup_dir"
|
||||
EOF
|
||||
|
||||
docker compose exec -T "$SERVICE" nginx -s reload
|
||||
|
||||
echo "Nginx configuration reloaded successfully."
|
||||
exec docker compose exec -T nginx sh -s <./.reload-in-container.sh
|
||||
Reference in new issue
Block a user