#!/bin/bash
#
# script: rc.nginx
#
# Nginx daemon control script.
# Written for Slackware Linux by Cherife Li <cherife-#-dotimes.com>.

# LimeTech - modified for Unraid OS
# Bergware - modified for Unraid OS, May 2025

# reference:
# LANNAME                     'tower'
# LANMDNS               'tower.local'
# LANFQDN  'lan-ip.hash.myunraid.net'  (wildcard cert)
# WANFQDN  'wan-ip.hash.myunraid.net'  (wildcard cert)
# WG0FQDN  'wg0-ip.hash.myunraid.net'  (wildcard cert)

DAEMON="Nginx server daemon"
CALLER="nginx"
NGINX="/usr/sbin/nginx"
TS="/usr/local/sbin/tailscale"
PID="/var/run/nginx.pid"
SSL="/boot/config/ssl"
CONF="/etc/nginx/nginx.conf"
IDENT="/boot/config/ident.cfg"
SERVERS="/etc/nginx/conf.d/servers.conf"
LOCATIONS="/etc/nginx/conf.d/locations.conf"
INI="/var/local/emhttp/nginx.ini.new"
CERTPATH="$SSL/certs/certificate_bundle.pem"
TSCERTPATH="$SSL/certs/ts_bundle.pem"
MYSERVERS="/boot/config/plugins/dynamix.my.servers/myservers.cfg"
DEFAULTS="/etc/default/nginx"
SYSTEM="/sys/class/net"
SYSLOG="/var/log/syslog"

# Load defaults
# Defines NGINX_CUSTOMFA for custom Content-Security-Policy frame-ancestors url
[[ -r $DEFAULTS ]] && . $DEFAULTS

# hold server names
SERVER_NAMES=()

# read Unraid settings
[[ -r $IDENT ]] && . <(fromdos <$IDENT)

# preset default values
[[ -z $START_PAGE ]] && START_PAGE=Main
[[ -z $PORT ]] && PORT=80
[[ -z $PORTSSL ]] && PORTSSL=443
[[ -z $USE_SSL ]] && USE_SSL=no
[[ $PORTSSL != 443 ]] && PORTSSL_URL=":$PORTSSL"
[[ $PORT != 80 ]] && PORT_URL=":$PORT"

# delete legacy unraid.net certificate
if [[ -f $CERTPATH ]]; then
  TMPCERTNAME=$(openssl x509 -noout -subject -nameopt multiline -in $CERTPATH | sed -n 's/ *commonName *= //p')
  [[ $TMPCERTNAME == *\.unraid\.net ]] && rm $CERTPATH
fi

# if USE_SSL="auto" and no uploaded cert, treat like USE_SSL="no"
[[ $USE_SSL == auto && ! -f $CERTPATH ]] && USE_SSL=no

# override default page if no regkey
if ! find /boot/config/*.key &>/dev/null; then
  START_PAGE="Tools/Registration"
fi

# run & log functions
. /etc/rc.d/rc.runlog

# library functions
. /etc/rc.d/rc.library.source

fqdn(){
  echo ${CERTNAME/'*'/${1//[.:]/-}}
}

# create listening ports
listen(){
  T='    '
  if check && [[ $1 == lo ]]; then
    if [[ $IPV4 == yes ]]; then
      echo "${T}listen 127.0.0.1:$PORT; # lo"
      echo "${T}listen 127.0.0.1:$PORTSSL; # lo"
    fi
    if [[ $IPV6 == yes ]]; then
      echo "${T}listen [::1]:$PORT; # lo"
      echo "${T}listen [::1]:$PORTSSL; # lo"
    fi
  elif [[ -n $BIND ]]; then
    for ADDR in $BIND; do
      [[ $(ipv $ADDR) == 4 ]] && echo "${T}listen $ADDR:$*; # $(show $ADDR)"
      [[ $(ipv $ADDR) == 6 ]] && echo "${T}listen [$ADDR]:$*; # $(show $ADDR)"
    done
  else
    # default listen on any interface with ipv4 protocol
    echo "${T}listen $*;"
  fi
}

# create redirect server blocks
redirect(){
  T='    '
  if check && [[ -n $BIND ]]; then
    URL=$1
    TAG=$2
    shift 2
    case $URL in
    'host')
      echo "server {"
      for ADDR in $BIND; do
        HOST=
        [[ $(ipv $ADDR) == 4 ]] && HOST="$ADDR"
        [[ $(ipv $ADDR) == 6 ]] && HOST="[$ADDR]"
        [[ -n $HOST ]] && echo "${T}listen $HOST:$*; # $(show $ADDR)"
      done
      echo "${T}add_header Content-Security-Policy \"frame-ancestors 'self' $NGINX_CUSTOMFA\";"
      echo "${T}return 302 https://\$host:$PORTSSL\$request_uri;"
      echo "}"
      ;;
    'fqdn')
      for ADDR in $BIND; do
        HOST=
        [[ $TAG == 4 && $(ipv $ADDR) == 4 ]] && HOST="$ADDR"
        [[ $TAG == 6 && $(ipv $ADDR) == 6 ]] && HOST="[$ADDR]"
        if [[ -n $HOST ]]; then
          echo "server {"
          echo "${T}listen $HOST:$*; # $(show $ADDR)"
          echo "${T}add_header Content-Security-Policy \"frame-ancestors 'self' $NGINX_CUSTOMFA\";"
          echo "${T}return 302 https://$(fqdn $ADDR)$PORTSSL_URL\$request_uri;"
          echo "}"
        fi
      done
      ;;
    esac
  fi
}

# build our servers
# pay attention to escaping
build_servers(){
  cat <<- 'EOF' >$SERVERS
	#
	# Listen on local socket for nchan publishers
	#
	server {
	    listen unix:/var/run/nginx.socket default_server;
	    location ~ /pub/(.*)$ {
	        nchan_publisher;
	        nchan_channel_id "$1";
	        nchan_message_buffer_length $arg_buffer_length;
	        nchan_message_timeout 0;
	    }
	    location ~ /nchan_stub_status$ {
	        nchan_stub_status;
	    }
	}
	EOF
  cat <<- EOF >>$SERVERS
	#
	# Always accept http requests from localhost
	# ex: http://localhost
	# ex: http://127.0.0.1
	# ex: http://[::1]
	#
	server {
	$(listen lo)
	    #
	    add_header Content-Security-Policy "frame-ancestors 'self' $NGINX_CUSTOMFA";
	    include /etc/nginx/conf.d/locations.conf;
	}
	EOF
  if [[ $USE_SSL == no ]]; then
    cat <<- EOF >>$SERVERS
	#
	# Port settings for http protocol
	# ex: http://tower  (IP address resolved via NetBIOS)
	# ex: http://tower.local  (IP address resolved via mDNS)
	# ex: http://192.168.1.100
	# ex: http://[::ffff:192.168.1.100]
	#
	server {
	$(listen $PORT default_server)
	    #
	    add_header Content-Security-Policy "frame-ancestors 'self' $NGINX_CUSTOMFA";
	    location ~ /wsproxy/$PORT/ { return 403; }
	    include /etc/nginx/conf.d/locations.conf;
	}
	EOF
  elif [[ $USE_SSL == yes ]]; then
    cat <<- EOF >>$SERVERS
	#
	# Port settings for https protocol (self-signed cert)
	# ex: https://tower.local
	#
	server {
	$(listen $PORTSSL ssl default_server)
	    http2 on;
	    add_header Content-Security-Policy "frame-ancestors 'self' $SELFCERTFA $NGINX_CUSTOMFA";
	    # Ok to use concatenated pem files; nginx will do the right thing.
	    ssl_certificate         $SELFCERTPATH;
	    ssl_certificate_key     $SELFCERTPATH;
	    ssl_trusted_certificate $SELFCERTPATH;
	    #
	    # OCSP stapling
	    ssl_stapling $SELFCERTSTAPLE;
	    ssl_stapling_verify $SELFCERTSTAPLE;
	    #
	    location ~ /wsproxy/$PORTSSL/ { return 403; }
	    include /etc/nginx/conf.d/locations.conf;
	}
	#
	# Redirect http requests to https
	# ex: http://tower.local -> https://tower.local
	#
	$(redirect host 0 $PORT default_server)
	EOF
  elif [[ $USE_SSL == auto ]]; then
    if [[ -n $LANFQDN ]]; then
      cat <<- EOF >>$SERVERS
	#
	# Redirect http requests to https
	# ex: http://tower.local -> https://lan-ip.hash.myunraid.net
	# ex: http://192.168.1.100 -> https://lan-ip.hash.myunraid.net
	#
	$(redirect fqdn 4 $PORT default_server)
	EOF
    fi
    if [[ -n $LANFQDN6 ]]; then
      cat <<- EOF >>$SERVERS
	#
	# Redirect http requests to https
	# ex: http://[::ffff:192.168.1.100] -> https://lan-ip.hash.myunraid.net
	#
	$(redirect fqdn 6 $PORT default_server)
	EOF
    fi
    cat <<- EOF >>$SERVERS
	#
	# Return 404 (Not Found) as default ssl action, using self-signed cert
	#
	server {
	$(listen $PORTSSL ssl default_server)
	    http2 on;
	    add_header Content-Security-Policy "frame-ancestors 'self' $SELFCERTFA $NGINX_CUSTOMFA";
	    # Ok to use concatenated pem files; nginx will do the right thing.
	    ssl_certificate         $SELFCERTPATH;
	    ssl_certificate_key     $SELFCERTPATH;
	    ssl_trusted_certificate $SELFCERTPATH;
	    #
	    # OCSP stapling
	    ssl_stapling $SELFCERTSTAPLE;
	    ssl_stapling_verify $SELFCERTSTAPLE;
	    return 404;
	}
	EOF
  fi
  if [[ -f $CERTPATH ]]; then
    if [[ $USE_SSL == no ]]; then
      cat <<- EOF >>$SERVERS
	#
	# Return 404 (Not Found) as default ssl action
	#
	server {
	$(listen $PORTSSL ssl default_server)
	    http2 on;
	    add_header Content-Security-Policy "frame-ancestors 'self' $SELFCERTFA $NGINX_CUSTOMFA";
	    # Ok to use concatenated pem files; nginx will do the right thing.
	    ssl_certificate         $SELFCERTPATH;
	    ssl_certificate_key     $SELFCERTPATH;
	    ssl_trusted_certificate $SELFCERTPATH;
	    #
	    # OCSP stapling
	    ssl_stapling $SELFCERTSTAPLE;
	    ssl_stapling_verify $SELFCERTSTAPLE;
	    return 404;
	}
	EOF
    fi
    if [[ -n $LANFQDN || -n $LANFQDN6 ]]; then
      cat <<- EOF >>$SERVERS
	#
	# Port settings for https using CA-signed cert
	# ex: https://lan-ip.hash.myunraid.net
	#
	server {
	$(listen $PORTSSL ssl)
	    http2 on;
	    server_name ${SERVER_NAMES[@]};
	    add_header Content-Security-Policy "frame-ancestors 'self' $CERTFA $NGINX_CUSTOMFA";
	    # Ok to use concatenated pem files; nginx will do the right thing.
	    ssl_certificate         $CERTPATH;
	    ssl_certificate_key     $CERTPATH;
	    ssl_trusted_certificate $CERTPATH;
	    #
	    # OCSP stapling
	    ssl_stapling $CERTSTAPLE;
	    ssl_stapling_verify $CERTSTAPLE;
	    #
	    location ~ /wsproxy/$PORTSSL/ { return 403; }
	    include /etc/nginx/conf.d/locations.conf;
	}
	EOF
    fi
  fi
  if [[ -n $TSFQDN ]]; then
    cat <<- EOF >>$SERVERS
	#
	# Redirect Tailscale http requests to https
	# ex: http://tower.magicDNS.ts.net -> https://tower.magicDNS.ts.net
	#
	server {
	$(listen $PORT)
	    server_name $TSFQDN;
	    return 302 https://$TSFQDN$PORTSSL_URL$request_uri;
	}
	#
	# Port settings for https using Tailscale cert
	# ex: https://tower.magicDNS.ts.net
	#
	server {
	$(listen $PORTSSL ssl http2)
	    server_name $TSFQDN;
	    add_header Content-Security-Policy "frame-ancestors 'self' $TSFA $NGINX_CUSTOMFA";
	    # Ok to use concatenated pem files; nginx will do the right thing.
	    ssl_certificate         $TSCERTPATH;
	    ssl_certificate_key     $TSCERTPATH;
	    ssl_trusted_certificate $TSCERTPATH;
	    #
	    # OCSP stapling
	    ssl_stapling on;
	    ssl_stapling_verify on;
	    #
	    location ~ /wsproxy/$PORTSSL/ { return 403; }
	    include /etc/nginx/conf.d/locations.conf;
	}
	EOF
  fi
}

# build our locations
# pay attention to escaping
build_locations(){
  cat <<- EOF >$LOCATIONS
	#
	# Default start page
	#
	location = / {
	    return 302 \$scheme://\$http_host/$START_PAGE;
	}
	EOF
  cat <<- 'EOF' >>$LOCATIONS
	#
	# Redirect to login page for authentication
	#
	location /login {
	    allow all;
	    limit_req zone=authlimit burst=20 nodelay;
	    try_files /login.php =404;
	    include fastcgi_params;
	}
	location /logout {
	    allow all;
	    try_files /login.php =404;
	    include fastcgi_params;
	}
	#
	# Redirect to login page on failed authentication (401)
	#
	error_page 401 @401;
	location @401 {
	    return 302 $scheme://$http_host/login;
	}
	#
	# deny access to any hidden file (beginning with a .period)
	#
	location ~ /\. {
	    return 404;
	}
	#
	# page files handled by template.php
	#
	location ~^/[A-Z].* {
	    try_files $uri /webGui/template.php$is_args$args;
	}
	#
	# nchan subscriber endpoint
	#
	location ~ /sub/(.*)$ {
	    nchan_subscriber;
	    nchan_subscriber_timeout 0;
	    # nchan_authorize_request <url here>
	    nchan_channel_id "$1";
	    nchan_channel_id_split_delimiter ",";
	}
	location /nchan_stub_status {
	    nchan_stub_status;
	}
	#
	# my servers proxy
	#
	location /graphql {
	    allow all;
	    error_log /dev/null crit;
	    proxy_pass http://unix:/var/run/unraid-api.sock:/graphql;
	    proxy_http_version 1.1;
	    proxy_set_header Host $host;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection $connection_upgrade;
	    proxy_cache_bypass $http_upgrade;
	    proxy_intercept_errors on;
	    error_page 502 = @graph502;
	}
	location @graph502 {
	    default_type application/json;
	    return 200 '{"errors":[{"error":{"name":"InternalError","message":"Graphql is offline."}}]}';
	}
	#
	# websocket proxy
	#
	location ~ /wsproxy/(.*)$ {
	    proxy_read_timeout 3600;
	    proxy_pass http://127.0.0.1:$1;
	    proxy_http_version 1.1;
	    proxy_set_header Host $host;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection $connection_upgrade;
	}
	#
	# add Cache-Control headers to novnc
	#
	location ~ /plugins\/dynamix.vm.manager\/novnc/(.*)$ {
	    gzip on;
	    gzip_disable "MSIE [1-6]\.";
	    gzip_types text/css application/javascript text/javascript application/x-javascript;
	    add_header Cache-Control no-cache;
	}
	#
	# pass PHP scripts to FastCGI server listening on unix:/var/run/php-fpm.sock
	#
	location ~ ^(.+\.php)(.*)$ {
	    fastcgi_split_path_info  ^(.+\.php)(.*)$;
	    fastcgi_param PATH_INFO  $fastcgi_path_info;
	    include fastcgi_params;
	}
	#
	# enable compression of JS/CSS/WOFF files
	# if version tag on querystring, tell browser to cache indefinitely
	#
	location ~ \.(js|css|woff)$ {
	    gzip on;
	    gzip_disable "MSIE [1-6]\.";
	    gzip_types text/css application/javascript text/javascript application/x-javascript application/font-woff font-woff;
	    if ( $args ~ "v=" ) {
	        expires max;
	    }
	}
	#
	# robots.txt available without authentication
	#
	location = /robots.txt {
	    add_header Access-Control-Allow-Origin *; #robots.txt any origin
	    allow all;
	}
	#
	# redirect.htm available without authentication
	#
	location = /redirect {
	    rewrite ^ /redirect.htm break;
	    allow all;
	}
	#
	# proxy update.htm and logging.htm scripts to emhttpd listening on local socket
	#
	location = /update.htm {
	    keepalive_timeout 0;
	    proxy_read_timeout 180; # 3 minutes
	    proxy_pass http://unix:/var/run/emhttpd.socket:/update.htm;
	}
	location = /logging.htm {
	    proxy_read_timeout 864000; # 10 days(!)
	    proxy_pass http://unix:/var/run/emhttpd.socket:/logging.htm;
	}
	#
	# proxy webterminal to ttyd server listening on unix:/var/run/<tag>.sock
	#
	location ~ /webterminal/(.*)/(.*)$ {
	    proxy_read_timeout 864000; # 10 days(!)
	    proxy_pass http://unix:/var/run/$1.sock:/$2;
	    proxy_http_version 1.1;
	    proxy_set_header Host $host;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection $connection_upgrade;
	}
	location = /webterminal/auth_token.js {
	    return 204;
	}
	#
	# proxy logterminal to ttyd server listening on unix:/var/tmp/<tag>.sock
	#
	location ~ /logterminal/(.*)/(.*)$ {
	    proxy_read_timeout 864000; # 10 days(!)
	    proxy_pass http://unix:/var/tmp/$1.sock:/$2;
	    proxy_http_version 1.1;
	    proxy_set_header Host $host;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection $connection_upgrade;
	}
	EOF
}

# check if certificate common name or any alternative name matches LANMDNS
acceptable_selfcert(){
  local CN
  for CN in $(openssl x509 -noout -subject -nameopt multiline -in $SELFCERTPATH | sed -n 's/ *commonName *= //p' ;
              openssl x509 -noout -ext subjectAltName -in $SELFCERTPATH | grep -Eo "DNS:[a-zA-Z 0-9.*-]*" | sed "s/DNS://g"); do
    CN=${CN/\*/$LANNAME} # support wildcard custom certs
    [[ ${CN,,} = ${LANMDNS,,} ]] && return 0
  done
  return 1
}

build_ssl(){
  mkdir -p $SSL/certs

  if [[ ! -f $SSL/dhparam.pem ]]; then
    # regenerate dhparam file
    # use -dsaparam per: https://security.stackexchange.com/questions/95178/diffie-hellman-parameters-still-calculating-after-24-hours
    echo "Regenerating dhparam..."
    openssl dhparam -dsaparam -out $SSL/dhparam.pem 2048 &>/dev/null
  fi
  ln -sf $SSL/dhparam.pem /etc/nginx/dhparam.pem

  LANNAME=$(hostname)
  LANMDNS=${LANNAME}${LOCAL_TLD:+.$LOCAL_TLD}

  # fetch LAN IP address (read management interface eth0)
  [[ -e $SYSTEM/bond0 ]] && DEV=bond0 || DEV=eth0
  [[ -e $SYSTEM/br0 ]] && DEV=br0
  LANIP=$(ip -4 -br addr show scope global primary dev $DEV | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
  LANIP6=$(ip -6 -br addr show scope global primary -deprecated dev $DEV | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')

  # try wireless connection if no IP address on interface eth0
  [[ -z $LANIP && -e $SYSTEM/wlan0 ]] && LANIP=$(ip -4 -br addr show scope global primary dev wlan0 | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
  [[ -z $LANIP6 && -e $SYSTEM/wlan0 ]] && LANIP6=$(ip -6 -br addr show scope global primary -deprecated dev wlan0 | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')

  # regenerate self-signed cert if local TLD changes */
  SELFCERTPATH=$SSL/certs/${LANNAME}_unraid_bundle.pem
  [[ -f $SELFCERTPATH ]] && ! acceptable_selfcert && rm -f $SELFCERTPATH
  if [[ ! -f $SELFCERTPATH ]]; then
    # regenerate private key and certificate
    echo "Regenerating private key and certificate..."
    openssl_subject="/O=Self-signed/OU=Unraid/CN=$LANMDNS"
    openssl_altname="DNS:$LANMDNS"
    openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -sha512 -keyout /tmp/key.pem -out /tmp/cert.pem -subj "$openssl_subject" -extensions SAN -config <(cat /etc/ssl/openssl.cnf; printf "[SAN]\nsubjectAltName=${openssl_altname}") &>/dev/null
    cat /tmp/cert.pem /tmp/key.pem >$SELFCERTPATH
    rm -f /tmp/cert.pem /tmp/key.pem
  fi
  # determine if OCSP stapling should be enabled for this cert
  [[ -n $(openssl x509 -noout -ocsp_uri -in "$SELFCERTPATH") ]] && SELFCERTSTAPLE=on || SELFCERTSTAPLE=off
  # define CSP frame-ancestors for the self-signed cert
  [[ -n $LOCAL_TLD ]] && [[ "$LOCAL_TLD" != "local" ]] && SELFCERTFA="https://*.$LOCAL_TLD/"

  # handle Certificate Authority signed cert if present
  if [[ -f $CERTPATH ]]; then
    # extract common name from cert
    CERTNAME=$(openssl x509 -noout -subject -nameopt multiline -in $CERTPATH | sed -n 's/ *commonName *= //p')
    # define CSP frame-ancestors for cert
    CERTFA="https://*.${CERTNAME#*.}/"
    # check if Remote Access is enabled and fetch WANIP
    if [[ -L /usr/local/sbin/unraid-api ]] && grep -qs 'wanaccess="yes"' $MYSERVERS && ! grep -qs 'username=""' $MYSERVERS; then
      WANACCESS=yes
      WANIP=$(curl https://wanip4.unraid.net/ 2>/dev/null)
      WANIP6=$(curl https://wanip6.unraid.net/ 2>/dev/null)
    fi
    if [[ $CERTNAME == *\.myunraid\.net ]]; then
      # wildcard LE certificate
      # add Unraid Connect to CSP frame-ancestors for a myunraid.net cert
      CERTFA+=" https://connect.myunraid.net/"
      [[ -n $LANIP ]] && LANFQDN=$(fqdn $LANIP) SERVER_NAMES+=($LANFQDN)
      [[ -n $LANIP6 ]] && LANFQDN6=$(fqdn $LANIP6) SERVER_NAMES+=($LANFQDN6)
      # check if remote access enabled
      if [[ -n $WANACCESS ]]; then
        [[ -n $WANIP ]] && WANFQDN=$(fqdn $WANIP) SERVER_NAMES+=($WANFQDN)
        [[ -n $WANIP6 ]] && WANFQDN6=$(fqdn $WANIP6) SERVER_NAMES+=($WANFQDN6)
      fi
      if check; then
        # add included interfaces
        declare -A NET_FQDN NET_FQDN6
        for ADDR in $BIND; do
          # convert IP to name
          NET=$(show $ADDR)
          # skip invalid interface, LAN interface and WG VPN tunneled interfaces
          [[ -z $NET || $(show $LANIP) == $NET || (${NET:0:2} == wg && $(scan TYPE:1 $WIREGUARD/$NET.cfg) -ge 7) ]] && continue
          [[ $(ipv $ADDR) == 4 ]] && NET_FQDN[$NET]=$(fqdn $ADDR) || NET_FQDN6[$NET]=$(fqdn $ADDR)
          SERVER_NAMES+=($(fqdn $ADDR))
        done
      fi
    else
      # custom certificate, this would be better as SELFCERTPATH
      LANFQDN=${CERTNAME/\*/$LANNAME} # support wildcard custom certs
      SERVER_NAMES+=($LANFQDN)
    fi
    # determine if OCSP stapling should be enabled for this cert
    [[ -n $(openssl x509 -noout -ocsp_uri -in "$CERTPATH") ]] && CERTSTAPLE=on || CERTSTAPLE=off
  fi

  # handle TS cert if present
  if [[ -f "$TSCERTPATH" ]]; then
    # confirm TS is intalled and running
    if [[ -x $TS ]] && $TS status &>/dev/null; then
      # extract common name from cert
      TSFQDN1=$(openssl x509 -noout -subject -nameopt multiline -in "$TSCERTPATH" | sed -n 's/ *commonName *= //p')
      # get tailscale domain
      TSFQDN2=$($TS status -json | jq ' .Self.DNSName' | tr -d '"' | sed 's/.$//')
      if [[ -n "$TSFQDN1" ]] && [[ "$TSFQDN1" == "$TSFQDN2" ]]; then
        # common name and tailscale domain are equal and not empty, the cert is valid, use it
        TSFQDN=$TSFQDN1
        # define CSP frame-ancestors for TS cert
        TSFA="https://*.${TSFQDN#*.}/"
      fi
    fi
  fi

  # build servers configuration file
  build_servers
  # build locations configuration file
  build_locations

  # define the default URL used to access the server
  if [[ $USE_SSL == auto ]]; then
    [[ -n $LANIP && $(ipv $LANIP) == 4 ]] && DEFAULTURL="https://$LANFQDN$PORTSSL_URL"
    [[ -n $LANIP && $(ipv $LANIP) == 6 ]] && DEFAULTURL="https://[$LANFQDN6]$PORTSSL_URL"
  elif [[ $USE_SSL == yes ]]; then
    DEFAULTURL="https://$LANMDNS$PORTSSL_URL"
  else
    DEFAULTURL="http://$LANMDNS$PORT_URL"
  fi

  mkdir -p $(dirname "$INI")
  # always defined:
  echo "NGINX_LANIP=\"$LANIP\"" >$INI
  echo "NGINX_LANIP6=\"$LANIP6\"" >>$INI
  echo "NGINX_LANNAME=\"$LANNAME\"" >>$INI
  echo "NGINX_LANMDNS=\"$LANMDNS\"" >>$INI
  echo "NGINX_BIND=\"$BIND\"" >>$INI
  echo "NGINX_CERTPATH=\"$CERTPATH\"" >>$INI
  echo "NGINX_USESSL=\"$USE_SSL\"" >>$INI
  echo "NGINX_PORT=\"$PORT\"" >>$INI
  echo "NGINX_PORTSSL=\"$PORTSSL\"" >>$INI
  echo "NGINX_DEFAULTURL=\"$DEFAULTURL\"" >>$INI
  # defined if certificate_bundle.pem present:
  echo "NGINX_CERTNAME=\"$CERTNAME\"" >>$INI
  echo "NGINX_LANFQDN=\"$LANFQDN\"" >>$INI
  echo "NGINX_LANFQDN6=\"$LANFQDN6\"" >>$INI
  # defined if remote access enabled:
  echo "NGINX_WANACCESS=\"$WANACCESS\"" >>$INI
  echo "NGINX_WANIP=\"$WANIP\"" >>$INI
  echo "NGINX_WANIP6=\"$WANIP6\"" >>$INI
  echo "NGINX_WANFQDN=\"$WANFQDN\"" >>$INI
  echo "NGINX_WANFQDN6=\"$WANFQDN6\"" >>$INI
  # defined if ts_bundle.pem present:
  echo "NGINX_TAILSCALEFQDN=\"$TSFQDN\"" >>$INI
  # add included interfaces
  for NET in ${!NET_FQDN[@]}; do
    echo "NGINX_${NET^^}FQDN=\"${NET_FQDN[$NET]}\"" >>$INI
  done
  for NET in ${!NET_FQDN6[@]}; do
    echo "NGINX_${NET^^}FQDN6=\"${NET_FQDN6[$NET]}\"" >>$INI
  done
  # atomically update file
  mv $INI ${INI%.*}
}

unraid_api_control(){
  # signal unraid-api script, if installed
  if [[ -f /etc/rc.d/rc.unraid-api ]]; then
    /etc/rc.d/rc.unraid-api $1
  fi
}

nginx_running(){
  sleep 0.1
  [[ -s $PID && -n "$(cat $PID)" && -d "/proc/$(cat $PID)" ]] && return 0 || return 1
}

nginx_waitfor_shutdown(){
  for i in {1..10}; do
    if ! nginx_running; then break; fi
    sleep 1
  done
  return 0
}

nginx_check(){
  log "Checking configuration for correct syntax and then trying to open files referenced in configuration..."
  run $NGINX -t -c $CONF
}

nginx_start(){
  log "Starting $DAEMON..."
  local REPLY
  if nginx_running; then
    REPLY="Already started"
  elif [[ ! -r $CONF ]]; then
    # sanity checks, no config file, exit
    log "$CONF does not exist, aborting."
    exit 1
  else
    # build ssl configuration file
    build_ssl
    # nginx does not unlink stale unix sockets before rebinding
    # see: https://trac.nginx.org/nginx/ticket/753
    rm -f /var/run/nginx.socket
    [[ -x $NGINX ]] && $NGINX -c $CONF 2>/dev/null
    # side-load unraid-api
    unraid_api_control start
    # resume nchan publishers
    /usr/local/sbin/monitor_nchan start
    rm -f /tmp/publishPaused

    if nginx_running; then REPLY="Started"; else REPLY="Failed"; fi
  fi
  log "$DAEMON...  $REPLY."
}

nginx_stop(){
  log "Stopping $DAEMON gracefully..."
  local REPLY
  if ! nginx_running; then
    REPLY="Already stopped"
  else
    unraid_api_control stop
    # pause nchan publishers
    /usr/local/sbin/monitor_nchan stop
    kill -QUIT $(cat $PID)
    nginx_waitfor_shutdown
    # safety hammer
    pkill -f $NGINX
    nginx_waitfor_shutdown
    if ! nginx_running; then REPLY="Stopped"; else REPLY="Failed"; fi
  fi
  log "$DAEMON...  $REPLY."
}

nginx_stop_forced(){
  log "Stopping $DAEMON forcibly..."
  local REPLY
  if ! nginx_running; then
    REPLY="Already stopped"
  else
    unraid_api_control stop
    # stop nchan publishers
    /usr/local/sbin/monitor_nchan kill
    kill -TERM $(cat $PID)
    nginx_waitfor_shutdown
    if ! nginx_running; then REPLY="Stopped"; else REPLY="Failed"; fi
  fi
  log "$DAEMON...  $REPLY."
}

nginx_restart(){
  log "Restarting $DAEMON..."
  # only stop working system if configuration is valid
  if nginx_running; then
    if nginx_check; then
      nginx_stop
      nginx_start
    else
      log "Invalid configuration, $DAEMON not restarted"
      return 1
    fi
  else
    log "$DAEMON...  Not running."
  fi
}

nginx_reload(){
  log "Reloading $DAEMON..."
  # only stop working system if configuration is valid
  if nginx_running; then
    build_ssl
    if nginx_check; then
      log "Reloading $DAEMON configuration..."
      # pause nchan publishers
      /usr/local/sbin/monitor_nchan stop
      kill -HUP $(cat $PID)
      sleep 1
      if  tail -10 $SYSLOG | grep -qm1 'Address already in use'; then
        # unconditional restart when binding fails
        sleep 2
        log "Restarting $DAEMON..."
        nginx_renew
      fi
      # resume nchan publishers
      /usr/local/sbin/monitor_nchan start
      rm -f /tmp/publishPaused
    else
      log "Invalid configuration, $DAEMON not reloaded"
      return 1
    fi
  else
    log "$DAEMON...  Not running."
  fi
}

nginx_renew(){
  # stop unconditionally
  pkill -f $NGINX
  # rebuild configuration
  build_ssl
  # start unconditionally
  $NGINX -c $CONF 2>/dev/null
}

nginx_update(){
  if nginx_running && check && [[ "$(this)" != "$BIND" ]]; then
    log "Updating $DAEMON..."
    nginx_reload
  fi
}

nginx_upgrade(){
  if nginx_running; then
    echo "Upgrading to the new Nginx binary."
    echo "Make sure the Nginx binary has been replaced with new one"
    echo "or Nginx server modules were added/removed."
    kill -USR2 $(cat $PID)
    sleep 3
    kill -QUIT $(cat $PID.oldbin)
  fi
}

nginx_rotate(){
  if nginx_running; then
    log "Rotating $DAEMON logs..."
    kill -USR1 $(cat $PID)
  fi
}

nginx_status(){
  if nginx_running; then
    echo "$DAEMON is currently running."
  else
    echo "$DAEMON is not running."
    exit 1
  fi
}

case "$1" in
'check')
  nginx_check
  ;;
'start')
  nginx_start
  ;;
'stop')
  nginx_stop
  ;;
'term')
  nginx_stop_forced
  ;;
'restart')
  nginx_restart
  ;;
'reload')
  nginx_reload
  ;;
'renew')
  nginx_renew
  ;;
'update')
  nginx_update
  ;;
'port')
  echo $PORT
  ;;
'upgrade')
  nginx_upgrade
  ;;
'rotate')
  nginx_rotate
  ;;
'status')
  nginx_status
  ;;
*)
  echo "Usage: $BASENAME check|start|stop|term|restart|reload|renew|update|port|upgrade|rotate|status"
  exit 1
esac
exit 0
