#!/bin/bash # # flatten an nginx config # # replaces all occurences of the `include` directive # with the relevant included files # # assumptions: # - include file paths do not contain spaces # (this seems compatible with how nginx handles includes) # - no files are included from above the directory where the nginx config file # being processed resides in # set debug verbosity to none [ -z "${NGINX_CONFIG_FLATTEN_DEBUG+x}" ] && NGINX_CONFIG_FLATTEN_DEBUG="" function debug { if [ "$NGINX_CONFIG_FLATTEN_DEBUG" != "" ]; then echo "$@" >&2 fi } # # get the path relative to a different base path # if the former is relative # # $1 - path # $2 - base path function get-path-relative-to { # if the path is absolute, that's all we need if [[ "$1" = /* ]]; then echo "$1" else echo "$2/$1" fi } # # extract the include paths from an nginx config file # # $1 - the file to work with function nginx-extract-includes { # assumption: include path does not contain spaces egrep '^\s*include ' "$1" | sed -r -e "s/^\s*include\s*([^;]+).*$/\1/" } # # handle all includes of a file to the temporary workdir # creating the directory structure in the process # and adding them to the NGINX_CONFIG_FILES variable # for further processing # # $1 - the file to work with function nginx-handle-includes { # get the included files # this will loop through actual globbed filenames # # assumption: no files are included from directories above the directory # the main config file resides in for i in $( nginx-extract-includes "$1" ); do debug -n " +-- found include: $i" # if the temporary copy of the file does not exist if [ ! -e "$TMP_WORKDIR/$i" ]; then debug " - copying!" # create the directory structure mkdir -p "$TMP_WORKDIR/$( dirname "$i" )" # copy the file cp "$NGINX_CONFIG_DIR/$i" "$TMP_WORKDIR/$i" # add to the list of files to proces NGINX_CONFIG_ELEMENTS+=("$i") else debug " - already copied." fi done } # # generate a cleaned nginx config directory, # containing only the files that are included from the config file # passed as the argument, or files included from those, etc # # $NGINX_CONFIG - nginx config file to work off of # $OUTPUT_DEST - target directory (must *not* exist) function nginx-clean-directory { # let's get the file in a temporary location # (we don't want to screw up the original!) cp "$NGINX_CONFIG" "$TMP_WORKDIR"/ # go one by one over the NGINX_CONFIG_ELEMENTS array # but from the end while [[ ${#NGINX_CONFIG_ELEMENTS[@]} > 0 ]]; do CUR_INDEX=$(( ${#NGINX_CONFIG_ELEMENTS[@]} - 1 )) CUR_ELEMENT=${NGINX_CONFIG_ELEMENTS[$CUR_INDEX]} debug " +-- working with index: $CUR_INDEX" debug " $CUR_ELEMENT" unset "NGINX_CONFIG_ELEMENTS[$CUR_INDEX]" nginx-handle-includes "$CUR_ELEMENT"; done # move the temporary work dir to the output location debug "+-- moving the temporary output directory to the final output location" debug " src: $TMP_WORKDIR" debug " dst: $OUTPUT_DEST" mv "$TMP_WORKDIR" "$OUTPUT_DEST" } # # flatten an nginx config file, # including all includes inline, recursively # # $NGINX_CONFIG - nginx config file to work off of # $OUTPUT_DEST - where to output the assembled, flattened file (must not exist) function nginx-flatten { # let's get the file in a temporary location # (we don't want to screw up the original!) cp "$NGINX_CONFIG" "$TMP_WORKDIR"/ NGINX_CONFIG_COPY="$TMP_WORKDIR/$( basename "$NGINX_CONFIG")" OLDIFS="$IFS" IFS=$'\n' while CURRENT_INCLUDES="$( egrep -n '^\s*include\s*.+;.*$' "$NGINX_CONFIG_COPY" )"; do PREVIOUS_CI_LINE=0; for ci in $CURRENT_INCLUDES; do # get the actual file glob CI_FILE_GLOB="$( echo "$ci" | sed -r -e "s/^[0-9]+\:\s*include\s*([^;]+).*$/\1/" )" CI_LINE="$( echo "$ci" | cut -d ':' -f 1 )" CI_INDENT="$( echo "$ci" | cut -d ':' -f 2 | sed -r -e 's/^(\s*).*$/\1/' )" DATE_EXT="$( date +%s%N )" debug " +-- previous line : $PREVIOUS_CI_LINE" debug " ci line : $CI_LINE" debug " indent : '$CI_INDENT'" debug " glob : '$CI_FILE_GLOB'" tail -n "+$(( $PREVIOUS_CI_LINE + 1 ))" "$NGINX_CONFIG_COPY" | head -n $(( $CI_LINE - 1 - $PREVIOUS_CI_LINE )) > "$NGINX_CONFIG_COPY.$DATE_EXT" echo -e "\n$CI_INDENT############################################################\n$CI_INDENT### $CI_FILE_GLOB \n$CI_INDENT############################################################" >> "$NGINX_CONFIG_COPY.$DATE_EXT" sed -r -e "s/^/$CI_INDENT/" $CI_FILE_GLOB >> "$NGINX_CONFIG_COPY.$DATE_EXT" echo -e "\n$CI_INDENT############################################################\n$CI_INDENT### end $CI_FILE_GLOB \n$CI_INDENT############################################################" >> "$NGINX_CONFIG_COPY.$DATE_EXT" PREVIOUS_CI_LINE="$CI_LINE" done debug " +-- previous line : $PREVIOUS_CI_LINE" debug " finishing this run off." DATE_EXT="$( date +%s%N )" tail -n "+$(( $PREVIOUS_CI_LINE + 1 ))" "$NGINX_CONFIG_COPY" > "$NGINX_CONFIG_COPY.$DATE_EXT" cat $NGINX_CONFIG_COPY.* > "$NGINX_CONFIG_COPY" rm $NGINX_CONFIG_COPY.* done IFS="$OLDIFS" debug "+-- moving the temporary output file to the output destination" debug " src: $NGINX_CONFIG_COPY" debug " dst: $OUTPUT_DEST" mv "$NGINX_CONFIG_COPY" "$OUTPUT_DEST" debug "+-- cleaning up the temporary output directory" rm -rf "$TMP_WORKDIR" } function print-usage { cat - < modes: flatten: flatten an nginx config file by inlining all includes recursively, save to clean-directory: generate a cleaned nginx config directory, containing only the files that are included from the input file, or files included from those, and so on; output to HEREDOC } ORIG_CWD="$PWD" # we really need 3 arguments: # $1 - mode of operation # $2 - source file # $3 - destination file/directory if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ]; then print-usage exit 1 fi # source file exists? if [ ! -f "$2" ] || [ ! -r "$2" ]; then echo echo "ERROR: source config file $2 doesn't exist or read access not granted" echo exit 2 fi # destination file/directory must not exist OUTPUT_DEST="$( get-path-relative-to "$3" "$ORIG_CWD" )" if [ -e "$OUTPUT_DEST" ]; then echo echo "WARNING: output file/directory exists; quitting!" echo exit 3 fi NGINX_CONFIG="$( realpath -eL "$2" )" debug "+-- config file: '$NGINX_CONFIG'" NGINX_CONFIG_DIR="$( dirname "$NGINX_CONFIG" )" debug " config directory: 'NGINX_CONFIG_DIR'" cd "$NGINX_CONFIG_DIR" TMP_WORKDIR="$( mktemp -d /tmp/nginx-conf-flatten.XXXX )" debug "+-- TMP_WORKDIR: $TMP_WORKDIR" # the list of files whose includes we have not copied yet NGINX_CONFIG_ELEMENTS=("$NGINX_CONFIG") # what are we doing, eh? if [ "$1" == "flatten" ]; then nginx-flatten "$NGINX_CONFIG" "$3" elif [ "$1" == "clean-directory" ]; then nginx-clean-directory "$NGINX_CONFIG" "$3" else print-usage exit 1 fi # get back to the original directory we've been running from cd "$ORIG_CWD"