nut: fix driver, server, and monitor reload/stop
authorDaniel F. Dickinson <redacted>
Sun, 11 Jan 2026 10:41:48 +0000 (05:41 -0500)
committerMichael Heimpold <redacted>
Tue, 3 Mar 2026 18:53:14 +0000 (19:53 +0100)
Updated configuration was not being applied after config change. This
was due to the means used to do the daemon reloads.

Closes #28298 "Drivers not restarted on config change"

Enable creating PID files for the server, driver, and monitor daemon
processes. This allows to use NUT's built-in facilities for signalling
the daemon's.

For server, when reloading:
1. Check if upsd is running
   1. If not, start it.
   2. If it is send reload signal to upsd
2. For each driver:
   1. Check if the driver is running
      1. If it is, send reload-or-exit signal to driver
      2. If driver is not running, start it
3. Attempt to start server (upsd and drivers) if service was stopped.

For server, when stopping:
1. Check if upsd is running
   1. If it is send stop signal to upsd
   2. Ensure it really is stopped
2. For each driver:
   1. Check if the driver is running
      1. If it is, send stop signal to driver
      2. If driver is still running, stop it.
3. If the server process is active (even with not upsd or drivers),
   stop it.

For monitor, send the reload signal on config change, with fallback to
stopping and starting the daemon.

Change the names of variables and functions to make it more clear what
is being acted on, configured, or otherwise touched.

Avoid confusing messages in syslog

* Avoid attempting to remove a procd server instance that does not exist
  as doing so results in confusing/scary messages in syslog, such as:

  Command failed: ubus call service delete
  { "name": "nut-server", "instance": "upsd" } (Not found)

In NUT some models of UPS use shutdown_delay rather than offdelay, and
yet others use usd for the same purpose. shutdown_delay and usd were
previously not available in the list of available driver options, so
add them.

Signed-off-by: Daniel F. Dickinson <redacted>
net/nut/Makefile
net/nut/files/nut-monitor.init
net/nut/files/nut-server.init

index 320b496de6db548c6b7bcec13bf1ce0c4b741c9c..da68d6809099ca44fb1867a004abb28e0a7449ba 100644 (file)
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=nut
 PKG_VERSION:=2.8.4
-PKG_RELEASE:=1
+PKG_RELEASE:=2
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
 PKG_SOURCE_URL:=https://www.networkupstools.org/source/2.8/
@@ -578,8 +578,8 @@ CONFIGURE_ARGS += \
        --with-statepath=/var/run/nut \
        --with-pidpath=/var/run \
        --with-drvpath=/lib/nut \
-       --with-user=root \
-       --with-group=root \
+       --with-user=nut \
+       --with-group=nut \
        $(if $(CONFIG_PACKAGE_nut-web-cgi),--with-gd-includes="`pkg-config --cflags gdlib`") \
        $(if $(CONFIG_PACKAGE_nut-web-cgi),--with-gd-libs="`pkg-config --libs gdlib`")
 
index b39c5e3a569fd43ff3005f4ade1fc5798d85ed11..e22b45a903e35cdae77837eb9a5f06f4ad56ec17 100755 (executable)
@@ -1,9 +1,12 @@
 #!/bin/sh /etc/rc.common
 
+# shellcheck shell=ash
+
 START=82
 STOP=28
 USE_PROCD=1
 UPSMON_C=/var/etc/nut/upsmon.conf
+PIDFILE=/var/run/upsmon.pid
 
 nut_upsmon_conf() {
        local cfg="$1"
@@ -204,24 +207,73 @@ start_service() {
        procd_set_param respawn 10 20 6
        procd_set_param stderr 1
        procd_set_param stdout 0
-       procd_set_param command /usr/sbin/upsmon -D
+       procd_set_param env NUT_QUIET_INIT_UPSNOTIFY=true
+       procd_set_param reload_signal HUP
+       procd_set_param command /usr/sbin/upsmon -FF
        procd_close_instance
 
        return 0
 }
 
+# pkill is not available, and pgrep does not accept --signal
+pgrepkill() {
+       local pids
+
+       [ $# -eq 2 ] || return 1
+
+       pids="$(pgrep "$1")"
+
+       for pid in $pids; do
+               kill -"$2" "$pid"
+       done
+}
+
 reload_service() {
-       if pgrep upsmon >/dev/null 2>/dev/null; then
-               local runas=nutmon
-               build_config
-               /usr/sbin/upsmon -c reload
+       local should_stop
+
+       # shellcheck disable=SC1091
+       . /lib/functions/procd.sh
+
+       build_config
+
+       should_stop=0
+
+       [ "$havemon" = 1 ] || should_stop=1
+       [ "$havems" = 1 ] || should_stop=1
+       interface_triggers "check_interface_up" || should_stop=1
+
+       if [ "$should_stop" = "0" ]; then
+               if procd_running nut-monitor upsmon; then
+                       if [ -s "$PIDFILE" ]; then
+                               upsmon -c reload 2>&1 | logger -t nut-monitor
+                               return 0
+                       elif pgrep upsmon >/dev/null 2>/dev/null; then
+                               procd_send_signal nut-monitor upsmon HUP 2>&1 | logger -t nut-monitor
+                               return 0
+                       fi
+               else
+                       /etc/init.d/nut-monitor start
+               fi
        else
-               restart
+               if procd_running nut-monitor upsmon; then
+                       if [ -s "$PIDFILE" ]; then
+                               upsmon -c stop | logger -t nut-monitor
+                       else
+                               pgrepkill upsmon TERM >/dev/null 2>/dev/null
+                       fi
+                       procd_kill nut-monitor upsmon 2>/dev/null | logger -t nut-monitor
+               fi
        fi
 }
 
 stop_service() {
-       upsmon -c stop
+       if [ -s "$PIDFILE" ]; then
+               upsmon -c stop | logger -t nut-monitor
+               procd_kill nut-monitor 2>/dev/null | logger -t nut-monitor
+       else
+               pgrepkill upsmon TERM >/dev/null 2>/dev/null
+               procd_kill nut-monitor 2>/dev/null | logger -t nut-monitor
+       fi
 }
 
 service_triggers() {
index 8e0cc6b4f1f98542036e85d34f0cedeb0212563a..9e214af00c405562d0722a43a546063837a758e6 100755 (executable)
@@ -17,32 +17,32 @@ UPS_C=/var/etc/nut/ups.conf
 
 USE_PROCD=1
 
-get_write_driver_config() {
-       local cfg="$1"
+get_write_ups_config() {
+       local ups="$1"
        local var="$2"
        local def="$3"
        local flag="$4"
        local val
 
        [ -z "$flag" ] && {
-               config_get val "$cfg" "$var" "$def"
+               config_get val "$ups" "$var" "$def"
                [ -n "$val" ] && [ "$val" != "0" ] && echo "$var = $val" >>"$UPS_C"
        }
 
        [ -n "$flag" ] && {
-               config_get_bool val "$cfg" "$var" "$def"
+               config_get_bool val "$ups" "$var" "$def"
                [ "$val" = 1 ] && echo "$var" >>"$UPS_C"
        }
 }
 
-upsd_statepath() {
+srv_statepath() {
        local statepath
 
        config_get statepath upsd statepath /var/run/nut
        STATEPATH="$statepath"
 }
 
-upsd_runas() {
+srv_runas() {
        local runas
 
        [ -n "$RUNAS" ] && return 0
@@ -52,51 +52,51 @@ upsd_runas() {
 }
 
 listen_address() {
-       local cfg="$1"
+       local srv="$1"
 
-       config_get address "$cfg" address "::1"
-       config_get port "$cfg" port
+       config_get address "$srv" address "::1"
+       config_get port "$srv" port
        # shellcheck disable=SC2154
        echo "LISTEN $address $port" >>"$UPSD_C"
 }
 
-upsd_config() {
-       local cfg="$1"
+srv_config() {
+       local srv="$1"
        local maxage maxconn certfile runas statepath
 
        # Note runas support requires you make sure USB device file is readable by
        # the runas user
-       config_get runas "$cfg" runas nut
+       config_get runas "$srv" runas nut
        RUNAS="$runas"
 
-       config_get statepath "$cfg" statepath /var/run/nut
+       config_get statepath "$srv" statepath /var/run/nut
        STATEPATH="$statepath"
 
-       config_get maxage "$cfg" maxage
+       config_get maxage "$srv" maxage
        [ -n "$maxage" ] && echo "MAXAGE $maxage" >>"$UPSD_C"
 
        [ -n "$statepath" ] && echo "STATEPATH $statepath" >>"$UPSD_C"
 
-       config_get maxconn "$cfg" maxconn
+       config_get maxconn "$srv" maxconn
        [ -n "$maxconn" ] && echo "MAXCONN $maxconn" >>"$UPSD_C"
 
        #NOTE: certs only apply to SSL-enabled version
-       config_get certfile "$cfg" certfile
+       config_get certfile "$srv" certfile
        [ -n "$certfile" ] && echo "CERTFILE $certfile" >>"$UPSD_C"
 }
 
 nut_user_add() {
-       local cfg="$1"
+       local user="$1"
        local a
        local val
 
-       config_get val "$cfg" username "$1"
+       config_get val "$user" username "$1"
        echo "[$val]" >> "$USERS_C"
 
-       config_get val "$cfg" password
+       config_get val "$user" password
        echo "  password = $val" >> "$USERS_C"
 
-       config_get val "$cfg" actions
+       config_get val "$user" actions
        for a in $val; do
                echo "  actions = $a" >> "$USERS_C"
        done
@@ -108,9 +108,9 @@ nut_user_add() {
                echo "  instcmds = $val" >> "$USERS_C"
        }
 
-       config_list_foreach "$cfg" instcmd instcmd
+       config_list_foreach "$user" instcmd instcmd
 
-       config_get val "$cfg" upsmon
+       config_get val "$user" upsmon
        if [ -n "$val" ]; then
                echo "  upsmon $val" >> "$USERS_C"
        fi
@@ -128,66 +128,62 @@ build_server_config() {
 
        config_foreach nut_user_add user
        config_foreach listen_address listen_address
-       config_foreach upsd_config upsd
+       config_foreach srv_config upsd
        echo "MODE=netserver" >>/var/etc/nut/nut.conf
 
        chmod 0640 "$USERS_C"
        chmod 0640 "$UPSD_C"
        chmod 0644 /var/etc/nut/nut.conf
 
-       [ -d "${STATEPATH}" ] || {
-               mkdir -p "${STATEPATH}"
-               chmod 0750 "${STATEPATH}"
-       }
-
        if [ -n "$RUNAS" ]; then
-               chown "$RUNAS":"$(id -gn "$RUNAS")" "${STATEPATH}"
                chgrp "$(id -gn "$RUNAS")" "$USERS_C"
                chgrp "$(id -gn "$RUNAS")" "$UPSD_C"
        fi
-       haveserver=1
+       havesrvcfg=1
 }
 
-build_driver_config() {
-       local cfg="$1"
-
-       echo "[$cfg]" >>"$UPS_C"
-
-       get_write_driver_config "$cfg" bus
-       get_write_driver_config "$cfg" cable
-       get_write_driver_config "$cfg" community
-       get_write_driver_config "$cfg" desc
-       get_write_driver_config "$cfg" driver "usbhid-ups"
-       get_write_driver_config "$cfg" ignorelb 0 1
-       get_write_driver_config "$cfg" interruptonly 0 1
-       get_write_driver_config "$cfg" interruptsize
-       get_write_driver_config "$cfg" maxreport
-       get_write_driver_config "$cfg" maxstartdelay
-       get_write_driver_config "$cfg" mfr
-       get_write_driver_config "$cfg" model
-       get_write_driver_config "$cfg" nolock 0 1
-       get_write_driver_config "$cfg" notransferoids 0 1
-       get_write_driver_config "$cfg" offdelay
-       get_write_driver_config "$cfg" ondelay
-       get_write_driver_config "$cfg" pollfreq
-       get_write_driver_config "$cfg" port "auto"
-       get_write_driver_config "$cfg" product
-       get_write_driver_config "$cfg" productid
-       get_write_driver_config "$cfg" retrydelay
-       get_write_driver_config "$cfg" sdorder
-       get_write_driver_config "$cfg" sdtime
-       get_write_driver_config "$cfg" serial
-       get_write_driver_config "$cfg" snmp_version
-       get_write_driver_config "$cfg" snmp_retries
-       get_write_driver_config "$cfg" snmp_timeout
-       get_write_driver_config "$cfg" synchronous
-       get_write_driver_config "$cfg" vendor
-       get_write_driver_config "$cfg" vendorid
+build_ups_config() {
+       local ups="$1"
+
+       echo "[$ups]" >>"$UPS_C"
+
+       get_write_ups_config "$ups" bus
+       get_write_ups_config "$ups" cable
+       get_write_ups_config "$ups" community
+       get_write_ups_config "$ups" desc
+       get_write_ups_config "$ups" driver "usbhid-ups"
+       get_write_ups_config "$ups" ignorelb 0 1
+       get_write_ups_config "$ups" interruptonly 0 1
+       get_write_ups_config "$ups" interruptsize
+       get_write_ups_config "$ups" maxreport
+       get_write_ups_config "$ups" maxstartdelay
+       get_write_ups_config "$ups" mfr
+       get_write_ups_config "$ups" model
+       get_write_ups_config "$ups" nolock 0 1
+       get_write_ups_config "$ups" notransferoids 0 1
+       get_write_ups_config "$ups" offdelay
+       get_write_ups_config "$ups" ondelay
+       get_write_ups_config "$ups" pollfreq
+       get_write_ups_config "$ups" port "auto"
+       get_write_ups_config "$ups" product
+       get_write_ups_config "$ups" productid
+       get_write_ups_config "$ups" retrydelay
+       get_write_ups_config "$ups" sdorder
+       get_write_ups_config "$ups" sdtime
+       get_write_ups_config "$ups" serial
+       get_write_ups_config "$ups" shutdown_delay
+       get_write_ups_config "$ups" snmp_version
+       get_write_ups_config "$ups" snmp_retries
+       get_write_ups_config "$ups" snmp_timeout
+       get_write_ups_config "$ups" synchronous
+       get_write_ups_config "$ups" usd
+       get_write_ups_config "$ups" vendor
+       get_write_ups_config "$ups" vendorid
 
        # Params specific to NetXML driver
-       get_write_driver_config "$cfg" login
-       get_write_driver_config "$cfg" password
-       get_write_driver_config "$cfg" subscribe 0 1
+       get_write_ups_config "$ups" login
+       get_write_ups_config "$ups" password
+       get_write_ups_config "$ups" subscribe 0 1
 
        # shellcheck disable=SC2317
        defoverride() {
@@ -199,11 +195,11 @@ build_driver_config() {
                overtype="$(echo "$overvar" | tr '_' '.')"
 
                config_get overval "${defover}_${overvar}" value
-                       [ -n "$overval" ] && echo "${defover}.${overtype} = $overval" >>"$UPS_C"
+               [ -n "$overval" ] && echo "${defover}.${overtype} = $overval" >>"$UPS_C"
        }
 
-       config_list_foreach "$cfg" override defoverride override
-       config_list_foreach "$cfg" default defoverride default
+       config_list_foreach "$ups" override defoverride override
+       config_list_foreach "$ups" default defoverride default
 
        other() {
                # shellcheck disable=SC2317
@@ -223,23 +219,23 @@ build_driver_config() {
                fi
        }
 
-       config_list_foreach "$cfg" other other other
-       config_list_foreach "$cfg" otherflag other otherflag
+       config_list_foreach "$ups" other other other
+       config_list_foreach "$ups" otherflag other otherflag
        echo "" >>$UPS_C
-       havedriver=1
+       haveupscfg=1
 }
 
 build_global_driver_config() {
        local cfg="$1"
 
        # Global driver config
-       get_write_driver_config "$cfg" chroot
-       get_write_driver_config "$cfg" driverpath
-       get_write_driver_config "$cfg" maxstartdelay
-       get_write_driver_config "$cfg" maxretry
-       get_write_driver_config "$cfg" retrydelay
-       get_write_driver_config "$cfg" pollinterval
-       get_write_driver_config "$cfg" synchronous
+       get_write_ups_config "$cfg" chroot
+       get_write_ups_config "$cfg" driverpath
+       get_write_ups_config "$cfg" maxstartdelay
+       get_write_ups_config "$cfg" maxretry
+       get_write_ups_config "$cfg" retrydelay
+       get_write_ups_config "$cfg" pollinterval
+       get_write_ups_config "$cfg" synchronous
        config_get runas "$cfg" user nut
        RUNAS="$runas"
 
@@ -256,34 +252,42 @@ build_config() {
 
        config_load nut_server
 
-       upsd_runas
+       srv_runas
+       srv_statepath
+
+       [ -d "${STATEPATH}" ] || {
+               mkdir -p "${STATEPATH}"
+       }
+       chmod 0770 "${STATEPATH}"
+
+       if [ -n "$RUNAS" ]; then
+               chown root:"$(id -gn "$RUNAS")" "${STATEPATH}"
+       fi
+
+       SRV_RUNAS="$RUNAS"
        config_foreach build_global_driver_config driver_global
-       config_foreach build_driver_config driver
-       upsd_statepath
+       if [ "$SRV_RUNAS" != "$RUNAS" ]; then
+               echo "WARNING: for proper communication drivers and server must 'runas' the same user" | logger -t nut-server
+       fi
+
+       config_foreach build_ups_config driver
+
        build_server_config
        [ -n "$RUNAS" ] && chgrp "$(id -gn "$RUNAS")" "$UPS_C"
 }
 
-start_driver_instance() {
-       local cfg="$1"
+start_ups_driver() {
+       local ups="$1"
        local requested="$2"
        local driver
        local STATEPATH=/var/run/nut
        local RUNAS=nut
 
-       [ "$havedriver" != 1 ] && return
-
        # If wanting a specific instance, only start it
-       if [ "$requested" != "$cfg" ] && [ "$requested" != "" ]; then
+       if [ "$requested" != "$ups" ] && [ "$requested" != "" ]; then
                return 0
        fi
 
-       mkdir -p "$(dirname "$UPS_C")"
-       chmod 0755 "$UPS_C"
-
-       upsd_statepath
-       build_config
-
        # Avoid hotplug inadvertently restarting driver during
        # forced shutdown
        [ -f /var/run/killpower ] && return 0
@@ -291,18 +295,19 @@ start_driver_instance() {
                return 0
        fi
 
-       if [ -n "$RUNAS" ]; then
-               chown "$RUNAS":"$(id -gn "$RUNAS")" "${STATEPATH}"
-               chgrp "$(id -gn "$RUNAS")" "$UPS_C"
-       fi
+       srv_statepath
+       srv_runas
 
-       config_get driver "$cfg" driver "usbhid-ups"
-       procd_open_instance "$cfg"
+       config_get driver "$ups" driver "usbhid-ups"
+       procd_open_instance "$ups"
        procd_set_param respawn
-       procd_set_param stderr 0
-       procd_set_param stdout 1
-       procd_set_param command /lib/nut/"${driver}" -D -a "$cfg" ${RUNAS:+-u "$RUNAS"}
+       procd_set_param stderr 1
+       procd_set_param stdout 0 # Subset of stderr
+       procd_set_param env NUT_QUIET_INIT_UPSNOTIFY=true
+       procd_set_param env NUT_STATEPATH="${STATEPATH}"
+       procd_set_param command /lib/nut/"${driver}" -FF -a "$ups" ${RUNAS:+-u "$RUNAS"}
        procd_close_instance
+       haveupscfg=1
 }
 
 interface_triggers() {
@@ -333,47 +338,237 @@ interface_triggers() {
 }
 
 start_server_instance() {
-       local cfg="$1"
-
-       [ "$haveserver" != 1 ] && return
-       interface_triggers "check_interface_up" || return
+       local srv="$1"
 
-       procd_open_instance "$cfg"
+       procd_open_instance "$srv"
        procd_set_param respawn
-       procd_set_param stderr 0
-       procd_set_param stdout 1
-       procd_set_param command /usr/sbin/upsd -D ${RUNAS:+-u "$RUNAS"}
+       procd_set_param stderr 1
+       procd_set_param stdout 0 # Subset of stderr
+       procd_set_param env NUT_QUIET_INIT_UPSNOTIFY=true
+       procd_set_param env NUT_STATEPATH="$STATEPATH"
+       procd_set_param command /usr/sbin/upsd -FF ${RUNAS:+-u "$RUNAS"}
        procd_close_instance
 }
 
+# shellcheck disable=SC2120
 start_service() {
        local STATEPATH=/var/run/nut
+       local haveupscfg=0
+       local havesrvcfg=0
 
        # Avoid hotplug inadvertently restarting driver during
        # forced shutdown
        [ -f /var/run/killpower ] && return 0
 
+       srv_statepath
        config_load nut_server
        build_config
 
+       should_start_srv=1
+       [ "$havesrvcfg" = "1" ] || should_start_srv=0
+       # Avoid crashloop on server (upsd) when no ups is configured; make sure server
+       # is not running if no ups is found in configuration
+       [ "$haveupscfg" = "1" ] || should_start_srv=0
+       interface_triggers "check_interface_up" || should_start_srv=0
+
+       [ "$should_start_srv" = "1" ] || return 0
+
        case $@ in
        "")
-               config_foreach start_driver_instance driver "$@"
+               config_foreach start_ups_driver driver
                start_server_instance upsd
                ;;
        *upsd*)
                start_server_instance upsd
                ;;
        *)
-               config_foreach start_driver_instance driver "$@"
+               config_foreach start_ups_driver driver "$@"
                ;;
        esac
 }
 
+reload_ups_driver() {
+       local ups="$1"
+       local requested="$2"
+       local driver
+
+       # If wanting a specific instance, only reload that instance
+       if [ "$requested" != "$ups" ] && [ "$requested" != "" ]; then
+               return 0
+       fi
+
+       # Avoid hotplug inadvertently restarting driver during
+       # forced shutdown
+       [ -f /var/run/killpower ] && return 0
+       if [ -d /var/run/nut ] && [ -f /var/run/nut/disable-hotplug ]; then
+               return 0
+       fi
+
+       config_get driver "$ups" driver "usbhid-ups"
+
+       srv_statepath
+
+       # Try to reload, otherwise exit politely, then stop and restart procd instance
+       if procd_running nut-server "$ups"; then
+               if [ -s "${STATEPATH}/${driver}-${ups}".pid ]; then
+                       # should be respawned by procd
+                       /lib/nut/"${driver}" -c reload-or-exit -a "${ups}" 2>&1 | logger -t nut-server
+               elif pgrep "${driver}" >/dev/null 2>/dev/null; then
+                       procd_send_signal nut-server "${driver}" TERM 2>&1 | logger -t nut-server
+               fi
+       fi
+       /etc/init.d/nut-server start "$ups" 2>&1 | logger -t nut-server
+}
+
 reload_service() {
-       stop_service "$@"
-       sleep 2
-       start_service "$@"
+       local should_stop_srv
+       local STATEPATH=/var/run/nut
+       local havesrvcfg=0
+       local haveupscfg=0
+
+       # Avoid hotplug inadvertently restarting driver during forced shutdown
+       [ -f /var/run/killpower ] && return 0
+
+       config_load nut_server
+       build_config
+
+       should_stop_srv=0
+       [ "$havesrvcfg" = "1" ] || should_stop_srv=1
+       # Avoid crashloop on server (upsd) when no ups is configured; make sure server
+       # is not running if no ups is found in configuration
+       [ "$haveupscfg" = "1" ] || should_stop_srv=1
+       interface_triggers "check_interface_up" || should_stop_srv=1
+
+       if [ "$should_stop_srv" != "0" ]; then
+               if procd_running nut-server upsd >/dev/null 2>&1; then
+                       procd_kill nut-server upsd 2>&1 | logger -t nut-server
+               fi
+               config_foreach stop_ups_driver driver
+       fi
+
+       if (
+               local nut_server_active
+
+               # shellcheck disable=SC1091
+               . /usr/share/libubox/jshn.sh
+               json_init
+               json_add_string name "nut-server"
+
+               nut_server_active=$(_procd_ubus_call list | jsonfilter -l 1 -e "@['nut-server']")
+               [ "$nut_server_active" = "{ }" ] && return 0
+       ); then
+               logger -t nut-server "nut-server active with no instances"
+               /etc/init.d/nut-server start 2>&1 | logger -t nut-server
+       elif procd_running nut-server; then
+               if procd_running nut-server upsd; then
+                       # Try to signal server (upsd) to reload configuration
+                       if [ -s "${STATEPATH}/upsd.pid" ]; then
+                               upsd -c reload 2>&1 | logger -t nut-server
+                       elif pgrep upsd >/dev/null 2>/dev/null; then
+                               procd_send_signal nut-server upsd HUP 2>&1 | logger -t nut-server
+                       fi
+               else
+                       /etc/init.d/nut-server start "upsd" 2>&1 | logger -t nut-server
+               fi
+               config_foreach reload_ups_driver driver
+       else
+               /etc/init.d/nut-server start 2>&1 | logger -t nut-server
+       fi
+}
+
+stop_ups_driver() {
+       local ups="$1" # The ups (driver instance)
+       local requested="$2"
+       local driver
+
+       # If wanting a specific instance, only stop it
+       if [ "$requested" != "$ups" ] && [ "$requested" != "" ]; then
+               return 0
+       fi
+
+       srv_statepath
+       build_ups_config "$ups"
+
+       if [ "$haveupscfg" != "1" ]; then
+               if procd_running nut-server upsd >/dev/null 2>&1; then
+                       procd_kill nut-server upsd 2>&1 | logger -t nut-server
+               fi
+               return 0
+       fi
+
+       config_get driver "$ups" driver "usbhid-ups"
+
+       if procd_running nut-server "$ups"; then
+               if [ -s "${STATEPATH}/${driver}-${ups}".pid ]; then
+                       /lib/nut/"${driver}" -c exit -a "${ups}" 2>&1 | logger -t nut-server
+               elif pgrep "${driver}" >/dev/null 2>/dev/null; then
+                       procd_send_signal nut-server "${driver}" TERM 2>&1 | logger -t nut-server
+               fi
+               if procd_running nut-server upsd >/dev/null 2>&1; then
+                       procd_kill nut-server upsd 2>&1 | logger -t nut-server
+               fi
+       fi
+}
+
+server_active() {
+       # subshell so as not to pollute initscript environment
+       (
+               local nut_server_active
+
+               # shellcheck disable=SC1091
+               . /usr/share/libubox/jshn.sh
+               json_init
+               json_add_string name "nut-server"
+
+               nut_server_active=$(_procd_ubus_call list | jsonfilter -l 1 -e "@['nut-server']")
+               [ "$nut_server_active" = "{ }" ] && return 0
+       )
+}
+
+stop_service() {
+       config_load nut_server
+       srv_statepath
+
+       case $@ in
+       "")
+               if server_active; then
+                       logger -t nut-server "nut-server active with no instances"
+                       procd_kill nut-server 2>&1 | logger -t nut-server
+               elif procd_running nut-server; then # if have at least one instance
+                       if procd_running nut-server upsd; then
+                               # Try to exit politely, then stop procd instance
+                               if [ -s "${STATEPATH}/upsd.pid" ]; then
+                                       upsd -c stop 2>&1 | logger -t nut-server
+                               elif pgrep upsd >/dev/null 2>/dev/null; then
+                                       procd_send_signal nut-server upsd TERM 2>&1 | logger -t nut-server
+                               fi
+                               if procd_running nut-server upsd >/dev/null 2>&1; then
+                                       procd_kill nut-server upsd 2>&1 | logger -t nut-server
+                               fi
+                       fi
+                       config_foreach stop_ups_driver driver
+                       if server_active >/dev/null 2>&1; then
+                               procd_kill nut-server 2>&1 | logger -t nut-server
+                       fi
+               fi
+               ;;
+       *upsd*)
+               if procd_running nut-server upsd; then
+                       # Try to exit politely, then stop procd instance
+                       if [ -s "${STATEPATH}/upsd.pid" ]; then
+                               upsd -c stop 2>&1 | logger -t nut-server
+                       elif pgrep upsd >/dev/null 2>/dev/null; then
+                               procd_send_signal nut-server upsd TERM 2>&1 | logger -t nut-server
+                       fi
+                       if procd_running nut-server upsd >/dev/null 2>&1; then
+                               procd_kill nut-server upsd 2>&1 | logger -t nut-server
+                       fi
+               fi
+               ;;
+       *)
+               config_foreach stop_ups_driver driver "$@"
+               ;;
+       esac
 }
 
 service_triggers() {
git clone https://git.99rst.org/PROJECT