This is a nut-upsd Docker image, implementing the UPS drivers and the upsd daemon from https://networkupstools.org/.
The idea behind this implementation is to have a generic container, which supports monitoring multiple UPS devices from the same container. This is different from other implementations, which are intended to support one (1) container per one (1) UPS.
The drawback of this implementation is that the container can't be easily driven by environment variables, since it can support any valid NUT config, rather than being limited to a subset of drivers (e.g. support for only USB or serial is common to see with other NUT images).
So instead, traditional config files have to be slipped into the container by use of a config volume mount.
Pull as usual:
docker pull ghcr.io/tigattack/nut-upsd[:<tag>]
See ghcr.io/tigattack/nut-upsd for a list of valid tags.
Then run it as follows:
docker run -d \
-p 3493:3493 \
-v /path/to/nut-config:/etc/nut:ro \
[ -e NUT_UID=... ] \
[ -e NUT_GID=... ] \
[ --privileged | --device ... ] \
ghcr.io/tigattack/nut-upsd[:<tag>]
NUT_UID
: NUT user ID. NUT drops privileges to this user after starting as described in upsd(8). While this env var has no default, the default NUT user ID is100
in this image.NUT_GID
: NUT group ID. While this env var has no default, the default NUT group ID is101
in this image.
As this image runs only the UPS drivers and the upsd daemon itself, you only need these configuration files:
This Docker image cannot be configured through environment variables. You have to use a config volume as shown:
- Create the ups.conf, upsd.conf and upsd.users config files with your favourite editor
- Store them into a permanent config directory, e.g.
/opt/nut-upsd
- Apply proper file permissions and ownership.
<UID>
should be the value you gave to theNUT_UID
env var, or100
if unspecified.
<GID>
should be the value you gave to theNUT_GID
env var, or101
if unspecified.cd /opt/nut-upsd chmod 0640 ups.conf upsd.conf upsd.users chown <UID>:<GID> ups.conf upsd.conf upsd.users # Example: chown 100:101 ups.conf upsd.conf upsd.users
- When running the container, point it mount the config directory as a volume, e.g.
-v /opt/nut-upsd:/etc/nut:ro
Tip
The container will fail to start when no volume is mounted, or not all needed files are present!
Some sample config files are provided for your conventience in the example_confs/etc/nut directory. You may use them as a starting point, however I recommend having an in-depth look at the official Network UPS Tools documentation.
You can find example udev rules for USB UPS's in example_confs/etc/udev/rules.d/62-nut-usbups.rules.
In order for the UPS monitoring to work, you have to map your device tree into the Docker container.
Just pass this option to the container at startup: --privileged
Warning
This is the least secure approach, as it grants the container an excessive amount of privileges on the host system.
A better choice than privileged mode is to pass just the individual devices into the container.
This can be done by passing --device
and --device-cgroup-rule
commands to Docker.
First, identify the device-id
, i.e. by running lsusb
:
$ lsusb
Bus 007 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 008 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 006 Device 002: ID 0665:5161 Cypress Semiconductor USB to serial # << generic UPS on USB
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 002: ID 051d:0002 American Power Conversion Uninterruptible Power Supply # << APC UPS on USB
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
The example above reveals two UPS's attached, one to Bus 6, as device #2, the other on Bus 5, as device #2. This translates to the following device paths:
/dev/bus/usb/005/002
/dev/bus/usb/006/002
Get the device major and minor device number like this:
$ ls -l /dev/bus/usb/005/002 /dev/bus/usb/006/002
crwxrwxrwx 1 root root 189, 513 Oct 8 22:06 /dev/bus/usb/005/002
crwxrwxrwx 1 root root 189, 641 Oct 8 22:06 /dev/bus/usb/006/002
The values we're looking for is in columns 5 and 6 respectively.
To map these devices now into the container, the devices have to be passed in, together with a control group rule each, matching the major and minor device number.
docker run [ ... ] \
--device /dev/bus/usb/005/002 --device-cgroup-rule='c 189:513 rw'
--device /dev/bus/usb/006/002 --device-cgroup-rule='c 189:641 rw'
[ ... ]
If you see "access denied" or "insufficient permissions" errors and your container's cgroup rules are correct, you may need to configure udev rules on your host (see example: example_confs/etc/udev/rules.d/62-nut-usbups.rules).
When restarting or reconnecting the UPS over USB, the bus ID can change. This renders the device mapping into the container useless as it will be mapping a non-existent or other device into your container.
To fix this, we need to set a udev rule to symlink the device to another name. We will set this in /etc/udev/rules.d/62-nut-usbups.rules
that we created on setup, and will use our vendor and product attributes for our UPS.
This example is using a Eaton UPS with a vendor ID of 0463
and product ID of ffff
, and I am mapping this to group 1000
, with a symlink set to ups
which will result in our device being symlinked to /dev/ups
.
$ cat /etc/udev/rules.d/62-nut-usbups.rules
ACTION=="remove", GOTO="nut-usbups_rules_end"
SUBSYSTEM=="usb_device", GOTO="nut-usbups_rules_real"
SUBSYSTEM=="usb", GOTO="nut-usbups_rules_real"
GOTO="nut-usbups_rules_end"
LABEL="nut-usbups_rules_real"
ATTR{idVendor}=="0463", ATTR{idProduct}=="ffff", MODE="664", GROUP="1000", SYMLINK+="ups"
LABEL="nut-usbups_rules_end"
Next, reload and trigger the udev rules by either running the first command below or restart the device, then check if it's working:
$ sudo udevadm control -R && sudo udevadm trigger
$ ls -l /dev/ups
lrwxrwxrwx 1 root root 15 Feb 27 21:39 /dev/ups -> bus/usb/001/006
In addition to these steps, we will need to read the link for /dev/ups
when starting our container.
This can be done by using the "readlink" command:
$ docker run -d \
...
--device $(readlink -f /dev/ups) \
...
When the USB device is replugged, the container will in some cases not rebind the device.
This can be fixed by restarting the container, however we would preferably want to automate this process, so we don't have to connect to the machine to restart NUT.
To automate this, we can append a RUN
parameter to our udev rule. The RUN
parameter will run a command when the device is connected. This is recommended to be used in conjunction with the persistent device symlink as above:
$ cat /etc/udev/rules.d/62-nut-usbups.rules
ACTION=="remove", GOTO="nut-usbups_rules_end"
SUBSYSTEM=="usb_device", GOTO="nut-usbups_rules_real"
SUBSYSTEM=="usb", GOTO="nut-usbups_rules_real"
GOTO="nut-usbups_rules_end"
LABEL="nut-usbups_rules_real"
ATTR{idVendor}=="0463", ATTR{idProduct}=="ffff", MODE="664", GROUP="1000", SYMLINK+="ups", RUN+="/bin/bash /home/user/nut/run.sh"
LABEL="nut-usbups_rules_end"
In this example, this will run /bin/bash /home/user/nut/run.sh
, which contains the commands to stop, remove, and run the container. For example:
#!/usr/bin/env bash
docker stop nut
docker rm nut
docker run -d \
...
--device $(readlink -f /dev/ups) \
...
This repository builds on the great work of Gianpaolo Del Matto. Thank you.