# Setup nut and netdata on Ubuntu

In this document, I will explain how to setup `nut` (Network UPS Tools) on Ubuntu __18.04__ and __20.04__.

> It is basically the next chapter of my previous gist, [Upgrade nut on Ubuntu 18.04](https://gist.github.com/Jiab77/d015f8a9630c0fb3548bcdf17d29f69f).

I'll only document `USB` connected __UPS__ and not the other supported connection modes.

## Install required dependencies

### Nut

```
sudo apt install nut
```

### Netdata

```
bash <(curl -Ss https://my-netdata.io/kickstart.sh) all --dont-wait --disable-telemetry
```

## Must read

I'd seriously recommend you to take the time to explore this [book](http://rogerprice.org/NUT/ConfigExamples.A5.pdf) written by [Roger Price](http://rogerprice.org/NUT/) that contains tons of useful information about how to implement and configure [nut](https://github.com/networkupstools/nut).

## Detect connected UPS

Here is how you can detect your connected UPS. I'll mainly focus on the `USB` connection and not the other connection modes.

```bash
# Scan all available devices (default)
$ nut-scanner

# Scan USB devices only
$ nut-scanner -U

# Scan USB devices only but print in ups.conf format
$ nut-scanner -UN
```

Use the following commands to add the details about the connected UPS in the `/etc/nut/ups.conf` file:

```bash
# Add some spacing and UPS details
echo -e "\n# Detected UPS from USB" | sudo tee -a /etc/nut/ups.conf

# Add detected UPS details
nut-scanner -UNq 2>/dev/null | sudo tee -a /etc/nut/ups.conf

# Check the result
sudo cat /etc/nut/ups.conf
```

You should get something similar:

```ini
# Detected UPS from USB
[nutdev1]
	driver = "usbhid-ups"
	port = "auto"
	vendorid = "051D"
	productid = "0002"
	bus = "001"
```

You can now change `[nutdev1]` by something more representative to your UPS and also add the `desc` field. You'll need `sudo` to edit the config files.

```bash
# Edit the config file
sudo nano /etc/nut/ups.conf
```

Then change the previously generated config that way:

```ini
# Detected UPS from USB
[APCBU1400]
	driver = "usbhid-ups"
	port = "auto"
	desc = "APC Back-UPS 1400VA / 700w"
	vendorid = "051D"
	productid = "0002"
	bus = "001"
```

## Create monitoring connection

Once the UPS defined in the `/etc/nut/ups.conf` file, we must define the monitoring user that will used to read the data from the UPS and define the actions according to them.

The created user is not related to the standard UNIX users, it will only exist in the `nut` context.

Here is how to create it:

```bash
# Install password generator
sudo apt install pwgen

# Generate strong password (but avoid using special chars)
export NUT_RAND_PW=$(pwgen -snc -B 128 | awk '{ print $1 }')

# Create monitoring user dynamicaly
echo -e "\n[monitor]\n\tpassword = ${NUT_RAND_PW}\n\tupsmon master\n" | sudo tee -a /etc/nut/upsd.users

# Generate connection string
# You will have to copy it in the 'upsmon.conf' file later
echo -e "\nMONITOR APCBU1400@localhost 1 monitor $NUT_RAND_PW master\n"

# Remove generated password from environment
unset NUT_RAND_PW
```

You should get something similar:

```
$ export NUT_RAND_PW=$(pwgen -snc -B 128 | awk '{ print $1 }')
$ echo -e "\n[monitor]\n\tpassword = ${NUT_RAND_PW}\n\tupsmon master\n" | sudo tee -a /etc/nut/upsd.users

[monitor]
	password = [REDACTED]
	upsmon master

$ echo -e "\nMONITOR APCBU1400@localhost 1 monitor $NUT_RAND_PW master\n"

MONITOR APCBU1400@localhost 1 monitor [REDACTED] master

$ unset NUT_RAND_PW
```

Now we have to add the generated connection string into the `/etc/nut/upsmon.conf` file:

```
sudo nano /etc/nut/upsmon.conf
```

after the examples of the __MONITOR__ section:

```bash
# Examples: 
#
# MONITOR myups@bigserver 1 monmaster blah master
# MONITOR su700@server.example.com 1 upsmon secretpass slave
# MONITOR myups@localhost 1 upsmon pass master	(or slave)

MONITOR APCBU1400@localhost 1 monitor [REDACTED] master

# --------------------------------------------------------------------------
```

## Improve UPS commands scheduling script

By default the `/bin/upssched-cmd` script is pretty basic and does not contains enough commands:

```bash
#! /bin/sh
#
# This script should be called by upssched via the CMDSCRIPT directive.
# 
# Here is a quick example to show how to handle a bunch of possible
# timer names with the help of the case structure.
#
# This script may be replaced with another program without harm.
#
# The first argument passed to your CMDSCRIPT is the name of the timer
# from your AT lines.

case $1 in
	upsgone)
		logger -t upssched-cmd "The UPS has been gone for awhile"
		;;
	*)
		logger -t upssched-cmd "Unrecognized command: $1"
		;;
esac
```

Now, create a copy of the original script:

```bash
# Create a copy
sudo cp -v /bin/upssched-cmd /bin/upssched-cmd.bak

# Edit the current version
sudo nano /bin/upssched-cmd
```

And change the script to that:

```bash
#! /bin/sh
#
# This script should be called by upssched via the CMDSCRIPT directive.
# 
# Here is a quick example to show how to handle a bunch of possible
# timer names with the help of the case structure.
#
# This script may be replaced with another program without harm.
#
# The first argument passed to your CMDSCRIPT is the name of the timer
# from your AT lines.

# Dynamic name assignment
UPS=$( upsc -l 2>/dev/null )

# Or manual name assignement
# UPS="YOUR-UPS-DEFINED-NAME-IN-UPS.CONF"

STATUS=$( upsc $UPS ups.status )
CHARGE=$( upsc $UPS battery.charge )
CHMSG="[$STATUS]:$CHARGE%"

case $1 in
	online) MSG="$UPS, $CHMSG - power supply has been restored." ;;
	onbatt) MSG="$UPS, $CHMSG - power failure - save your work!" ;;
	lowbatt) MSG="$UPS, $CHMSG - shutdown now!" ;;
	upsgone) MSG="The UPS $UPS has been gone for awhile" ;;
	*) MSG="$UPS, $CHMSG - Unrecognized command: $1" ;;
esac
logger -i -t upssched-cmd $MSG

# Comment out the line below for workstations
# notify-send-all "$MSG"
```

## Configure commands scheduling

Now that we have improved the existing commands scheduling scripts, we have to edit the `/etc/nut/upssched.conf` file and change or set some values:

```bash
# Define PIPE file
sudo sed -e 's|# PIPEFN /run/|PIPEFN /run/|' -i /etc/nut/upssched.conf

# Define Lock file
sudo sed -e 's|# LOCKFN /run/|LOCKFN /run/|' -i /etc/nut/upssched.conf

# Add commands defined in the scripts
echo -e "\n# Custom commands" | sudo tee -a /etc/nut/upssched.conf
echo -e "AT ONLINE $(upsc -l 2>/dev/null)@localhost EXECUTE online\nAT ONBATT $(upsc -l 2>/dev/null)@localhost EXECUTE onbatt\nAT LOWBATT $(upsc -l 2>/dev/null)@localhost EXECUTE lowbatt" | sudo tee -a /etc/nut/upssched.conf

# Comment out this line if it didn't worked with the 'upsc' command above
# echo -e "AT ONLINE APCBU1400@localhost EXECUTE online\nAT ONBATT APCBU1400@localhost EXECUTE onbatt\nAT LOWBATT APCBU1400@localhost EXECUTE lowbatt" | sudo tee -a /etc/nut/upssched.conf

# Check the result
sudo nano /etc/nut/upssched.conf
```

## Start nut services

Now that we have defined the UPS in the `/etc/nut/ups.conf` file and the monitoring user in the `/etc/nut/upsd.users` then finally set the monitoring connection in `/etc/nut/upsmon.conf`, we must now change the running mode which is by default set to `none` and would not let `systemd` services to run correctly. We'll set it to the `standalone` mode for now.

```bash
# Change the running mode
sudo sed -e 's/MODE=none/MODE=standalone/' -i /etc/nut/nut.conf

# Check the result
sudo nano /etc/nut/nut.conf
```

You can now restart all the `nut` related `systemd` services:

```
for S in nut-client.service nut-driver.service nut-monitor.service nut-server.service ; do sudo systemctl restart $S ; done
```

Wait few seconds to let them start then check their status:

```
for S in nut-client.service nut-driver.service nut-monitor.service nut-server.service ; do systemctl status $S -l ; done
```

You should see something similar:

```
$ for S in nut-client.service nut-driver.service nut-monitor.service nut-server.service ; do systemctl status $S -l ; done
● nut-monitor.service - Network UPS Tools - power device monitor and shutdown controller
   Loaded: loaded (/lib/systemd/system/nut-monitor.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-07-02 07:49:48 CEST; 1min 8s ago
  Process: 8731 ExecStart=/sbin/upsmon (code=exited, status=0/SUCCESS)
 Main PID: 8733 (upsmon)
    Tasks: 2 (limit: 4915)
   Memory: 3.0M
   CGroup: /system.slice/nut-monitor.service
           ├─8732 /lib/nut/upsmon
           └─8733 /lib/nut/upsmon

jui 02 07:49:48 [REDACTED] upsmon[8731]: Using power down flag file /etc/killpower
jui 02 07:49:48 [REDACTED] systemd[1]: nut-monitor.service: Can't open PID file /run/nut/upsmon.pid (yet?) 
jui 02 07:49:48 [REDACTED] upsmon[8732]: Startup successful
jui 02 07:49:48 [REDACTED] systemd[1]: nut-monitor.service: Supervising process 8733 which is not our child
jui 02 07:49:48 [REDACTED] systemd[1]: Started Network UPS Tools - power device monitor and shutdown contro
jui 02 07:49:48 [REDACTED] upsmon[8733]: Login on UPS [APCBU1400@localhost] failed - got [ERR ACCESS-DENIED
jui 02 07:49:53 [REDACTED] upsmon[8733]: Poll UPS [APCBU1400@localhost] failed - Write error: Broken pipe
jui 02 07:49:53 [REDACTED] upsmon[8733]: Communications with UPS APCBU1400@localhost lost
jui 02 07:49:58 [REDACTED] upsmon[8733]: Communications with UPS APCBU1400@localhost established
jui 02 07:49:58 [REDACTED] upsmon[8733]: UPS APCBU1400@localhost on line power
● nut-driver.service - Network UPS Tools - power device driver controller
   Loaded: loaded (/lib/systemd/system/nut-driver.service; static; vendor preset: enabled)
   Active: active (running) since Fri 2021-07-02 07:49:48 CEST; 1min 16s ago
  Process: 8704 ExecStart=/sbin/upsdrvctl start (code=exited, status=0/SUCCESS)
 Main PID: 8725 (usbhid-ups)
    Tasks: 1 (limit: 4915)
   Memory: 1.1M
   CGroup: /system.slice/nut-driver.service
           └─8725 /lib/nut/usbhid-ups -a APCBU1400

jui 02 07:49:48 [REDACTED] systemd[1]: Starting Network UPS Tools - power device driver controller...
jui 02 07:49:48 [REDACTED] upsdrvctl[8704]: Using subdriver: APC HID 0.96
jui 02 07:49:48 [REDACTED] upsdrvctl[8704]: Network UPS Tools - Generic HID driver 0.41 (2.7.4)
jui 02 07:49:48 [REDACTED] upsdrvctl[8704]: USB communication driver 0.33
jui 02 07:49:48 [REDACTED] upsdrvctl[8704]: Network UPS Tools - UPS driver controller 2.7.4
jui 02 07:49:48 [REDACTED] usbhid-ups[8725]: Startup successful
jui 02 07:49:48 [REDACTED] systemd[1]: Started Network UPS Tools - power device driver controller.
● nut-monitor.service - Network UPS Tools - power device monitor and shutdown controller
   Loaded: loaded (/lib/systemd/system/nut-monitor.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-07-02 07:49:48 CEST; 1min 16s ago
  Process: 8731 ExecStart=/sbin/upsmon (code=exited, status=0/SUCCESS)
 Main PID: 8733 (upsmon)
    Tasks: 2 (limit: 4915)
   Memory: 3.0M
   CGroup: /system.slice/nut-monitor.service
           ├─8732 /lib/nut/upsmon
           └─8733 /lib/nut/upsmon

jui 02 07:49:48 [REDACTED] upsmon[8731]: Using power down flag file /etc/killpower
jui 02 07:49:48 [REDACTED] systemd[1]: nut-monitor.service: Can't open PID file /run/nut/upsmon.pid (yet?) 
jui 02 07:49:48 [REDACTED] upsmon[8732]: Startup successful
jui 02 07:49:48 [REDACTED] systemd[1]: nut-monitor.service: Supervising process 8733 which is not our child
jui 02 07:49:48 [REDACTED] systemd[1]: Started Network UPS Tools - power device monitor and shutdown contro
jui 02 07:49:48 [REDACTED] upsmon[8733]: Login on UPS [APCBU1400@localhost] failed - got [ERR ACCESS-DENIED
jui 02 07:49:53 [REDACTED] upsmon[8733]: Poll UPS [APCBU1400@localhost] failed - Write error: Broken pipe
jui 02 07:49:53 [REDACTED] upsmon[8733]: Communications with UPS APCBU1400@localhost lost
jui 02 07:49:58 [REDACTED] upsmon[8733]: Communications with UPS APCBU1400@localhost established
jui 02 07:49:58 [REDACTED] upsmon[8733]: UPS APCBU1400@localhost on line power
● nut-server.service - Network UPS Tools - power devices information server
   Loaded: loaded (/lib/systemd/system/nut-server.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-07-02 07:49:52 CEST; 1min 16s ago
  Process: 8787 ExecStart=/sbin/upsd (code=exited, status=0/SUCCESS)
 Main PID: 8800 (upsd)
    Tasks: 1 (limit: 4915)
   Memory: 1.1M
   CGroup: /system.slice/nut-server.service
           └─8800 /lib/nut/upsd

jui 02 07:49:52 [REDACTED] upsd[8787]: fopen /run/nut/upsd.pid: No such file or directory
jui 02 07:49:52 [REDACTED] upsd[8787]: listening on 127.0.0.1 port 3493
jui 02 07:49:52 [REDACTED] upsd[8787]: listening on ::1 port 3493
jui 02 07:49:52 [REDACTED] upsd[8787]: listening on 127.0.0.1 port 3493
jui 02 07:49:52 [REDACTED] upsd[8787]: listening on ::1 port 3493
jui 02 07:49:52 [REDACTED] upsd[8787]: Connected to UPS [APCBU1400]: usbhid-ups-APCBU1400
jui 02 07:49:52 [REDACTED] upsd[8787]: Connected to UPS [APCBU1400]: usbhid-ups-APCBU1400
jui 02 07:49:52 [REDACTED] upsd[8800]: Startup successful
jui 02 07:49:52 [REDACTED] systemd[1]: Started Network UPS Tools - power devices information server.
jui 02 07:49:58 [REDACTED] upsd[8800]: User monitor@::1 logged into UPS [APCBU1400]
```

If yes, then you can read the data available from your UPS that way:

```bash
# Use 'upsc -l' to get the name of the UPS
# Then pass it as self argument to read the UPS data
upsc $(upsc -l 2>/dev/null) 2>/dev/null
```

It should output something similar but with different values according to your UPS:

```
$ upsc $(upsc -l 2>/dev/null) 2>/dev/null
battery.charge: 100
battery.charge.low: 10
battery.charge.warning: 50
battery.date: 2001/09/25
battery.mfr.date: 2020/09/17
battery.runtime: 2604
battery.runtime.low: 120
battery.type: PbAc
battery.voltage: 27.3
battery.voltage.nominal: 24.0
device.mfr: American Power Conversion
device.model: Back-UPS XS 1400U 
device.serial: [REDACTED]  
device.type: ups
driver.name: usbhid-ups
driver.parameter.bus: 001
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.parameter.productid: 0002
driver.parameter.synchronous: no
driver.parameter.vendorid: 051D
driver.version: 2.7.4
driver.version.data: APC HID 0.96
driver.version.internal: 0.41
input.sensitivity: medium
input.transfer.high: 280
input.transfer.low: 155
input.voltage: 234.0
input.voltage.nominal: 230
ups.beeper.status: enabled
ups.delay.shutdown: 20
ups.firmware: 926.T2 .I
ups.firmware.aux: T2 
ups.load: 14
ups.mfr: American Power Conversion
ups.mfr.date: 2020/09/17
ups.model: Back-UPS XS 1400U 
ups.productid: 0002
ups.realpower.nominal: 700
ups.serial: [REDACTED]  
ups.status: OL
ups.test.result: No test initiated
ups.timer.reboot: 0
ups.timer.shutdown: -1
ups.vendorid: 051d
```

## Configure Netdata

You can now configure __Netdata__ and add the name of your UPS in the configuration file:

```bash
# Go to the netdata config folder
cd /etc/netdata

# Run the editing script
sudo ./edit-config charts.d/nut.conf
```

You don't really need to change anything as normally the defaults are good enough to simply having to restart the service and nothing more:

```
sudo systemctl restart netdata ; systemctl status netdata -l
```

Now you can navigate to <http://localhost:19999> and search for the __UPS__ section on the right, you should see something like that:

![image](https://user-images.githubusercontent.com/9881407/124230288-10eea600-db0f-11eb-9e6c-e1a50390fcb7.png)

## References

* <https://github.com/networkupstools/nut>
* <https://wiki.archlinux.org/title/Network_UPS_Tools>
* <https://manpages.ubuntu.com/manpages/focal/en/man8/nut-scanner.8.html>
* <http://rogerprice.org/NUT/>
* <http://rogerprice.org/NUT/ConfigExamples.A5.pdf>
* <https://learn.netdata.cloud/docs/agent/collectors/charts.d.plugin/nut>