#!/bin/sh # configuration web_root_path="/service/html" cert_root_path="/service/ssl" temp_path="/service/temp" nginx_pid="/tmp/nginx.pid" server_retries=10 log_file="" # leave empty for stdout # variables config_file="$temp_path/nginx.conf" domain_file="$temp_path/domains.txt" certs_updated="$cert_root_path/.updated" certs_daemon_pid=-1 server_configs="" #functions function log() { local time=$(date +%F_%T) local prefix="LOG" case $1 in ERR | ERROR) prefix="ERR" shift ;; WAR | WARNING) prefix="WAR" shift ;; esac local func=$1; shift # remove any \n #local text=$(echo "$@"| tr '\n' '#' | sed 's|#|\\n |g') case "$log_file" in "") echo "[$time]:[$prefix]:[$0][$func]: $@" 2>&1 ;; *) echo "[$time]:[$prefix]:[$0][$func]: $@" >> $log_file ;; esac } # this is a helper function as dash does not support arrays function get_element() { element="$1" seperator="$2"; shift; shift array=$@ old_IFS=$IFS IFS="$seperator" set -- $array while [ "$element" -gt 0 ]; do shift element=$(( element - 1 )) done result=$1 IFS=$old_IFS echo $result } function sleep_wait() { # waiting on sleep to be able to handle SIGTERM sleep $1 & wait $! } function exit_handler() { # TODO: because nginx is started in foreground and replaces # the shell via exec, this handler is never called, however as there # is no cleanup for the certs_daemon it doesn't really matter if [ $certs_daemon_pid -gt 0 ] ; then log exit_handler "stopping certs_daemon" kill -SIGTERM $certs_daemon_pid fi log exit_handler "shutting down nginx" nginx -s quit log exit_handler "container shutting down" exit } trap "exit_handler" SIGINT trap "exit_handler" SIGTERM function var_to_file() { local var=$1 local file=$2 log var_to_file "ENV VAR -> [$file]: $var" echo -e "$var" > $file } function env_domain_file() { log env_domain_file "ENV DOMAINS: $DOMAINS" echo -e "$DOMAINS" > $1 } function read_file() { local func=$1 local file=$2 local result=0 local count=0 while read -r line || [[ -n "$line" ]]; do count=$(( count+1 )) # skip empty lines if READ_EMPTY_LINES is not set [ -z ${READ_EMPTY_LINES+x} ] && [ "${#line}" -lt 1 ] && continue log read_file "LINE[$count]: $line" log read_file "FUNC START [$func]" if $func $count $line; then log read_file "FUNC END [$func]" else result=1 log ERROR read_file "FUNC END [$func]" fi done < "$file" return $result } function rng() { rng_n=$(od -An -N2 -i < /dev/urandom) echo $rng_n } function gen_server_config() { local server=$1; shift local root_domain=$1; shift local domain_list="$@" local domain_count=0 local domains="$root_domain" # no arrays in dash local subdomain=$(get_element $domain_count " " $domain_list) while [ ${#subdomain} -gt 0 ]; do domains="$domains ${subdomain}.$root_domain" domain_count=$(( domain_count + 1 )) subdomain=$(get_element $domain_count " " $domain_list) done local rng_number=$(rng) local server_var=" server { listen 8443 ssl; # prevent redirecting to 8080 port # (e.g. when trailing slash is missed) port_in_redirect off; server_name $domains; ssl_certificate /service/ssl/$root_domain/fullchain.pem; ssl_certificate_key /service/ssl/$root_domain/privkey.pem; location / { # allow for upstream to be offline by setting # docker dns resolver and a variable for the server resolver 127.0.0.11 valid=30s; set \$web_$rng_number $server; proxy_pass http://\$web_$rng_number:8080; proxy_redirect off; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host \$server_name; } } " echo "$server_var" } function server_config() { local server_number=$1; shift local server_name=$(get_element $(( server_number-1 )) " " $SERVER) local server_domain=$@ if [ ${#server_name} -eq 0 ]; then log server_config "no name for server name for domains [$server_domain]" return 1 fi log server_config "creating config for [$server_name] with domains [$server_domain]" server_configs="$server_configs$(gen_server_config $server_name $server_domain)" } function cert_exists() { local domain=$2 local cert_path="$cert_root_path/$domain" [ -f "$cert_path/fullchain.pem" ] } function certs_daemon() { ( # wait for nginx to start while [ ! -f "$nginx_pid" ]; do log certs_daemon "Waiting for nginx to start" sleep_wait 1 done # wait for inital cert update while [ ! -f "$certs_updated" ]; do log certs_daemon "Waiting for initial cert update" sleep_wait 1 done # reload nginx nginx -s reload # cert watcher loop while inotifywait -e attrib "$certs_updated"; do log certs_daemon "Certs updated, reloading nginx" nginx -s reload done ) & certs_daemon_pid=$! } function nginx_handler() { local retries=0 # start the daemon (it forks itself) certs_daemon # generate server config read_file server_config "$domain_file" tmp_file="$temp_path/tmp.conf" echo -e "$server_configs" > $tmp_file awk 'NR==FNR { a[n++]=$0; next } /#/ { for (i=0;i "$config_file" # wait for certs to exist before starting nginx # just have nginx crash after n retries (default 10) while ! read_file cert_exists "$domain_file" && [ "$retries" -lt "$server_retries" ]; do log nginx_handler "Waiting for certs to be created" retries=$(( retries+1 )) sleep_wait 1 done # starting nginx in foreground log nginx_handler "Starting nginx in foreground" exec nginx -c "$config_file" -g "daemon off;" } function main() { log main "Starting nginx container" # TODO: load domains from nginx.conf env_domain_file "$domain_file" nginx_handler } main $@