327 lines
8.5 KiB
Bash
327 lines
8.5 KiB
Bash
#!/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()
|
|
{
|
|
local element="$1"; #index to return
|
|
local seperator="$2"; shift; shift
|
|
local array=$@
|
|
local old_IFS=$IFS
|
|
IFS="$seperator"
|
|
set -- $array
|
|
while [ "$element" -gt 0 ]; do
|
|
shift
|
|
element=$(( element - 1 ))
|
|
done
|
|
local result=$1
|
|
IFS=$old_IFS
|
|
echo $result
|
|
}
|
|
|
|
# helper function to check if a string contains a substring in dash
|
|
function contains()
|
|
{
|
|
local string="$1"
|
|
local substring="$2"
|
|
[ -z $(echo "$string" | sed "/$substring/d") ]
|
|
}
|
|
# helper function to check if string begins with substring in dash
|
|
function begins_with()
|
|
{
|
|
local string=$1
|
|
local substring=$2
|
|
[ -z $(echo "$string" | sed "/^$substring/d") ]
|
|
}
|
|
# extra function for readability
|
|
function has_port()
|
|
{
|
|
contains $1 ":"
|
|
}
|
|
|
|
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()
|
|
{
|
|
local rng_n=$(od -An -N2 -d < /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"
|
|
|
|
# default to 8080 if server port is undefined
|
|
local port=":8080";
|
|
has_port $server && port=""
|
|
|
|
# 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;
|
|
|
|
error_page 502 503 504 @error;
|
|
|
|
# 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;
|
|
set \$upstream $server;
|
|
|
|
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;
|
|
proxy_pass http://\$upstream$port;
|
|
|
|
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;
|
|
proxy_set_header X-Forwarded-Upstream \$upstream;
|
|
}
|
|
|
|
location /0x90 {
|
|
ssi on;
|
|
error_page 502 503 504 404 @error;
|
|
# 403 needs to be on its own, get default 503 otherwise
|
|
error_page 403 /0x90/errors/error_403.html;
|
|
# set alias (instead of root) to not append /0x90 to path
|
|
include /etc/nginx/mime.types;
|
|
alias /service/html;
|
|
}
|
|
location ~/0x90/?$ { error_page 404 @error; deny all; return 404; }
|
|
location ~/0x90/css/?$ { error_page 404 @error; deny all; return 404; }
|
|
location ~/0x90/fonts/?$ { error_page 404 @error; deny all; return 404; }
|
|
location /0x90/errors { error_page 404 @error; deny all; return 404; }
|
|
location /0x90/check { error_page 404 @error; deny all; return 404; }
|
|
|
|
location @error {
|
|
root /service/html;
|
|
internal;
|
|
# replace variables in html
|
|
ssi on;
|
|
try_files
|
|
|
|
/errors/error_\$status.html
|
|
/errors/error_\${status_prefix}x.html
|
|
/errors/error_\${status_category}xx.html
|
|
/errors/error.html;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"
|
|
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 dummy_cert_exists()
|
|
{
|
|
cert_exists 0 nixdomain
|
|
}
|
|
|
|
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 } /#<SERVER>/ { for (i=0;i<n;++i) print a[i]; next }1' \
|
|
$tmp_file /etc/nginx/nginx.conf > "$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" && ! dummy_cert_exists && [ "$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 $@
|