diff --git a/sysutils/nut/Makefile b/sysutils/nut/Makefile
index ed35203b9..ee680132f 100644
--- a/sysutils/nut/Makefile
+++ b/sysutils/nut/Makefile
@@ -1,6 +1,5 @@
PLUGIN_NAME= nut
-PLUGIN_VERSION= 1.9
-PLUGIN_REVISION= 1
+PLUGIN_VERSION= 2.0
PLUGIN_COMMENT= Network UPS Tools
PLUGIN_DEPENDS= nut
PLUGIN_MAINTAINER= m.muenz@gmail.com
diff --git a/sysutils/nut/pkg-descr b/sysutils/nut/pkg-descr
index 4018fb54d..13710e77f 100644
--- a/sysutils/nut/pkg-descr
+++ b/sysutils/nut/pkg-descr
@@ -9,6 +9,12 @@ and management interface.
Plugin Changelog
----------------
+2.0
+
+* Add support for any number UPS drivers
+* Add support for any UPS driver
+* Add support for arbitrary global UPS driver options
+
1.9
* Add dashboard widget
diff --git a/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/Api/DriversController.php b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/Api/DriversController.php
new file mode 100644
index 000000000..d9f7ce5a3
--- /dev/null
+++ b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/Api/DriversController.php
@@ -0,0 +1,69 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+namespace OPNsense\Nut\Api;
+
+use OPNsense\Base\ApiMutableModelControllerBase;
+
+class DriversController extends ApiMutableModelControllerBase
+{
+ protected static $internalModelClass = '\OPNsense\Nut\Nut';
+ protected static $internalModelName = 'nut';
+
+ public function searchDriverAction()
+ {
+ return $this->searchBase("drivers.ups", null, "name");
+ }
+
+ public function getDriverAction($uuid = null)
+ {
+ return $this->getBase("ups", "drivers.ups", $uuid);
+ }
+
+ public function addDriverAction()
+ {
+ return $this->addBase("ups", "drivers.ups");
+ }
+
+ public function setDriverAction($uuid)
+ {
+ return $this->setBase("ups", "drivers.ups", $uuid);
+ }
+
+ public function delDriverAction($uuid)
+ {
+ return $this->delBase("drivers.ups", $uuid);
+ }
+
+ public function toggleDriverAction($uuid, $enabled = null)
+ {
+ return $this->toggleBase("drivers.ups", $uuid, $enabled);
+ }
+}
diff --git a/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/DriversController.php b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/DriversController.php
new file mode 100644
index 000000000..2c24621f3
--- /dev/null
+++ b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/DriversController.php
@@ -0,0 +1,46 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+namespace OPNsense\Nut;
+
+use OPNsense\Base\IndexController;
+
+class DriversController extends IndexController
+{
+ public function indexAction()
+ {
+ $this->view->pick('OPNsense/Nut/drivers');
+ $this->view->driversForm = $this->getForm("drivers");
+ $this->view->formDialogDriver = $this->getForm("dialogDriver");
+ $this->view->formGridDriver = $this->getFormGrid("dialogDriver");
+ $model = new \OPNsense\Nut\Nut();
+ $this->view->server_enabled = $model->general->mode == "standalone";
+ }
+}
diff --git a/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/dialogDriver.xml b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/dialogDriver.xml
new file mode 100644
index 000000000..c8629ffcf
--- /dev/null
+++ b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/dialogDriver.xml
@@ -0,0 +1,43 @@
+
diff --git a/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/drivers.xml b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/drivers.xml
new file mode 100644
index 000000000..8168953f4
--- /dev/null
+++ b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/drivers.xml
@@ -0,0 +1,16 @@
+
diff --git a/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/settings.xml b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/settings.xml
index 48b3fdf6a..7024d6c2d 100644
--- a/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/settings.xml
+++ b/sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/settings.xml
@@ -1,230 +1,78 @@
diff --git a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Menu/Menu.xml b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Menu/Menu.xml
index 1e89fcc99..7b5cb2a95 100644
--- a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Menu/Menu.xml
+++ b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Menu/Menu.xml
@@ -2,7 +2,8 @@
-
+
+
diff --git a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Migrations/M2_0_0.php b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Migrations/M2_0_0.php
new file mode 100644
index 000000000..2daa9efcc
--- /dev/null
+++ b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Migrations/M2_0_0.php
@@ -0,0 +1,128 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace OPNsense\Nut\Migrations;
+
+use OPNsense\Base\BaseModelMigration;
+use OPNsense\Core\Config;
+use OPNsense\Firewall\Util;
+
+class M2_0_0 extends BaseModelMigration
+{
+ /**
+ * Migrate older keys into new model
+ * @param $model
+ */
+ public function run($model)
+ {
+ $config = Config::getInstance()->object();
+
+ if (empty($config->OPNsense->Nut)) {
+ return;
+ }
+
+ $nutConfig = $config->OPNsense->Nut;
+
+ // netclient isn't a local UPS so it can't be migrated as such, but
+ // still needs a UPS name.
+ $model->netclient->name = $nutConfig->general->name;
+
+ // Migrate UPS/monitor definitions. Disabled UPSs' names are
+ // suffixed with the driver to not conflict with the enabled UPS.
+ // This should avoid breaking existing configurations. Technically
+ // this can still result in conflicts between enabled UPSs, but
+ // this would only happen if the configuration was broken before
+ // the migration.
+ $this->migrateGenericUps($model, $nutConfig, "usbhid");
+ $this->migrateGenericUps($model, $nutConfig, "apcsmart");
+ $this->migrateGenericUps($model, $nutConfig, "bcmxcpusb");
+ $this->migrateGenericUps($model, $nutConfig, "blazerusb");
+ $this->migrateGenericUps($model, $nutConfig, "blazerser");
+ $this->migrateGenericUps($model, $nutConfig, "qx");
+ $this->migrateGenericUps($model, $nutConfig, "riello");
+ $this->migrateGenericUps($model, $nutConfig, "snmp");
+
+ // apcupsd
+ $ups = $model->drivers->ups->add();
+ $ups->driver = "apcupsd";
+ $ups->enabled = $nutConfig->apcupsd->enable;
+ if (empty($nutConfig->apcupsd->port)) {
+ $ups->port = $nutConfig->apcupsd->hostname;
+ } else {
+ $ups->port = $nutConfig->apcupsd->hostname . ":"
+ . $nutConfig->apcupsd->port;
+ }
+ if ($ups->enabled == "1") {
+ $ups->name = $nutConfig->general->name;
+ } else {
+ $ups->name = $nutConfig->general->name . "_" . $ups->driver;
+ }
+
+ parent::run($model);
+ }
+
+ private function migrateGenericUps($model, $nutConfig, $driverName)
+ {
+ $upsName = $nutConfig->general->name;
+ $ups = $model->drivers->ups->add();
+ $ups->driver = $driverName;
+ $ups->enabled = $nutConfig->$driverName->enable;
+ if ($ups->enabled == "1") {
+ $ups->name = $upsName;
+ } else {
+ $ups->name = $upsName . "_" . $driverName;
+ }
+ $options = explode(",", $nutConfig->$driverName->args);
+ $ports = array_map(
+ function ($o) {
+ return str_replace("port=", "", $o);
+ },
+ array_filter(
+ $options,
+ function ($o) {
+ return str_starts_with($o, "port=");
+ }
+ )
+ );
+ if (empty($ports)) {
+ $ups->port = "auto";
+ } else {
+ $ups->port = $ports[array_key_last($ports)];
+ }
+ $ups->options = implode(
+ ";",
+ array_filter(
+ $options,
+ function ($o) {
+ return !str_starts_with($o, "port=");
+ }
+ )
+ );
+ }
+}
diff --git a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.php b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.php
index a4cd0d50d..554bda4de 100644
--- a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.php
+++ b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.php
@@ -2,6 +2,7 @@
/*
Copyright (C) 2017 Michael Muenz
+ Copyright (C) 2026 Gabriel Smith
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -29,7 +30,135 @@
namespace OPNsense\Nut;
use OPNsense\Base\BaseModel;
+use OPNsense\Base\Messages\Message;
+use OPNsense\Firewall\Util;
class Nut extends BaseModel
{
+ /**
+ * @var null|string the cached listen address for loopback connections if one exists
+ */
+ private $cachedLoopbackListenAddress = null;
+
+ // Assumes that only 127.0.0.1 and ::1 are valid loopback addresses.
+ //
+ // Techinically all of 127.0.0.0/8 could be set up as loopback addresses,
+ // but by default opnSense/FreeBSD doesn't configure these.
+ public function getLoopbackListenAddress()
+ {
+ if ($this->cachedLoopbackListenAddress === null) {
+ $host = "";
+ foreach ($this->general->listen->getValues() as $address) {
+ if (
+ Util::isIpv4Address($address) &&
+ Util::isIPInCIDR($address, "127.0.0.1/32")
+ ) {
+ $host = $address;
+ } elseif (
+ Util::isIpv6Address($address) &&
+ Util::isIPInCIDR($address, "::1/128")
+ ) {
+ $host = $address;
+ }
+ }
+ $this->cachedLoopbackListenAddress = $host;
+ }
+ return $this->cachedLoopbackListenAddress;
+ }
+
+ // If any local monitors are defined, some sort of loopback must be defined
+ // in the listen field.
+ private function checkListenAddressForLocalMonitors($messages)
+ {
+ if (
+ $this->general->mode == "standalone" &&
+ !empty($this->drivers->ups->getNodes()) &&
+ empty($this->getLoopbackListenAddress())
+ ) {
+ if ($this->general->listen->isFieldChanged()) {
+ $messages->appendMessage(new Message(
+ gettext(
+ "Loopback required: A loopback listen address is "
+ . "required when a local UPS is defined. Add a "
+ . "listen address using 127.0.0.1 or ::1."
+ ),
+ "general.listen"
+ ));
+ }
+ foreach ($this->drivers->ups->iterateItems() as $ups) {
+ if ($ups->isFieldChanged()) {
+ $messages->appendMessage(new Message(
+ gettext(
+ "Loopback required: A loopback listen address is "
+ . "required when local a UPS is defined. Add a "
+ . "listen address using 127.0.0.1 or ::1."
+ ),
+ $ups->__reference . ".enabled"
+ ));
+ }
+ }
+ }
+ }
+
+ private function checkForUniqueUpsDefinitions($messages)
+ {
+ // Ensure UPS names are unique.
+ $ups_names = [];
+ foreach ($this->drivers->ups->iterateItems() as $ups) {
+ $name = (string) $ups->name;
+ if (isset($ups_names[$name])) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ "Duplicate entry: The name %s is already used. "
+ . "Each name must be unique."
+ ),
+ $name
+ ),
+ $ups->__reference . ".name"
+ ));
+ } else {
+ $ups_names[$name] = true;
+ }
+ }
+
+ // Ensure each UPS driver/port combination is unique.
+ $ups_ports = [];
+ foreach ($this->drivers->ups->iterateItems() as $ups) {
+ $driver = (string) $ups->driver;
+ $port = (string) $ups->port;
+ $key = $driver . "_" . $port;
+ if (isset($ups_ports[$key])) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ "Duplicate entry: A UPS with driver %s and port "
+ . "%s is already defined. Each driver and port "
+ . "combination must be unique."
+ ),
+ $name
+ ),
+ $ups->__reference . ".port"
+ ));
+ } else {
+ $ups_ports[$key] = true;
+ }
+ }
+ }
+
+ public function performValidation($validateFullModel = false)
+ {
+ $messages = parent::performValidation($validateFullModel);
+
+ // Invalidate the cached loopback listen address if the listen addresses
+ // were changed.
+ if ($this->general->listen->isFieldChanged()) {
+ $this->cachedLoopbackListenAddress = null;
+ }
+
+ $this->checkForUniqueUpsDefinitions($messages);
+ $this->checkListenAddressForLocalMonitors($messages);
+
+ return $messages;
+ }
}
diff --git a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.xml b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.xml
index 8ff52d823..1f0ec779d 100644
--- a/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.xml
+++ b/sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Nut.xml
@@ -1,7 +1,7 @@
//OPNsense/Nut
Network UPS Tools
- 1.0.4
+ 2.0.0
@@ -16,12 +16,6 @@
netclient
-
- UPSName
- Y
- /^([0-9a-zA-Z._\-]){1,128}$/u
- The name should only contain alphanumeric characters, dashes, underscores or a dot.
-
127.0.0.1,::1
Y
@@ -37,74 +31,16 @@
Password
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
-
-
- Y
- 0
-
-
- Y
- localhost
-
-
- N
-
-
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
Y
0
+
+ Y
+ /^([0-9a-zA-Z._\-]){1,128}$/u
+ The name should only contain alphanumeric characters, dashes, underscores or a dot.
+
3493
@@ -117,35 +53,32 @@
N
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
-
-
- Y
- 0
-
-
- port=auto
- N
-
-
-
-
- Y
- 0
-
-
- community=public
- N
-
-
+
+
+ ;
+
+
+
+ 1
+ Y
+
+
+ main
+ Y
+ /^([0-9a-zA-Z._\-]){1,128}$/u
+ The name should only contain alphanumeric characters, dashes, underscores or a dot.
+
+
+ Y
+
+
+ auto
+ Y
+
+
+ ;
+
+
+
diff --git a/sysutils/nut/src/opnsense/mvc/app/views/OPNsense/Nut/drivers.volt b/sysutils/nut/src/opnsense/mvc/app/views/OPNsense/Nut/drivers.volt
new file mode 100644
index 000000000..2302c1b6c
--- /dev/null
+++ b/sysutils/nut/src/opnsense/mvc/app/views/OPNsense/Nut/drivers.volt
@@ -0,0 +1,68 @@
+{#
+ # Copyright (C) 2026 Gabriel Smith
+ #
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions are met:
+ #
+ # 1. Redistributions of source code must retain the above copyright notice,
+ # this list of conditions and the following disclaimer.
+ #
+ # 2. Redistributions in binary form must reproduce the above copyright
+ # notice, this list of conditions and the following disclaimer in the
+ # documentation and/or other materials provided with the distribution.
+ #
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ # POSSIBILITY OF SUCH DAMAGE.
+ #}
+
+
+
+{% if !server_enabled %}
+
+
+ {{ lang._('NUT is in netclient mode. All local driver definitions will be ignored.') }}
+
+
+{% endif %}
+
+
+ {{ partial("layout_partials/base_form", ['fields':driversForm, 'id':'frm_nut-drivers']) }}
+
+ {{ partial('layout_partials/base_bootgrid_table', formGridDriver) }}
+
+
+{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/nut/service/reconfigure'}) }}
+{{ partial("layout_partials/base_dialog",['fields':formDialogDriver,'id':formGridDriver['edit_dialog_id'],'label':lang._('Edit UPS')])}}
diff --git a/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/ups.conf b/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/ups.conf
index 846345cf7..3d46a75bc 100644
--- a/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/ups.conf
+++ b/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/ups.conf
@@ -1,87 +1,20 @@
# Please don't modify this file as your changes might be overwritten with
# the next update.
#
-{% if helpers.exists('OPNsense.Nut.general.enable') and OPNsense.Nut.general.enable == '1' %}
-{% if helpers.exists('OPNsense.Nut.usbhid.enable') and OPNsense.Nut.usbhid.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=usbhid-ups
-{% if helpers.exists('OPNsense.Nut.usbhid.args') and OPNsense.Nut.usbhid.args != '' %}
-{% for args in OPNsense.Nut.usbhid.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.apcsmart.enable') and OPNsense.Nut.apcsmart.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=apcsmart
-{% if helpers.exists('OPNsense.Nut.apcsmart.args') and OPNsense.Nut.apcsmart.args != '' %}
-{% for args in OPNsense.Nut.apcsmart.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.apcupsd.enable') and OPNsense.Nut.apcupsd.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=apcupsd-ups
-{% if helpers.exists('OPNsense.Nut.apcupsd.port') and OPNsense.Nut.apcupsd.port != '' %}
-port={{ OPNsense.Nut.apcupsd.hostname }}:{{ OPNsense.Nut.apcupsd.port }}
-{% else %}
-port={{ OPNsense.Nut.apcupsd.hostname }}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.bcmxcpusb.enable') and OPNsense.Nut.bcmxcpusb.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=bcmxcp_usb
-{% if helpers.exists('OPNsense.Nut.bcmxcpusb.args') and OPNsense.Nut.bcmxcpusb.args != '' %}
-{% for args in OPNsense.Nut.bcmxcpusb.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.blazerusb.enable') and OPNsense.Nut.blazerusb.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=blazer_usb
-{% if helpers.exists('OPNsense.Nut.blazerusb.args') and OPNsense.Nut.blazerusb.args != '' %}
-{% for args in OPNsense.Nut.blazerusb.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.blazerser.enable') and OPNsense.Nut.blazerser.enable == '1' %}
-user=root
-[{{ OPNsense.Nut.general.name }}]
-driver=blazer_ser
-{% if helpers.exists('OPNsense.Nut.blazerser.args') and OPNsense.Nut.blazerser.args != '' %}
-{% for args in OPNsense.Nut.blazerser.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.qx.enable') and OPNsense.Nut.qx.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=nutdrv_qx
-{% if helpers.exists('OPNsense.Nut.qx.args') and OPNsense.Nut.qx.args != '' %}
-{% for args in OPNsense.Nut.qx.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.riello.enable') and OPNsense.Nut.riello.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=riello_usb
-{% if helpers.exists('OPNsense.Nut.riello.args') and OPNsense.Nut.riello.args != '' %}
-{% for args in OPNsense.Nut.riello.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.snmp.enable') and OPNsense.Nut.snmp.enable == '1' %}
-[{{ OPNsense.Nut.general.name }}]
-driver=snmp-ups
-{% if helpers.exists('OPNsense.Nut.snmp.args') and OPNsense.Nut.snmp.args != '' %}
-{% for args in OPNsense.Nut.snmp.args.split(',') %}
-{{ args }}
-{% endfor %}
-{% endif %}
-{% endif %}
+
+{% if helpers.exists('OPNsense.Nut.drivers.extra_global_options') %}
+{% for option in OPNsense.Nut.drivers.extra_global_options.split(';') %}
+{{ option }}
+{% endfor %}
{% endif %}
+{% for ups in helpers.toList('OPNsense.Nut.drivers.ups') %}
+{% if ups.enabled|default("0") == "1" %}
+[{{ ups.name }}]
+driver={{ ups.driver }}
+port="{{ ups.port }}"
+{% for option in (ups.options|default("")).split(";") if option %}
+{{ option }}
+{% endfor %}
+{% endif %}
+
+{% endfor %}
diff --git a/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/upsmon.conf b/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/upsmon.conf
index e4d3ab405..40fe45867 100644
--- a/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/upsmon.conf
+++ b/sysutils/nut/src/opnsense/service/templates/OPNsense/Nut/upsmon.conf
@@ -1,53 +1,16 @@
# Please don't modify this file as your changes might be overwritten with
# the next update.
#
-{% if helpers.exists('OPNsense.Nut.usbhid.enable') and OPNsense.Nut.usbhid.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
SHUTDOWNCMD "/usr/local/etc/rc.halt"
POWERDOWNFLAG /etc/killpower
+{% set generalSettings = helpers.getNodeByTag('OPNsense.Nut.general') %}
+{% if generalSettings.mode|default("none") == "standalone" %}
+{% for ups in helpers.toList("OPNsense.Nut.drivers.ups") %}
+{% if ups.enabled|default("0") == "1" %}
+MONITOR {{ ups.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} primary
+{% endif %}
+{% endfor %}
{% endif %}
{% if helpers.exists('OPNsense.Nut.netclient.enable') and OPNsense.Nut.netclient.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }}@{{ helpers.host_with_port('OPNsense.Nut.netclient.address', 'OPNsense.Nut.netclient.port') }} 1 {{ OPNsense.Nut.netclient.user }} {{ OPNsense.Nut.netclient.password }} slave
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.apcsmart.enable') and OPNsense.Nut.apcsmart.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.apcupsd.enable') and OPNsense.Nut.apcupsd.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.bcmxcpusb.enable') and OPNsense.Nut.bcmxcpusb.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.blazerusb.enable') and OPNsense.Nut.blazerusb.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.blazerser.enable') and OPNsense.Nut.blazerser.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.qx.enable') and OPNsense.Nut.qx.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.riello.enable') and OPNsense.Nut.riello.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
-{% endif %}
-{% if helpers.exists('OPNsense.Nut.snmp.enable') and OPNsense.Nut.snmp.enable == '1' %}
-MONITOR {{ OPNsense.Nut.general.name }} 1 monuser {{ OPNsense.Nut.account.mon_password }} master
-SHUTDOWNCMD "/usr/local/etc/rc.halt"
-POWERDOWNFLAG /etc/killpower
+MONITOR {{ OPNsense.Nut.netclient.name }}@{{ helpers.host_with_port('OPNsense.Nut.netclient.address', 'OPNsense.Nut.netclient.port') }} 1 {{ OPNsense.Nut.netclient.user }} {{ OPNsense.Nut.netclient.password }} secondary
{% endif %}