From f0eec7c646dfa0b0e1949a0ad521746e93e90e1f Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Sat, 28 Feb 2026 10:40:31 -0500 Subject: [PATCH] sysutils/nut: Add support for any number of UPS drivers --- sysutils/nut/Makefile | 3 +- sysutils/nut/pkg-descr | 6 + .../OPNsense/Nut/Api/DriversController.php | 69 ++++ .../OPNsense/Nut/DriversController.php | 46 +++ .../OPNsense/Nut/forms/dialogDriver.xml | 43 +++ .../OPNsense/Nut/forms/drivers.xml | 16 + .../OPNsense/Nut/forms/settings.xml | 296 +++++------------- .../mvc/app/models/OPNsense/Nut/Menu/Menu.xml | 3 +- .../models/OPNsense/Nut/Migrations/M2_0_0.php | 128 ++++++++ .../mvc/app/models/OPNsense/Nut/Nut.php | 129 ++++++++ .../mvc/app/models/OPNsense/Nut/Nut.xml | 133 ++------ .../mvc/app/views/OPNsense/Nut/drivers.volt | 68 ++++ .../service/templates/OPNsense/Nut/ups.conf | 99 +----- .../templates/OPNsense/Nut/upsmon.conf | 53 +--- 14 files changed, 637 insertions(+), 455 deletions(-) create mode 100644 sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/Api/DriversController.php create mode 100644 sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/DriversController.php create mode 100644 sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/dialogDriver.xml create mode 100644 sysutils/nut/src/opnsense/mvc/app/controllers/OPNsense/Nut/forms/drivers.xml create mode 100644 sysutils/nut/src/opnsense/mvc/app/models/OPNsense/Nut/Migrations/M2_0_0.php create mode 100644 sysutils/nut/src/opnsense/mvc/app/views/OPNsense/Nut/drivers.volt 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 @@ +
+ + ups.enabled + + checkbox + Enable or disable this UPS. + + 20 + boolean + rowtoggle + + + + ups.name + + text + Set a name for this UPS. + + + ups.driver + + text + Set the driver for this UPS. + + + ups.port + + text + Set the port for this UPS. + + + ups.options + + + ; + select_multiple + true + Set options for for this UPS. Valid options depend on the driver. + + false + + +
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 @@ +
+ + header + + true + + + nut.drivers.extra_global_options + + + select_multiple + true + Set extra options for ups.conf not for a specific driver. + true + +
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 @@
- - - nut.general.enable - - checkbox - Enable or disable the nut service. If enabled, the system will shutdown when the UPS emits a low battery warning. - - - nut.general.mode - - dropdown - Set the service mode. Currently only standalone and netclient are available. - - - nut.general.name - - text - Set a name for your UPS. - - - nut.general.listen - - - select_multiple - true - Set the addresses this service listen on. - - - - - nut.account.admin_password - - text - Set the password for admin user "admin". - - - nut.account.mon_password - - text - Set the password for monitoring user "monuser". - - + + nut.general.enable + + checkbox + Enable or disable the nut service. If enabled, the system will shutdown when the UPS emits a low battery warning. + + + nut.general.mode + + dropdown + Set the service mode. Currently only standalone and netclient are available. + + + nut.general.listen + + + select_multiple + true + Set the addresses this service listen on. + - - - - nut.usbhid.enable - - checkbox - Enable the USBHID driver. - - - nut.usbhid.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.apcsmart.enable - - checkbox - Enable the APCSMART driver. - - - nut.apcsmart.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.apcupsd.enable - - checkbox - Enable the APCUPSD controlled devices driver. - - - nut.apcupsd.hostname - - text - Set the hostname or ip of the remote apcupsd server. - - - nut.apcupsd.port - - text - Set the port of the remote apcupsd server (optional). - - - - - nut.bcmxcpusb.enable - - checkbox - Enable the PowerWare BCMXCPUSB driver. - - - nut.bcmxcpusb.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.blazerusb.enable - - checkbox - Enable the BlazerUSB driver. - - - nut.blazerusb.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.blazerser.enable - - checkbox - Enable the BlazerSerial driver. Please be aware that this driver needs to run nut-tools as root. - - - nut.blazerser.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.netclient.enable - - checkbox - Enable the Netclient driver. - - - nut.netclient.address - - text - Set the IP address of the remote NUT server. - - - nut.netclient.port - - text - Set the TCP port of the remote NUT server. - - - nut.netclient.user - - text - Set the username of the remote NUT server. - - - nut.netclient.password - - password - Set the password of the remote NUT server. - - - - - nut.qx.enable - - checkbox - Enable the QX driver. - - - nut.qx.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.riello.enable - - checkbox - Enable the Riello driver. - - - nut.riello.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "port=auto". - - - - - nut.snmp.enable - - checkbox - Enable the SNMP driver. - - - nut.snmp.args - - - select_multiple - true - Set extra arguments for this UPS, e.g. "community=public". - - + + + nut.account.admin_password + + text + Set the password for admin user "admin". + + + nut.account.mon_password + + text + Set the password for monitoring user "monuser". + + + + + nut.netclient.enable + + checkbox + Enable the Netclient driver. + + + nut.netclient.name + + text + Set the name for the remote UPS. + + + nut.netclient.address + + text + Set the IP address of the remote NUT server. + + + nut.netclient.port + + text + Set the TCP port of the remote NUT server. + + + nut.netclient.user + + text + Set the username of the remote NUT server. + + + nut.netclient.password + + password + Set the password of the remote NUT server. + - nut-general-settings + nut-general 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 %} + +{% 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 %}