__netbox*: added systemd socket support

The Gunicorn type now supports systemd sockets only. With uWSGI, you can
choose between it and the native sockets based on the parameters chosen.
This is done because it could not be implemented to have multiple
protocols with the systemd sockets (so you may choose).

The systemd socket unit file is generally available, so both types use
the same script to generate the socket unit file.
This commit is contained in:
matze 2020-10-11 16:39:19 +02:00
parent 3b780c4794
commit 13e97d171b
15 changed files with 218 additions and 39 deletions

View file

@ -0,0 +1,33 @@
#!/bin/sh -e
# __netbox/files/netbox.socket.sh
# This is shared between all WSGI-server types.
# Arguments:
# 1: File which list all sockets to listen on (sepearated by \n)
if [ $# -ne 1 ]; then
printf "netbox.socket.sh: argument \$1 missing or too much given!\n" >&2
exit 1
fi
cat << UNIT
[Unit]
Description=Socket for NetBox via $TYPE
[Socket]
UNIT
# read all sockets to listen to
while read line; do
printf "ListenStream=%s\n" "$line"
done < "$1"
cat << UNIT
SocketUser=netbox
SocketGroup=www-data
[Install]
WantedBy=sockets.target
UNIT

View file

@ -13,7 +13,7 @@ cores="$(cat "$__explorer/cpu_cores")"
cat << EOF cat << EOF
# The IP address (typically localhost) and port that the Netbox WSGI process should listen on # The IP address (typically localhost) and port that the Netbox WSGI process should listen on
bind = [$HOST ] #bind = done via systemd socket 'gunicorn-netbox.socket'
# Number of gunicorn workers to spawn. This should typically be 2n+1, where # Number of gunicorn workers to spawn. This should typically be 2n+1, where
# n is the number of CPU cores present. # n is the number of CPU cores present.

View file

@ -3,13 +3,14 @@ Description=NetBox Gunicorn WSGI Service
Documentation=https://netbox.readthedocs.io/en/stable/ Documentation=https://netbox.readthedocs.io/en/stable/
PartOf=netbox.service PartOf=netbox.service
Requires=netbox-rq.service Requires=netbox-rq.service
Requires=gunicorn-netbox.socket
Wants=network.target Wants=network.target
After=netbox.service After=netbox.service
After=network.target After=network.target
After=redis-server.service postgresql.service After=redis-server.service postgresql.service
[Service] [Service]
Type=simple Type=notify
User=netbox User=netbox
Group=netbox Group=netbox

View file

@ -0,0 +1 @@
../../__netbox/files/netbox.socket.sh

View file

@ -14,6 +14,9 @@ sockets. Static content must be served independent of Gunicorn. The Gunicorn
daemon is available as the `gunicorn-netbox` systemd service, but also daemon is available as the `gunicorn-netbox` systemd service, but also
available via the `netbox` wrapper service. available via the `netbox` wrapper service.
It will use systemd socket activation to listen to the given sockets. This
should allow to bind to privileaged ports (all below 1024) and hot reloads.
REQUIRED PARAMETERS REQUIRED PARAMETERS
------------------- -------------------
@ -38,8 +41,9 @@ state
bind-to bind-to
The hosts the gunicorn socket should be bind to. Formats are `IP`, The hosts the gunicorn socket should be bind to. Formats are `IP`,
`IP:PORT`, `unix:PATH` and `fd://FD`. Parameter can be set a multiple `IP:PORT`, `PATH` or anything other that systemd socket units will
times. Defaults to ``127.0.0.1:8001``. understand as stream. Parameter can be set multiple times. Defaults
to ``127.0.0.1:8001``.
BOOLEAN PARAMETERS BOOLEAN PARAMETERS

View file

@ -25,17 +25,10 @@ case "$param_state" in
esac esac
if [ "$state" = "present" ]; then
HOST=""
while read -r host; do
# shellcheck disable=SC2089
HOST="$HOST '$host',"
done < "$__object/parameter/bind-to"
# shellcheck disable=SC2090
export HOST
# process template
mkdir "$__object/files" mkdir "$__object/files"
if [ "$state" = "present" ]; then
# process template
"$__type/files/gunicorn.py.sh" > "$__object/files/gunicorn.py" "$__type/files/gunicorn.py.sh" > "$__object/files/gunicorn.py"
# gunicorn config file # gunicorn config file
@ -49,7 +42,16 @@ else
fi fi
# install service file TYPE="Gunicorn"
export TYPE
"$__type/files/netbox.socket.sh" "$__object/parameter/bind-to" \
> "$__object/files/netbox.socket"
# install systemd files
__systemd_unit gunicorn-netbox.socket \
--state "$state" --enablement-state "$unit_state" \
--source "$__object/files/netbox.socket" --restart
__systemd_unit gunicorn-netbox.service \ __systemd_unit gunicorn-netbox.service \
--state "$state" --enablement-state "$unit_state" \ --state "$state" --enablement-state "$unit_state" \
--source "$__type/files/netbox.service" --restart --source "$__type/files/netbox.service" --restart

View file

@ -0,0 +1,12 @@
#!/bin/sh -e
# explorer/bind-capablility
# Checks if the uWSGI binary have the capability to bind to privileaged ports
# as a non-root user. It's required if no systemd sockets are used (cause of
# the use of multiple protocols etc.)
binary="/opt/netbox/venv/bin/uwsgi"
# -v verifies if capability is set
if setcap -q -v CAP_NET_BIND_SERVICE+ep "$binary"; then
echo set
fi

View file

@ -1,15 +1,26 @@
#!/bin/sh -e
cat << EOF
[Unit] [Unit]
Description=Netbox uWSGI WSGI Service Description=Netbox uWSGI WSGI Service
Documentation=https://netbox.readthedocs.io/en/stable/ Documentation=https://netbox.readthedocs.io/en/stable/
PartOf=netbox.service PartOf=netbox.service
Requires=netbox-rq.service Requires=netbox-rq.service
EOF
# Add dependency to own socket
if [ "$(cat "$__object/files/systemd_socket")" = "yes" ]; then
echo "Requires=uwsgi-netbox.socket"
fi
cat << EOF
Wants=network.target Wants=network.target
After=netbox.service After=netbox.service
After=network.target After=network.target
After=redis-server.service postgresql.service After=redis-server.service postgresql.service
[Service] [Service]
Type=simple Type=notify
User=netbox User=netbox
Group=netbox Group=netbox
@ -17,8 +28,8 @@ WorkingDirectory=/opt/netbox
ExecStart=/opt/netbox/venv/bin/uwsgi --master --chdir /opt/netbox/netbox --module netbox.wsgi uwsgi.ini ExecStart=/opt/netbox/venv/bin/uwsgi --master --chdir /opt/netbox/netbox --module netbox.wsgi uwsgi.ini
# signals: https://uwsgi-docs.readthedocs.io/en/latest/Management.html#signals-for-controlling-uwsgi # signals: https://uwsgi-docs.readthedocs.io/en/latest/Management.html#signals-for-controlling-uwsgi
ExecReload=kill -HUP $MAINPID ExecReload=kill -HUP \$MAINPID
ExecStop=kill -INT $MAINPID ExecStop=kill -INT \$MAINPID
KillSignal=SIGQUIT KillSignal=SIGQUIT
Restart=on-failure Restart=on-failure
@ -26,3 +37,4 @@ RestartSec=30
[Install] [Install]
WantedBy=netbox.service WantedBy=netbox.service
EOF

View file

@ -0,0 +1 @@
../../__netbox/files/netbox.socket.sh

View file

@ -28,20 +28,16 @@ cat << EOF
; socket(s) to bind ; socket(s) to bind
EOF EOF
if [ "$SYSTEMD_SOCKET" != "yes" ]; then
# special protocol to bind # special protocol to bind
while read -r param; do find "$__object/parameter/" -maxdepth 1 -name "*-bind" -print \
if [ -z "$param" ]; then continue; fi # ignore empty lines from the here-doc | while read -r param; do
multi_options "$(basename "$param" | awk -F'-' '{print $1}')-socket" "$param" multi_options "$(basename "$param" | awk -F'-' '{print $1}')-socket" "$param"
socket_changes="yes" done
else
done << INPUT # here-doc cause of SC2031 # else, systemd will offer socket
$( find "$__object/parameter/" -maxdepth 1 -name "*-bind" -print ) echo "; sockets managed via 'uwsgi-netbox.socket'"
INPUT printf "protocol = %s\n" "$PROTOCOL"
# else, default bind to
if [ -z "$socket_changes" ]; then
multi_options "socket" "$__object/parameter/bind-to"
fi fi

View file

@ -3,18 +3,67 @@
# control state # control state
state="$(cat "$__object/parameter/state")" state="$(cat "$__object/parameter/state")"
# Set capabilities to aquire privileaged ports as netbox user. Two modes are
# available to efficiently set capabilites. Assumes libcap-bin is installed as
# default on debian systems.
#
# Arguments:
# 1: mode to detect if capabilites are required to set ('set' or 'correct')
set_bind_cap() {
cap_mode="" # reset variable from the execution before
# check if capabilites are required after given mode
case "$1" in
# assumes capabilites are not set (cause of new binaries)
set)
if [ "$SYSTEMD_SOCKET" != "yes" ]; then
cap_mode="+ep"
fi
;;
# check if capabilities have changed
correct)
if [ -s "$__object/explorer/bind-capability" ]; then
# capabilites are set
if [ "$SYSTEMD_SOCKET" = "yes" ]; then
cap_mode="-ep" # unset
fi
else
# capabilities are unset
if [ "$SYSTEMD_SOCKET" != "yes" ]; then
cap_mode="+ep" # set
fi
fi
;;
# faulty mode
*)
echo "called set_bind_cap incorrect (\$1 missing)" >&2
;;
esac
# set capabilities if any
if [ "$cap_mode" ]; then
printf "setcap -q CAP_NET_BIND_SERVICE%s /opt/netbox/venv/bin/uwsgi\n" "$cap_mode"
fi
}
SYSTEMD_SOCKET="$(cat "$__object/files/systemd_socket")"
case "$state" in case "$state" in
# install uwsgi # install uwsgi
enabled|disabled) enabled|disabled)
# not installed # not installed
if ! [ -s "$__object/explorer/installed" ]; then if ! [ -s "$__object/explorer/installed" ]; then
echo "/opt/netbox/venv/bin/pip3 install uwsgi" echo "/opt/netbox/venv/bin/pip3 install -q uwsgi"
set_bind_cap set
do_restart=yes do_restart=yes
printf "installed\n" >> "$__messages_out" printf "installed\n" >> "$__messages_out"
# updates available # updates available
elif [ -s "$__object/explorer/upgradeable" ]; then elif [ -s "$__object/explorer/upgradeable" ]; then
echo "/opt/netbox/venv/bin/pip3 install --upgrade uwsgi" echo "/opt/netbox/venv/bin/pip3 install -q --upgrade uwsgi"
set_bind_cap set
do_restart=yes do_restart=yes
printf "upgraded\n" >> "$__messages_out" printf "upgraded\n" >> "$__messages_out"
fi fi
@ -25,6 +74,11 @@ case "$state" in
printf "configured\n" >> "$__messages_out" printf "configured\n" >> "$__messages_out"
fi fi
# if no capabilities were set yet, check if any are required
if [ -z "$cap_mode" ]; then
set_bind_cap correct
fi
# restart uwsgi # restart uwsgi
if [ "$do_restart" ] && [ "$state" != "disabled" ]; then if [ "$do_restart" ] && [ "$state" != "disabled" ]; then
@ -40,7 +94,7 @@ EOF
# check if installed # check if installed
if [ -s "$__object/explorer/installed" ]; then if [ -s "$__object/explorer/installed" ]; then
# service already disabled # service already disabled
echo "/opt/netbox/venv/bin/pip3 uninstall -y uwsgi" echo "/opt/netbox/venv/bin/pip3 uninstall -qy uwsgi"
printf "uninstalled\n" >> "$__messages_out" printf "uninstalled\n" >> "$__messages_out"
fi fi
;; ;;

View file

@ -15,6 +15,19 @@ protocols like uwsgi, fastcgi or HTTP to comunicate with the proxy server. This
application is available via the `uwsgi-netbox` systemd service. It is application is available via the `uwsgi-netbox` systemd service. It is
controllable via the `netbox` wrapper service, too. controllable via the `netbox` wrapper service, too.
**As uWSGI will be started as netbox user, it does not have privileges to
bind to a privileaged port (all ports below 1024).** Because uWSGI will
drop privileages anyway before binding to a port, solutions are to use
the systemd sockets to activate the ports as root or set linux kernel
capabilites to bind to such a privileaged port.
As systemd sockets (or uwsgi itself) do not allow to distinguish multiple
sockets if different protocols are used for different sockets, this type does
not use systemd sockets if it is requested from the user. Using the
``--bind-to`` and ``--protocol`` parameters, it uses the systemd socket
activation. Else, it set the different sockets and protocols natively to uwsgi
and add kernel capabilities to be able to listen to privileaged ports.
REQUIRED PARAMETERS REQUIRED PARAMETERS
------------------- -------------------
@ -39,8 +52,18 @@ state
bind-to bind-to
The socket uwsgi should bind to. Must be UNIX/TCP for the uwsgi protocol. The socket uwsgi should bind to. Must be UNIX/TCP (or anything that
Defaults to ``127.0.0.1:3031``. Can be set multiple times. systemd sockets accept as stream). Defaults to ``127.0.0.1:3031``. Can be
set multiple times. The used protocol is defined by ``--protocol``.
**By setting up the socket via this parameter, it uses systemd sockets to
handle these.** This parameter will be ignored if a more detailed paramter
is given (``--$proto-bind``).
protocol
The protocol which should be used for the socket given by the ``--bind-to``
parameter. Possible values are ``uwsgi``, ``http``, ``fastcgi`` and
``scgi``. If nothing given, it defaults to ``uwsgi``.
uwsgi-bind uwsgi-bind
http-bind http-bind
@ -50,6 +73,12 @@ scgi-bind
``--bind-to``. If such parameter given, ``--bind-to`` will be ignored. Must ``--bind-to``. If such parameter given, ``--bind-to`` will be ignored. Must
be a UNIX/TCP socket. Can be set multiple times. be a UNIX/TCP socket. Can be set multiple times.
**By using such parameters instead of ``--bind-to``, no systemd sockets
will be used because it can not handle sockets for multiple protocols.**
Instead, the native socket binding will be used. It will add kernel
capabilites to bind to privileaged ports, too. This allow binds to ports
like 80 as netbox user.
BOOLEAN PARAMETERS BOOLEAN PARAMETERS
------------------ ------------------

View file

@ -25,15 +25,30 @@ case "$param_state" in
esac esac
mkdir "$__object/files"
# check if systemd sockets will be used
if [ -f "$__object/parameter/bind-to" ]; then
SYSTEMD_SOCKET="yes"
fi
if find "$__object/parameter/" -maxdepth 1 -name "*-bind" -print -quit | grep -q .; then
SYSTEMD_SOCKET="no"
fi
echo "$SYSTEMD_SOCKET" > "$__object/files/systemd_socket"
if [ "$state" = "present" ]; then if [ "$state" = "present" ]; then
# *bind* parameters are directly processed in the gen script # already checked outside this if-clause
export SYSTEMD_SOCKET
PROTOCOL="$(cat "$__object/parameter/protocol")"
export PROTOCOL
if [ -f "$__object/parameter/serve-static" ]; then if [ -f "$__object/parameter/serve-static" ]; then
STATIC_MAP="yes" STATIC_MAP="yes"
export STATIC_MAP export STATIC_MAP
fi fi
# process template # process template
mkdir "$__object/files"
"$__type/files/uwsgi.ini.sh" > "$__object/files/uwsgi.ini" "$__type/files/uwsgi.ini.sh" > "$__object/files/uwsgi.ini"
# uwsgi config file # uwsgi config file
@ -48,7 +63,24 @@ else
fi fi
# handle the systemd socket
if [ "$SYSTEMD_SOCKET" = "yes" ]; then
TYPE="uWSGI"
export TYPE
# generate and set the socket unit
"$__type/files/netbox.socket.sh" "$__object/parameter/bind-to" \
> "$__object/files/netbox.socket"
__systemd_unit uwsgi-netbox.socket \
--state "$state" --enablement-state "$unit_state" \
--source "$__object/files/netbox.socket" --restart
else
# remove the systemd socket unit
__systemd_unit uwsgi-netbox.socket --state absent
fi
# install service file # install service file
"$__type/files/netbox.service.sh" > "$__object/files/netbox.service"
__systemd_unit uwsgi-netbox.service \ __systemd_unit uwsgi-netbox.service \
--state "$state" --enablement-state "$unit_state" \ --state "$state" --enablement-state "$unit_state" \
--source "$__type/files/netbox.service" --restart --source "$__object/files/netbox.service" --restart

View file

@ -0,0 +1 @@
uwsgi

View file

@ -1 +1,2 @@
state state
protocol