mirror of
https://github.com/unraid/api.git
synced 2025-12-31 05:29:48 -06:00
fix: backport unraid/webgui#2269 rc.nginx update (#1436)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced remote access detection for nginx using updated configuration and plugin integration. - Introduced a comprehensive Nginx management script tailored for Unraid OS with SSL handling, server lifecycle controls, and dynamic configuration. - **Bug Fixes** - Improved script robustness by fixing shell loop syntax for proper handling of array keys. - **Chores** - Added compatibility patches for Unraid versions before 7.2.0. - Updated test suites to include new nginx script modifications. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1 +1 @@
|
||||
1750189490614
|
||||
1750864232307
|
||||
@@ -1 +1 @@
|
||||
1750189490326
|
||||
1750864231987
|
||||
@@ -1 +1 @@
|
||||
1750189490462
|
||||
1750864232160
|
||||
@@ -1 +1 @@
|
||||
1750189490730
|
||||
1750864232467
|
||||
@@ -0,0 +1,891 @@
|
||||
#!/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
|
||||
@@ -0,0 +1 @@
|
||||
1750864333264
|
||||
@@ -10,6 +10,7 @@ import AuthRequestModification from '@app/unraid-api/unraid-file-modifier/modifi
|
||||
import DefaultPageLayoutModification from '@app/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.js';
|
||||
import LogRotateModification from '@app/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.js';
|
||||
import NotificationsPageModification from '@app/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.js';
|
||||
import RcNginxModification from '@app/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.js';
|
||||
import SSOFileModification from '@app/unraid-api/unraid-file-modifier/modifications/sso.modification.js';
|
||||
|
||||
interface ModificationTestCase {
|
||||
@@ -47,6 +48,11 @@ const patchTestCases: ModificationTestCase[] = [
|
||||
'https://raw.githubusercontent.com/unraid/webgui/refs/heads/7.1/emhttp/auth-request.php',
|
||||
fileName: 'auth-request.php',
|
||||
},
|
||||
{
|
||||
ModificationClass: RcNginxModification,
|
||||
fileUrl: 'https://raw.githubusercontent.com/unraid/webgui/refs/heads/7.1/etc/rc.d/rc.nginx',
|
||||
fileName: 'rc.nginx',
|
||||
},
|
||||
];
|
||||
|
||||
/** Modifications that simply add a new file & remove it on rollback. */
|
||||
@@ -103,9 +109,11 @@ async function testModification(testCase: ModificationTestCase) {
|
||||
|
||||
// Apply patch and verify modified file
|
||||
await patcher.apply();
|
||||
await expect(await readFile(filePath, 'utf-8')).toMatchFileSnapshot(
|
||||
`snapshots/${fileName}.modified.snapshot.php`
|
||||
);
|
||||
let snapshotFile = `snapshots/${fileName}.modified.snapshot`;
|
||||
if (fileName.endsWith('.php') || fileName.endsWith('.page')) {
|
||||
snapshotFile += '.php';
|
||||
}
|
||||
await expect(await readFile(filePath, 'utf-8')).toMatchFileSnapshot(snapshotFile);
|
||||
|
||||
// Rollback and verify original state
|
||||
await patcher.rollback();
|
||||
|
||||
@@ -0,0 +1,909 @@
|
||||
#!/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"
|
||||
|
||||
# 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)
|
||||
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
|
||||
@@ -0,0 +1,76 @@
|
||||
Index: /etc/rc.d/rc.nginx
|
||||
===================================================================
|
||||
--- /etc/rc.d/rc.nginx original
|
||||
+++ /etc/rc.d/rc.nginx modified
|
||||
@@ -26,11 +26,12 @@
|
||||
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"
|
||||
+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"
|
||||
|
||||
# Load defaults
|
||||
@@ -73,10 +74,27 @@
|
||||
|
||||
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
|
||||
@@ -566,11 +584,11 @@
|
||||
# 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
|
||||
+ if [[ -L /usr/local/sbin/unraid-api ]] && check_remote_access; 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
|
||||
@@ -660,14 +678,14 @@
|
||||
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
|
||||
+ for NET in "${!NET_FQDN[@]}"; do
|
||||
echo "NGINX_${NET^^}FQDN=\"${NET_FQDN[$NET]}\"" >>$INI
|
||||
done
|
||||
- for NET in ${!NET_FQDN6[@]}; do
|
||||
+ for NET in "${!NET_FQDN6[@]}"; do
|
||||
echo "NGINX_${NET^^}FQDN6=\"${NET_FQDN6[$NET]}\"" >>$INI
|
||||
done
|
||||
# atomically update file
|
||||
mv $INI ${INI%.*}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
import {
|
||||
FileModification,
|
||||
ShouldApplyWithReason,
|
||||
} from '@app/unraid-api/unraid-file-modifier/file-modification.js';
|
||||
|
||||
/**
|
||||
* Patch rc.nginx on < Unraid 7.2.0 to read the updated connect & api config files
|
||||
*
|
||||
* Backport of https://github.com/unraid/webgui/pull/2269
|
||||
*/
|
||||
export default class RcNginxModification extends FileModification {
|
||||
public filePath: string = '/etc/rc.d/rc.nginx' as const;
|
||||
id: string = 'rc-nginx';
|
||||
|
||||
/**
|
||||
* Generate a patch for the rc.nginx file
|
||||
*
|
||||
* Should result in the same patch as
|
||||
* https://patch-diff.githubusercontent.com/raw/unraid/webgui/pull/2269.patch
|
||||
*
|
||||
* @param overridePath - The path to override the default file path
|
||||
* @returns The patch for the rc.nginx file
|
||||
*/
|
||||
protected async generatePatch(overridePath?: string): Promise<string> {
|
||||
if (!existsSync(this.filePath)) {
|
||||
throw new Error(`File ${this.filePath} not found.`);
|
||||
}
|
||||
const fileContent = await readFile(this.filePath, 'utf8');
|
||||
if (!fileContent.includes('MYSERVERS=')) {
|
||||
throw new Error(`MYSERVERS not found in the file; incorrect target?`);
|
||||
}
|
||||
|
||||
let newContent = fileContent.replace(
|
||||
'MYSERVERS="/boot/config/plugins/dynamix.my.servers/myservers.cfg"',
|
||||
`CONNECT_CONFIG="/boot/config/plugins/dynamix.my.servers/configs/connect.json"
|
||||
API_UTILS="/usr/local/share/dynamix.unraid.net/scripts/api_utils.sh"`
|
||||
);
|
||||
|
||||
if (!newContent.includes('check_remote_access()')) {
|
||||
newContent = newContent.replace(
|
||||
'# create listening ports',
|
||||
`# 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`
|
||||
);
|
||||
}
|
||||
|
||||
newContent = newContent.replace(
|
||||
`if [[ -L /usr/local/sbin/unraid-api ]] && grep -qs 'wanaccess="yes"' $MYSERVERS && ! grep -qs 'username=""' $MYSERVERS; then`,
|
||||
`if [[ -L /usr/local/sbin/unraid-api ]] && check_remote_access; then`
|
||||
);
|
||||
|
||||
newContent = newContent.replace(
|
||||
'for NET in ${!NET_FQDN6[@]}; do',
|
||||
'for NET in "${!NET_FQDN6[@]}"; do'
|
||||
);
|
||||
newContent = newContent.replace(
|
||||
'for NET in ${!NET_FQDN[@]}; do',
|
||||
'for NET in "${!NET_FQDN[@]}"; do'
|
||||
);
|
||||
|
||||
return this.createPatchWithDiff(overridePath ?? this.filePath, fileContent, newContent);
|
||||
}
|
||||
|
||||
async shouldApply(): Promise<ShouldApplyWithReason> {
|
||||
if (await this.isUnraidVersionGreaterThanOrEqualTo('7.2.0')) {
|
||||
return {
|
||||
shouldApply: false,
|
||||
reason: 'Patch unnecessary for Unraid 7.2 or later because the Unraid API is integrated.',
|
||||
};
|
||||
}
|
||||
const { shouldApply, reason } = await super.shouldApply();
|
||||
return {
|
||||
shouldApply: shouldApply,
|
||||
reason: shouldApply ? 'Unraid version is less than 7.2.0, applying the patch.' : reason,
|
||||
};
|
||||
}
|
||||
}
|
||||
2
web/helpers/globals.d.ts
vendored
2
web/helpers/globals.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
|
||||
var csrf_token: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user