#!/bin/bash cd "$(dirname "$0")" CONF_DIR="./nginx/conf.d/" SLICE_DIR="./nginx/conf.d/slice/" TPL_DIR="./nginx/template.d/" CERTBOT_DIR="./certbot/etc/live/" if [ $# = 0 ]; then BATCH_MODE=0; else BATCH_MODE=1; fi exit_by_error() { echo "$1"; exit 1; } read_value() { read -rp "> " "$1" || exit_by_error $'\n'"Failed to read!"; } # Step 1: Enter the priority of config (01 to 99) PRIORITY= if [ "$BATCH_MODE" = 1 ]; then [ $# -gt 0 ] || exit_by_error "$0: Miss priority!" [[ "$1" =~ ^[0-9]{2}$ ]] || exit_by_error "$0: Invalid priority: \"$1\"" PRIORITY="$1"; shift else PRIORITY=50 echo "Enter the priority of config: (01 to 99) (Default: ${PRIORITY})" while true; do read_value input [ -n "$input" ] || break [[ "$input" =~ ^[0-9]{2}$ ]] || { echo "Invalid priority!"; continue; } PRIORITY="$input"; break done fi # Step 2: Enter your domain DOMAIN= if [ "$BATCH_MODE" = 1 ]; then [ $# -gt 0 ] || exit_by_error "$0: Miss domain!" [ -n "$1" ] || exit_by_error "$0: Invalid domain: \"\"" DOMAIN="$1"; shift else echo "Enter your domain: (e.g. example.com)" while [ -z "$DOMAIN" ]; do read_value DOMAIN [ -n "$DOMAIN" ] || echo "Invalid domain!" done fi # Step 3: Enter your template TPL_NAME= if [ "$BATCH_MODE" = 1 ]; then [ $# -gt 0 ] || exit_by_error "$0: Miss template name!" [ -n "$1" ] || exit_by_error "$0: Invalid template name: \"\"" [ -f "${TPL_DIR}${1}.conf" ] || exit_by_error "$0: Miss template file: \"${TPL_DIR}${1}.conf\"" TPL_NAME="$1"; shift else tpl_names=( $(find "$TPL_DIR" -type f -name "*.conf" -exec basename \{\} .conf \; | sort) ) max_idx="$(( ${#tpl_names[@]} - 1 ))" [ "$max_idx" -ge 0 ] || exit_by_error "Miss templates in directory ${TPL_DIR} !" echo "Available templates:" i=0; for tpl in "${tpl_names[@]}"; do echo " $i) ${tpl} : $(sed -nE 's/^### *\{\{desc\}\} *(.*)$/\1/p' "${TPL_DIR}${tpl}.conf")" i=$((1+$i)) done echo "Select a template (0 to ${max_idx}):" while true; do read_value input [[ "$input" =~ ^[0-9]{1,3}$ ]] && [ "${tpl_names[$input]}" ] \ || { echo "Invalid number!"; continue; } TPL_NAME="${tpl_names[$input]}"; break done fi TPL_FILE="${TPL_DIR}${TPL_NAME}.conf" # Step 4: Setup template arguments TPL_PARAM_LINES=() TPL_PARAM_PAIRS=() PORT= while read -r line; do [ -z "$line" ] || TPL_PARAM_LINES+=("$line") done <<<"$(sed -nE 's/^### *(\{\{params.*)$/\1/p' "$TPL_FILE")" i=0; for line in "${TPL_PARAM_LINES[@]}"; do name="${line%% *}" value= if [ "$BATCH_MODE" = 1 ]; then [ $# -gt 0 ] || exit_by_error "$0: Miss value of \"${name}\" !" [ -n "$1" ] || exit_by_error "$0: Invalid value of \"${name}\": \"$1\"" value="$1"; shift elif [ "$name" = "{{params.slice}}" ]; then # For convenience, select a slice instead of enter a slice name slice_names=( $(find "$SLICE_DIR" -type f -name "*.slice" ! -name '_*' -exec basename \{\} .slice \; | sort) ) max_idx="$(( ${#slice_names[@]} - 1 ))" [ "$max_idx" -ge 0 ] || exit_by_error "Miss slices in directory ${TPL_DIR} !" echo "Available slices:" j=0; for slice in "${slice_names[@]}"; do echo " $j) ${slice}" j=$((1+$j)) done echo "Select a slice (0 to ${max_idx}):" while true; do read_value input [[ "$input" =~ ^[0-9]{1,3}$ ]] && [ -n "${slice_names[$input]}" ] \ || { echo "Invalid number!"; continue; } value="${slice_names[$input]}"; break done else echo "Set ${line}" value="$(echo "$line" | sed -nE 's/^.*\(Default: *([^\)]+)\).*$/\1/p')" read_value input [ -z "$input" ] || value="$input" fi TPL_PARAM_PAIRS+=("$name" "$value") if [ "$name" = "{{params.port}}" ]; then PORT="${value##*:}" fi i=$((1+$i)) done TARGET_NAME="${DOMAIN}${PORT:+-$PORT}" TARGET_FILE="${CONF_DIR}${PRIORITY}-${TARGET_NAME}.conf" handle_template() { local tpl_file="${TPL_DIR}${1}.conf"; shift [ -f "$tpl_file" ] || { echo "$0: Miss template file: ${tpl_file}"; return 1; } local awk_vars=() local awk_script= i=0; while [ $# -gt 0 ]; do awk_vars+=(-v "n${i}=${1}" -v "v${i}=${2}") awk_script="${awk_script} gsub(n${i}, v${i}, s);" shift 2 i=$((1+$i)) done awk_script="!/^###/ { s=\$0; ${awk_script} print s; }" awk -F $'\n' "${awk_vars[@]}" "$awk_script" "$tpl_file" || return 1 } deploy_site_by_tpl() { local tpl_name="$1"; shift # Handle template and save config handle_template "$tpl_name" "{{domain}}" "$DOMAIN" "$@" > "$TARGET_FILE" \ || { echo "$0: Failed to handle template!"; return 1; } # Reload nginx ./nginx-reload.sh || { echo "$0: Failed to reload nginx!"; return 1; } } deploy_target_site() { # Apply for an ssl certificate if [ -z "${TPL_NAME##*.https}" -o -z "${TPL_NAME##*.https-}" ] \ && [ ! -f "${CERTBOT_DIR}${DOMAIN}/privkey.pem" ] then # Deploy a simple http site for certbot deploy_site_by_tpl "welcome.http" || return 1 # Run certbot certonly ./certbot.sh certonly -d "$DOMAIN" \ || { echo "$0: Certbot certonly failed: \"${DOMAIN}\""; return 1; } fi # Deploy target site deploy_site_by_tpl "$TPL_NAME" "${TPL_PARAM_PAIRS[@]}" || return 1 } echo_vars() { echo "========" echo "PRIORITY: [$PRIORITY]" echo "DOMAIN: [$DOMAIN]" echo "PORT: [$PORT]" echo "TPL_NAME: [$TPL_NAME]" echo "TPL_FILE: [$TPL_FILE]" echo "TPL_PARAM_PAIRS: [${TPL_PARAM_PAIRS[*]}]" echo "TARGET_NAME: [$TARGET_NAME]" echo "TARGET_FILE: [$TARGET_FILE]" echo "--------" handle_template "$TPL_NAME" "{{domain}}" "$DOMAIN" "${TPL_PARAM_PAIRS[@]}" echo "========" } #echo_vars # Rename conflicting config (xxx.conf to xxx.conf.bak-tmp) find "$CONF_DIR" -type f -name "??-${TARGET_NAME}.conf" -exec mv -f \{\} \{\}.bak-tmp \; # Do deploy deploy_target_site || { # Restore: # 1. Remove target file if exists rm -f "$TARGET_FILE" # 2. Restore conflicting config (xxx.conf.bak-tmp to xxx.conf) find "$CONF_DIR" -type f -name "*.bak-tmp" \ -exec bash -c 'path="{}"; mv -f "$path" "${path%.bak-tmp}"' \; # 3. Reload nginx ./nginx-reload.sh exit 1 } # Backup conflicting config (xxx.conf.bak-tmp to xxx.conf.bak-YYYY-MMDD-hhmmss) BACKUP_SUFFIX=".bak-$(date +%Y-%m%d-%H%M%S)" find "$CONF_DIR" -type f -name "*.bak-tmp" -exec bash -c " src='{}' old=\"\${src%.bak-tmp}\" bkp=\"\${old}${BACKUP_SUFFIX}\" if [ -z \"\$(diff '{}' \"$TARGET_FILE\" 2>/dev/null)\" ]; then rm -f '{}' echo \"Remove old: \$old\" else mv -f '{}' \"\$bkp\" echo \"Backup old: \$bkp\" fi " \; echo "New: ${TARGET_FILE}" echo "Done."