mirror of
https://github.com/unraid/webgui.git
synced 2026-01-05 17:20:04 -06:00
918 lines
26 KiB
Nginx Configuration File
Executable File
918 lines
26 KiB
Nginx Configuration File
Executable File
#!/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"
|
|
CONNECT_CONFIG="/boot/config/plugins/dynamix.my.servers/configs/connect.json"
|
|
API_UTILS="/usr/local/share/dynamix.unraid.net/scripts/api_utils.sh"
|
|
DEFAULTS="/etc/default/nginx"
|
|
SYSTEM="/sys/class/net"
|
|
SYSLOG="/var/log/syslog"
|
|
# Disable IPv6 myunraid.net certs for now
|
|
IPV6MYUNRAIDNET=0
|
|
# 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//[.:]/-}}
|
|
}
|
|
|
|
# check if remote access should be enabled
|
|
check_remote_access(){
|
|
# Check if connect plugin is enabled using api_utils.sh
|
|
if [[ -f $API_UTILS ]] && $API_UTILS is_api_plugin_enabled "unraid-api-plugin-connect"; then
|
|
# Plugin is enabled, check connect.json configuration
|
|
if [[ -f $CONNECT_CONFIG ]] && command -v jq >/dev/null 2>&1; then
|
|
local wanaccess=$(jq -r '.wanaccess' "$CONNECT_CONFIG" 2>/dev/null)
|
|
local username=$(jq -r '.username' "$CONNECT_CONFIG" 2>/dev/null)
|
|
# Enable remote access if wanaccess is true and username is not empty
|
|
if [[ $wanaccess == "true" && -n $username && $username != "null" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
return 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 ]] && check_remote_access; then
|
|
WANACCESS=yes
|
|
WANIP=$(curl https://wanip4.unraid.net/ 2>/dev/null)
|
|
[[ $IPV6MYUNRAIDNET == 1 ]] && 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)
|
|
[[ $IPV6MYUNRAIDNET == 1 && -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)
|
|
[[ $IPV6MYUNRAIDNET == 1 && -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
|
|
if [[ $(ipv $ADDR) == 4 ]]; then
|
|
NET_FQDN[$NET]=$(fqdn $ADDR)
|
|
SERVER_NAMES+=($(fqdn $ADDR))
|
|
elif [[ $IPV6MYUNRAIDNET == 1 ]]; then
|
|
NET_FQDN6[$NET]=$(fqdn $ADDR)
|
|
SERVER_NAMES+=($(fqdn $ADDR))
|
|
fi
|
|
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
|
|
if [[ $IPV6MYUNRAIDNET == 1 ]]; then
|
|
for NET in "${!NET_FQDN6[@]}"; do
|
|
echo "NGINX_${NET^^}FQDN6=\"${NET_FQDN6[$NET]}\"" >>$INI
|
|
done
|
|
fi
|
|
# 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 --ns $$ -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 --ns $$ -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
|