fix: make nginx configuration reload transactional

This commit is contained in:
lingkai5wu committed 2026-10-11 23:12:20 +08:00
1 parent 87c4e5b2a9
commit 02953a98ad
2 files changed
+132 -97

No files matched your search

+130
View File
@@ -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
View File
@@ -1,102 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -euo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")" cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
readonly SERVICE="nginx" exec docker compose exec -T nginx sh -s <./.reload-in-container.sh
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."