Skip to content

Instantly share code, notes, and snippets.

@sjtuross
Last active July 29, 2023 06:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjtuross/8b7e9d5762b92ca6459f447429c814a4 to your computer and use it in GitHub Desktop.
Save sjtuross/8b7e9d5762b92ca6459f447429c814a4 to your computer and use it in GitHub Desktop.
Support BIND_MGT for nginx on Unraid 6.11.5
#!/bin/bash
#
# Nginx daemon control script.
# Written for Slackware Linux by Cherife Li <cherife-#-dotimes.com>.
# limetech: modified for Unraid
# reference:
# LANNAME 'tower'
# LANMDNS 'tower.local'
# LANFQDN 'lan-ip.hash.myunraid.net' (wildcard cert)
# LANFQDN 'hash.unraid.net' (legacy cert)
# WANFQDN 'wan-ip.hash.myunraid.net' (wildcard cert)
# WANFQDN 'www.hash.unraid.net' (legacy)
BIN=/usr/sbin/nginx
PID=/var/run/nginx.pid
SSL=/boot/config/ssl
CONF=/etc/nginx/nginx.conf
SERVERS=/etc/nginx/conf.d/servers.conf
LOCATIONS=/etc/nginx/conf.d/locations.conf
STATE=/var/local/emhttp/nginx.ini.new
CERTPATH="$SSL/certs/certificate_bundle.pem"
MYSERVERS="/boot/config/plugins/dynamix.my.servers/myservers.cfg"
# read settings
if [[ -a /boot/config/ident.cfg ]]; then
source <(/usr/bin/fromdos < /boot/config/ident.cfg)
fi
# 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"
# bind/unbind GUI
if [[ $BIND_MGT == yes ]]; then
ETH=eth0
[[ -e /sys/class/net/bond0 ]] && ETH=bond0
[[ -e /sys/class/net/br0 ]] && ETH=br0
IPV4=$(ip -4 addr show $ETH|awk '/inet /{gsub(/\/.+$/,"",$2);print $2;exit}')
IPV6=$(ip -6 addr show $ETH noprefixroute|awk '/inet6 /{gsub(/\/.+$/,"",$2);print $2;exit}')
[[ -z $IPV6 ]] && IPV6=$(ip -6 addr show $ETH scope global permanent|awk '/inet6 /{gsub(/\/.+$/,"",$2);print $2;exit}')
fi
[[ -z $IPV4 ]] && IPV4='*'
[[ -z $IPV6 ]] && IPV6='::'
# 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
# pay attention to escaping
define_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;
}
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 127.0.0.1:$PORT;
listen [::1]:$PORT;
#
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 $IPV4:$PORT default_server;
listen [$IPV6]:$PORT default_server;
#
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 $IPV4:$PORTSSL ssl http2 default_server;
listen [$IPV6]:$PORTSSL ssl http2 default_server;
# 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
#
server {
listen $IPV4:$PORT default_server;
listen [$IPV6]:$PORT default_server;
return 302 https://\$host${PORTSSL_URL}\$request_uri;
}
EOF
elif [[ "$USE_SSL" = "auto" ]]; 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
# ex: http://[::ffff:192.168.1.100] https://lan-ip.hash.myunraid.net
#
server {
listen $IPV4:$PORT default_server;
return 302 https://${LANFQDN}${PORTSSL_URL}\$request_uri;
}
server {
listen [$IPV6]:$PORT default_server;
return 302 https://[${LANFQDN6}]${PORTSSL_URL}\$request_uri;
}
#
# Return 404 (Not Found) as default ssl action, using self-signed cert
#
server {
listen $IPV4:$PORTSSL ssl http2 default_server;
listen [$IPV6]:$PORTSSL ssl http2 default_server;
# 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 $IPV4:$PORTSSL ssl http2 default_server;
listen [$IPV6]:$PORTSSL ssl http2 default_server;
# 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
cat <<- EOF >> $SERVERS
#
# Port settings for https using CA-signed cert
# ex: https://lan-ip.hash.myunraid.net
# ex: https://hash.unraid.net
#
server {
listen $IPV4:$PORTSSL ssl http2;
listen [$IPV6]:$PORTSSL ssl http2;
server_name $LANFQDN $LANFQDN6 $WANFQDN $WANFQDN6;
# 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
}
# define our locations
# pay attention to escaping
define_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_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/php5-fpm.sock
#
location ~ \.php$ {
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 {
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 certiicate 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
}
check_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 -s)
LANMDNS=${LANNAME}${LOCAL_TLD:+.$LOCAL_TLD}
# fetch LAN IP address (read management interface eth0)
LANIP=$(sed -n '/^\[eth0\]$/,/^TYPE/p' /var/local/emhttp/network.ini |grep -Pom1 '^IPADDR:0="\K[^"]+');
LANIP6=$(sed -n '/^\[eth0\]$/,/^TYPE/p' /var/local/emhttp/network.ini |grep -Pom1 '^IPADDR6:0="\K[^"]+');
# 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
# 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')
# 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 == *\.unraid\.net ]]; then
# legacy LE certificate (only supports IPv4)
LANFQDN=$CERTNAME
if [[ ! -z $WANACCESS && ! -z $WANIP ]]; then
WANFQDN="www.$CERTNAME"
fi
elif [[ $CERTNAME == *\.myunraid\.net ]]; then
# wildcard LE certificate
[[ -n $LANIP ]] && LANFQDN=${CERTNAME//'*'/${LANIP//'.'/'-'}}
[[ -n $LANIP6 ]] && LANFQDN6=${CERTNAME//'*'/${LANIP6//':'/'-'}}
# check if remote access enabled
if [[ ! -z $WANACCESS ]]; then
[[ -n $WANIP ]] && WANFQDN=${CERTNAME//'*'/${WANIP//'.'/'-'}}
[[ -n $WANIP6 ]] && WANFQDN6=${CERTNAME//'*'/${WANIP6//':'/'-'}}
fi
else
# custom certificate
LANFQDN=${CERTNAME/\*/$LANNAME} # support wildcard custom certs
fi
# determine if OCSP stapling should be enabled for this cert
[[ -n $(openssl x509 -noout -ocsp_uri -in "$CERTPATH") ]] && CERTSTAPLE=on || CERTSTAPLE=off
fi
define_servers
define_locations
# define the default URL used to access the server
if [[ $USE_SSL = auto ]]; then
[[ -n $LANIP ]] && DEFAULTURL="https://$LANFQDN$PORTSSL_URL" || 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 "${STATE}")
# always defined:
echo "NGINX_LANIP=\"$LANIP\"" > $STATE
echo "NGINX_LANIP6=\"$LANIP6\"" >> $STATE
echo "NGINX_LANNAME=\"$LANNAME\"" >> $STATE
echo "NGINX_LANMDNS=\"$LANMDNS\"" >> $STATE
echo "NGINX_CERTPATH=\"$CERTPATH\"" >> $STATE
echo "NGINX_USESSL=\"$USE_SSL\"" >> $STATE
echo "NGINX_PORT=\"$PORT\"" >> $STATE
echo "NGINX_PORTSSL=\"$PORTSSL\"" >> $STATE
echo "NGINX_DEFAULTURL=\"$DEFAULTURL\"" >> $STATE
# defined if certificate_bundle.pem present:
echo "NGINX_CERTNAME=\"$CERTNAME\"" >> $STATE
echo "NGINX_LANFQDN=\"$LANFQDN\"" >> $STATE
echo "NGINX_LANFQDN6=\"$LANFQDN6\"" >> $STATE
# defined if remote access enabled:
echo "NGINX_WANACCESS=\"$WANACCESS\"" >> $STATE
echo "NGINX_WANIP=\"$WANIP\"" >> $STATE
echo "NGINX_WANIP6=\"$WANIP6\"" >> $STATE
echo "NGINX_WANFQDN=\"$WANFQDN\"" >> $STATE
echo "NGINX_WANFQDN6=\"$WANFQDN6\"" >> $STATE
# atomically update file
/usr/bin/mv $STATE ${STATE%.*}
}
nginx_status() {
[[ -s $PID && -n "$(cat $PID)" && -d "/proc/$(cat $PID)" ]] && return 0 || return 1;
}
wait_nginx_shutdown() {
for i in {1..10} ; do
if ! nginx_status ; then
break
fi
sleep 1
done
}
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() {
if ! nginx_status ; then
echo "Nginx is not running"
exit 1
fi
}
nginx_start() {
if nginx_status ; then
echo "Nginx is already running"
exit 1
fi
# Sanity checks.
if [[ ! -r $CONF ]]; then # no config file, exit:
echo "$CONF does not appear to exist. Abort."
exit 1
fi
check_ssl
# nginx does not unlink stale unix sockets before rebinding
# see: https://trac.nginx.org/nginx/ticket/753
rm -f /var/run/nginx.socket
echo "Starting Nginx server daemon..."
if [[ -x $BIN ]]; then
$BIN -c $CONF
fi
# side-load unraid-api
unraid_api_control start
}
nginx_test_conf() {
echo "Checking configuration for correct syntax and"
echo "then trying to open files referenced in configuration..."
$BIN -t -c $CONF
}
nginx_term() {
if nginx_running ; then
echo "Shutdown Nginx quickly..."
unraid_api_control stop
kill -TERM $(cat $PID)
wait_nginx_shutdown
fi
}
nginx_stop() {
if nginx_running ; then
echo "Shutdown Nginx gracefully..."
unraid_api_control stop
kill -QUIT $(cat $PID)
wait_nginx_shutdown
fi
}
nginx_reload() {
# only stop working system if configuration is valid
if nginx_running ; then
check_ssl
if nginx_test_conf ; then
echo "Reloading Nginx configuration..."
kill -HUP $(cat $PID)
sleep 3
else
echo "Invalid configuration, Nginx not reloaded"
return 1
fi
fi
}
nginx_restart() {
# only stop working system if configuration is valid
if nginx_running ; then
if nginx_test_conf ; then
nginx_stop
nginx_start
else
echo "Invalid configuration, Nginx not restarted"
return 1
fi
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
echo "Rotating Nginx logs..."
kill -USR1 $(cat $PID)
fi
}
case "$1" in
check)
nginx_test_conf
;;
start)
nginx_start
;;
term)
nginx_term
;;
stop)
nginx_stop
;;
reload)
nginx_reload
;;
restart)
nginx_restart
;;
port)
echo $PORT
;;
status)
if nginx_running ; then
echo "Nginx is running"
fi
;;
upgrade)
nginx_upgrade
;;
rotate)
nginx_rotate
;;
*)
echo "usage: `basename $0` {check|start|term|stop|reload|restart|status|port|upgrade|rotate}"
exit 1
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment