#!/bin/bash ####### ### Update NGINX configuration '' with proper values and optionally copy ### to updated directory structure ####### ### text formatting ansi codes err="\e[1;31m" ok="\e[1;32m" warn="\e[93m" mag="\e[95m" cyan="\e[96m" norm="\e[0m" ### set variables # clear variables unset IP4 unset useCertbot unset CertbotDomain unset CertPath unset KeyPath unset CAChainPath unset DHPath unset phpType unset noOSCP # set variables regexIP4="(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" regexIP6="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" regexHostname="(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])" useSSL=1 serverNames_working=() serverNames=() detectedIP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p') certbotFiles=(cert.pem chain.pem fullchain.pem privkey.pem) generateDH=0 usePHP=1 hostname=$(hostname) configPath="./etc.${hostname}" # set tags and files to update tag_servernames="" file_servernames="nginx/snippets/server_names.conf" tag_sslcert="" tag_sslkey="" file_ssl="nginx/snippets/ssl/ssl_certs.conf" tag_dhparam="" tag_cachain="" file_mozmodern="nginx/conf.d/mozModern_ssl.conf" tag_lanip="" file_lanip="nginx/nginx.conf" file_buffers="nginx/conf.d/buffers.conf" file_buffersPHP="nginx/conf.d/buffers_conf_php.insert" tag_phphandler="#$" file_phphandler="nginx/nginx.conf" file_phpTCP="nginx/nginx_conf_phphandler_TCP.insert" file_phpSock="nginx/nginx_conf_phphandler_SOCK.insert" tag_phpAddr="" tag_phpPort="" tag_phpSock="" ### check running as root, otherwise exit if [ $(id -u) -ne 0 ]; then echo -e "\n${err}This script MUST be run as ROOT. Exiting.${norm}" exit 2 fi ### quick intro for the user echo -e "\n${mag}This script will customize the provided NGINX template files for your" echo "environment. You will be prompted for all necessary information. After that," echo "default error pages will be copied to your webroot and your NGINX configuration" echo -e "directory structure will be customized.${norm}\n" echo -e "${warn}You may accept the default option (listed in brackets) by simply" echo "pressing ENTER (i.e. no answer)." echo -e "You may exit this script at any prompt by typing 'X'${norm}\n" ### get local IP address echo -e "\n${mag}In cases where this server will be acting as a (reverse) proxy, NGINX" echo "needs to know it's own local IP address so that the logs can properly reflect" echo -e "the IP of the remote system(s) instead of this server.${norm}\n" while true; do read -p "What is this machine's local IP4 address? (${detectedIP}) " inputIP case "${inputIP}" in '') IP4="${detectedIP}" break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # check IP for validity if [[ "${inputIP}" =~ ^${regexIP4}$ ]]; then IP4="${inputIP}" break else echo -e "\n${err}Invalid IP4 format (xxx.xxx.xxx.xxx)${norm}" fi ;; esac done ### get server names for this installation echo -e "\n${mag}To which hostnames should NGINX answer? You can pick as many hostnames as you" echo "like. Please ensure you provide fully-qualified domain names unless you" echo "understand the impact not doing so." echo -e "\nIf you are setting up SSL on this system, then make sure the hostnames match" echo "what appears on your certificates!" echo -e "\n${warn}You will be prompted to enter one hostname at a time." echo "Provide a blank-entry (hit enter) when you're done." echo -e "'X' will exit the script, as always${norm}\n" while true; do read -p "Hostname for this server? " inputServerName case "${inputServerName}" in '') # check that at least one name as been provided if [ -z "${serverNames_working}" ]; then echo -e "\n${err}You must provide at least one hostname${norm}" else break fi ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # check hostname for validity if [[ ! "${inputServerName}" =~ ^${regexHostname}$ ]]; then echo -e "\n${err}Invalid hostname${norm}" else serverNames_working+=("${inputServerName}") fi ;; esac done # create new array with unqiue values only declare -A uniqueSN for name in "${serverNames_working[@]}"; do uniqueSN["${name}"]="" done for sn in "${!uniqueSN[@]}"; do serverNames+=("${sn}") done ### SSL related options # enable SSL? while true; do read -p "Do you want to enable SSL on this server? (default: Yes)" yn case "${yn}" in [Yy]*|'') usingSSL=1 break ;; [Nn]*) usingSSL=0 break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) echo -e "\n${err}Please answer (Y)es, (N)o, e(X)it or accept default${norm}" ;; esac done # using certbot? if [ "${usingSSL}" -eq 1 ]; then echo -e "\n${mag}If you are using Certbot, you only need to provide the primary domain name" echo "of your certificate and the script will auto-generate the paths NGINX needs to" echo "make everything work." echo -e "\nIf you haven't run Certbot yet, you can enter the domain you intend to use" echo "as your primary domain and the paths generated by this script will work after" echo -e "you run Certbot. In that case, you will have to answer ${warn}'yes'${mag} when asked" echo -e "${warn}'Do you want to use this domain setting anyways?'${norm}\n" while true; do read -p "Are you using Certbot to handle your SSL certificates? (default: No) " yn case "${yn}" in [Yy]*) useCertbot=1 break ;; [Nn]*|'') useCertbot=0 unset CertbotDomain break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) echo -e "\n${err}Please answer (Y)es, (N)o, e(X)it or accept default${norm}" ;; esac done fi # using Certbot: get primary domain name since that how Certbot determines paths if [ "${useCertbot}" -eq 1 ]; then while true; do read -p "What is the primary domain for your Certbot Certificates? " inputCertbotDomain case "${inputCertbotDomain}" in '') echo -e "\n${err}You cannot have an empty domain name${norm}" ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # check hostname for validity if [[ ! "${inputCertbotDomain}" =~ ^${regexHostname}$ ]]; then echo -e "\n${err}Invalid hostname${norm}" else # check if Certbot files exist in path implied from hostname echo -e "\n${cyan}Verifying Certbot files..." echo -e "(/etc/letsencrypt/live/${inputCertbotDomain}/...)${norm}" certbotBadFile=() for certbotFile in "${certbotFiles[@]}"; do if [ -f "/etc/letsencrypt/live/${inputCertbotDomain}/$certbotFile" ]; then echo -e "File: ${certbotFile} -- ${ok}OK${norm}" else echo -e "File: ${certbotFile} -- ${err}X${norm}" certbotBadFile+=("${certbotFile}") fi done if [ -z "${certbotBadFile}" ]; then echo -e "${cyan}Certbot files seem intact${norm}\n" CertbotDomain="${inputCertbotDomain}" break else echo -e "\n${err}The following files are missing from ${inputCertbotDomain}:" echo -e "${warn}${certbotBadFile[*]}${norm}" echo -e "${err}These files should all be present in a functional Certbot install.${norm}\n" while true; do read -p "Do you want to use this domain setting anyways? " yn case "${yn}" in [Yy]*) CertbotDomain="${inputCertbotDomain}" break ;; [Nn]*) break ;; *) ;; esac done fi if [ -n "${CertbotDomain}" ]; then break fi fi ;; esac done fi # Generate paths from CertbotDomain if [ "${CertbotDomain}" -eq 1 ]; then CertPath="/etc/letsencrypt/live/${CertbotDomain}/fullchain.pem" KeyPath="/etc/letsencrypt/live/${CertbotDomain}/privkey.pem" CAChainPath="/etc/letsencrypt/live/${CertbotDomain}/chain.pem" fi # only process manual certificate paths if using SSL and NOT using Certbot if [ "${useSSL}" -eq 1 ] && [ "${useCertbot}" -eq 0 ]; then echo -e "\n${mag}NGINX requires the full paths to your PEM formatted certificate and" echo "private key in order to serve pages securely and properly over SSL." echo "If you haven't generated/copied your certificates yet, you can enter the" echo "paths where they will be located and confirm when prompted by the script." echo -e "${warn}NGINX will NOT work until these files are actually copied!${norm}\n" # not using Certbot: get location of certificate while true; do read -p "What is the path to your primary SSL certificate? " inputCertPath case "${inputCertPath}" in '') echo -e "\n${err}You cannot have an empty path to your SSL certificate${norm}" ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # validate path if [ -f "${inputCertPath}" ]; then CertPath="${inputCertPath}" break else echo -e "\n${warn}The file you specified doesn't exist${norm}" while true; do read -p "Do you want to use this path anyways? " yn case $yn in [Yy]*) CertPath="${inputCertPath}" break ;; [Nn]*) break ;; *) ;; esac done if [ -n "${CertPath}" ]; then break fi fi ;; esac done # not using Certbot: get location of private key while true; do read -p "What is the path to your primary SSL private key? " inputKeyPath case "${inputKeyPath}" in '') echo -e "\n${err}You cannot have an empty path to your SSL private key${norm}" ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # validate path if [ -f "${inputKeyPath}" ]; then KeyPath="${inputKeyPath}" break else echo -e "\n${warn}The file you specified doesn't exist${norm}" while true; do read -p "Do you want to use this path anyways? " yn case $yn in [Yy]*) KeyPath="${inputKeyPath}" break ;; [Nn]*) break ;; *) ;; esac done if [ -n "${KeyPath}" ]; then break fi fi ;; esac done # not using Certbot: get location of CA Certificate Chain echo -e "\n${mag}Your full Certificate Authority certificate-chain (root and any/all" echo "intermediate certificates bundled in one file) is required if you want NGINX" echo "to provide OSCP stapling for your visitors. In most cases, you want this." echo "If you don't have your CA chain, you can fill in the filename you'll be saving" echo "it as in the future and confirm it when the scripts prompts you. In that" echo -e "case, however, ${warn}NGINX will not work until that file actually exists.${mag}" echo -e "\nIf you are using a self-signed certificate or do not want OSCP stapling, leave" echo -e "this blank ${warn}(hit enter)${mag} and the relevant configuration section will be disabled.${norm}\n" while true; do read -p "What is the path to your primary SSL CA Chain certificate? " inputCAChainPath case "${inputCAChainPath}" in '') noOSCP=1 break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # validate path if [ -f "${inputCAChainPath}" ]; then CAChainPath="${inputCAChainPath}" break else echo -e "\n${warn}The file you specified doesn't exist${norm}" while true; do read -p "Do you want to use this path anyways? " yn case $yn in [Yy]*) CAChainPath="${inputCAChainPath}" break ;; [Nn]*) break ;; *) ;; esac done if [ -n "${CAChainPath}" ]; then break fi fi ;; esac done fi # dhparam: get location of DH Parameters file if [ "${useSSL}" -eq 1 ]; then echo -e "\n${mag}Having your own unique Diffie-Hellman Parameters file makes your SSL" echo "communication more secure by helping to generate unique safe large prime" echo "numbers. You shouldn't use any pre-installed dhparam.pem files. You should" echo "always generate your own. If you haven't done that already and would like this" echo -e "script to do it for you, please type ${warn}generate${mag} at the prompt instead" echo -e "of a path${norm}\n" while true; do read -p "What is the path to your DH Parameters file? (default: /etc/ssl/certs/dhparam.pem) " inputDHPath case "${inputDHPath}" in '') # verify default path exists inputDHPath="/etc/ssl/certs/dhparam.pem" if [ -f "${inputDHPath}" ]; then DHPath="${inputDHPath}" break else echo -e "\n${warn}The file you specified doesn't exist${norm}" while true; do read -p "Do you want to use this path anyways? " yn case $yn in [Yy]*) DHPath="${inputDHPath}" break ;; [Nn]*) break ;; *) ;; esac done if [ -n "${DHPath}" ]; then break fi fi ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; [Gg][Ee][Nn][Ee][Rr][Aa][Tt][Ee]*) generateDH=1 break ;; *) # validate path if [ -f "${inputDHPath}" ]; then DHPath="${inputDHPath}" break else echo -e "\n${warn}The file you specified doesn't exist${norm}" while true; do read -p "Do you want to use this path anyways? " yn case $yn in [Yy]*) DHPath="${inputDHPath}" break ;; [Nn]*) break ;; *) ;; esac done if [ -n "${DHPath}" ]; then break fi fi ;; esac done fi ### PHP-FPM related options # get PHP-FPM usage status echo -e "\n${mag}PHP-FPM allows your server to process and serve dynamic PHP content. If you" echo "have PHP-FPM installed, NGINX needs to know how to access it. The following" echo "questions will allow the script to auto-configure that for you." echo -e "\nIf you have not yet installed PHP-FPM but intend to do so in the future, you" echo -e "can answer ${warn}yes${mag} and provide details for your intended setup so the script can" echo -e "generate a configuration that will work once your PHP-FPM is set up." echo -e "{warn}Note: NGINX WILL STILL WORK if configured to use PHP-FPM but without PHP-FPM actually installed. However, it will throw 500/503 errors if you attempt to serve PHP content.${norm}\n" while true; do read -p "Are you using PHP-FPM? (default: Yes) " yn case $yn in [Yy]*|'') usePHP=1 break ;; [Nn]*) usePHP=0 break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) echo -e "\n${err}Please answer (Y)es, (N)o, e(X)it or accept default${norm}" ;; esac done # get PHP-FPM TCP or Sockets configuration if [ "${usePHP}" -eq 1 ]; then echo -e "\n${mag}PHP-FPM can be set up to respond to requests via TCP or via UNIX sockets." echo "If you have no idea what any of this means, then you're probably using the" echo -e "default setup which is sockets${norm}\n" while true; do read -p "Is your PHP-FPM set up to listen via 'TCP' or 'sockets'? (default: sockets) " inputPHPType case "${inputPHPType}" in [Ss][Oo][Cc][Kk][Ee][Tt][Ss]*|'') phpType='sockets' break ;; [Tt][Cc][Pp]*) phpType='tcp' break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) echo -e "\n${err}Please enter either 'tcp' or 'sockets' or 'X' to exit script${norm}" ;; esac done fi # PHP-TCP: get address and port information if [ "${phpType}" = "tcp" ]; then while true; do read -p "What IP address is PHP-FPM listening on? (default: 127.0.0.1) " inputPHPAddr case "${inputPHPAddr}" in '') phpAddr="127.0.0.1" break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # check basic format validity if [[ "${inputPHPAddr}" =~ ^${regexIP6}$ ]] || [[ "${inputPHPAddr}" =~ ^${regexIP4}$ ]]; then phpAddr="${inputPHPAddr}" break else echo -e "\n${err}Invalid IP4/IP6 address format${norm}" fi ;; esac done while true; do read -p "What port is PHP-FPM listening on? (default: 9000) " inputPHPPort case "${inputPHPPort}" in '') phpPort=9000 break ;; [Xx]*) echo -e "\n${cyan}---exiting---\n${norm}" exit 1 ;; *) # check port range validity if [ "${inputPHPPort}" -ge 0 ] && [ "${inputPHPPort}" -le 65535 ]; then phpPort="${inputPHPPort}" break else echo -e "\n${err}Port must be between 0-65535${norm}" fi ;; esac done fi # get PHP-FPM socket file from www.conf if [ "${phpType}" = "sockets" ]; then phpSock=$(find /etc/php/ -name 'www.conf' -type f -exec grep 'listen.*sock$' {} + | sed 's/.*=\ //') # check if phpSock is null if [ -z "${phpSock}" ]; then echo -e "\n${err}Could not auto-detect socket file name${norm}" echo -e "PHP handler will be set up but you will have to edit ${warn}nginx.conf${norm}" echo -e "manually to include the proper socket-file name where you see ${warn}${norm}\n" phpSock='' fi fi ### notify user and generate DHParms if necessary if [ "${generateDH}" -eq 1 ]; then echo -e "\n${mag}---------------------${norm}" echo -e "${cyan}Generating DH-Parameters file... this may take a while${norm}" # delete existing (likely default) dhparam.pem rm -f /etc/ssl/certs/dhparam.pem # generate 4096-bit DHParams and store in /etc/ssl/certs/dhparam.pem openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 # verify non-zero length file generated if [ -s "/etc/ssl/certs/dhparam.pem" ]; then echo -e "${ok}-- dhparam.pem generated --${norm}" # set DHParam to proper location DHPath='/etc/ssl/certs/dhparam.pem' else echo -e "${err}-- error generating dhparam.pem --" echo -e "you should manaully generate this file${norm}" fi fi ### Write configurations to template files # let user know what's happening echo -e "\n${mag}---------------------${norm}" echo -e "${cyan}Updating template files now...${norm}" # copy template files to working versions echo "copying files to dedicated directory for customization:" echo -e "${warn}${configPath}${norm}\n" rm -rf "${configPath}" cp -R ./etc "${configPath}" # process server_names snippet echo -e "updating ${warn}${configPath}/${file_servernames}${norm}" for name in "${serverNames[@]}"; do sed -i "/${tag_servernames}/a \ \ ${name}" "${configPath}/${file_servernames}" done sed -i "/${tag_servernames}/d" "${configPath}/${file_servernames}" # process SSL snippet if using SSL if [ "${useSSL}" -eq 1 ]; then echo -e "updating ${warn}${configPath}/${file_ssl}${norm}" sed -i -e "s%${tag_sslcert}%${CertPath}%" "${configPath}/${file_ssl}" sed -i -e "s%${tag_sslkey}%${KeyPath}%" "${configPath}/${file_ssl}" fi # process mozModern SSL configuration if using SSL if [ "${useSSL}" -eq 1 ]; then echo -e "updating ${warn}${configPath}/${file_mozmodern}${norm}" sed -i -e "s%${tag_dhparam}%${DHPath}%" "${configPath}/${file_mozmodern}" sed -i -e "s%${tag_cachain}%${CAChainPath}%" "${configPath}/${file_mozmodern}" # comment OSCP lines if noOSCP=1 if [ "${noOSCP}" -eq 1 ]; then sed 's/^ssl_stapling/#ssl_stapling/g' "${configPath}/${file_mozmodern}" sed 's/^ssl_trusted/#ssl_trusted/' "${configPath}/${file_mozmodern}" fi fi # process LAN IP in nginx.conf echo -e "updating ${warn}${configPath}/${file_lanip}${norm}" sed -i "s/${tag_lanip}/${IP4}/" "${configPath}/${file_lanip}" # if using PHP: process buffers.conf if [ "${usePHP}" -eq 1 ]; then echo -e "updating ${warn}${configPath}/${file_buffers}${norm}" cat "${configPath}/${file_buffersPHP}" >> "${configPath}/${file_buffers}" fi ## if using PHP-TCP: add upstream handler in nginx.conf if [ "${phpType}" = "tcp" ]; then echo -e "updating ${warn}${configPath}/${file_phphandler}${norm}" # update .insert tags with address and port information sed -i "s/${tag_phpAddr}/${phpAddr}/" "${configPath}/${file_phpTCP}" sed -i "s/${tag_phpPort}/${phpPort}/" "${configPath}/${file_phpTCP}" # copy .insert file into nginx.conf sed -i -e "/${tag_phphandler}/{r ${configPath}/${file_phpTCP}" -e 'd}' "${configPath}/${file_phphandler}" fi # if using PHP-SOCK: add upstream handler in nginx.conf if [ "${phpType}" = "sockets" ]; then echo -e "updating ${warn}${configPath}/${file_phphandler}${norm}" # update .insert tag with version number sed -i "s/${tag_phpSock}/${phpSock}/" "${configPath}/${file_phpSock}" # copy .insert file into nginx.conf sed -i -e "/${tag_phphandler}/{r ${configPath}/${file_phpSock}" -e 'd}' "${configPath}/${file_phphandler}" fi # notify user file updates are completed echo -e "${ok}...files updated${norm}" # debug section echo -e "\n${mag}---------------------${norm}" echo "Local IP4: $IP4" echo "Server names: ${serverNames[*]}" echo -e "${cyan}--------------------${norm}" echo "Using Certbot: $useCertbot" echo "CertbotDomain: $CertbotDomain" echo -e "${cyan}--------------------${norm}" echo "CertPath: $CertPath" echo "KeyPath: $KeyPath" echo "CA-Chain: $CAChainPath" echo "DHPath: $DHPath" echo "Generating DH Params? $generateDH" echo -e "${cyan}--------------------${norm}" echo "usePHP: $usePHP" echo "PHP listen type: $phpType" echo "PHP address: $phpAddr" echo "PHP port: $phpPort" echo "PHP socket: $phpSock" echo -e "${mag}---------------------${norm}\n" exit 0