6. NUT configuration management with Augeas

6.1. Introduction

Configuration has long been one of the two main NUT weaknesses. This is mostly due to the framework nature of NUT, and its many components and features, which make NUT configuration a very complex task.

In order to address this point, NUT now provides configuration tools and manipulation abstraction, to anybody who want to manipulate NUT configuration, through Augeas lenses and modules.

Augeas is a configuration editing tool. It parses configuration files in their native formats and transforms them into a tree. Configuration changes are made by manipulating this tree and saving it back into native config files.

In other words, Augeas is the dreamed Registry, with all the advantages (such as a uniform interface and tools), and the added bonus of being free/libre open source software and letting liberty on configuration file format.

6.2. Requirements

To be able to use Augeas with NUT, you will need to install Augeas, and also the NUT provided lenses, which describe NUT configuration files format.

Augeas

Having Augeas installed. You will need at least version 0.5.1 (prior versions may work too, reports are welcome).

As an example, on Debian and derivatives, do the following:

:; apt-get install augeas-lenses augeas-tools

And optionally:

:; apt-get install libaugeas0 libaugeas-dev python-augeas

On RedHat and derivatives, you have to install the packages augeas and augeas-libs.

NUT lenses and modules for Augeas

These are the *.aug files in the present directory.

You can either install the files to the right location on your system, generally in /usr/share/augeas/lenses/, or use these from NUT source directory (nut/scripts/augeas). The latter is to be preferred for the time being.

6.3. Create a test sandbox

Note

For now, it is easier to include an existing /etc/nut/ directory.

:; export AUGEAS_ROOT=./augeas-sandbox
:; mkdir $AUGEAS_ROOT
:; sudo cp -pr /etc/nut $AUGEAS_ROOT
:; sudo chown -R $(id -nu):$(id -ng) $AUGEAS_ROOT

6.4. Start testing and using

Augeas provides many tools and languages bindings (Python, Perl, Java, PHP, Ruby, …), still with the same simple logic.

This chapter will only illustrate some of these. Refer to the language binding’s help and Augeas documentation for more information.

Shell

Start an augeas shell using:

:; augtool -b

Note

If you have not installed NUT lenses, add -I/path/to/nut/scripts/augeas.

From there, you can perform different actions like:

  • list existing NUT-related files:

    augtool> ls /files/etc/nut/
    nut.conf/ = (none)
    upsd.users/ = (none)
    upsmon.conf = (none)
    ups.conf/ = (none)
    upsd.conf/ = (none

    or using the matcher:

    augtool> match /files/etc/nut/*
    /files/etc/nut/nut.conf = (none)
    /files/etc/nut/upsd.users = (none)
    /files/etc/nut/upsmon.conf = (none)
    /files/etc/nut/ups.conf = (none)
    /files/etc/nut/upsd.conf = (none)

    Note

    If you don’t see anything, you may search for error messages by using:

    augtool> ls /augeas/files/etc/nut/*/errors

    and

    augtool> get /augeas/files/etc/nut/ups.conf/error/message
    /augeas/files/etc/nut/ups.conf/error/message = Permission denied
  • create a new device entry (in ups.conf), called augtest:

    augtool> set /files/etc/nut/ups.conf/augtest/driver dummy-ups
    augtool> set /files/etc/nut/ups.conf/augtest/port auto
    augtool> save
  • list the devices currently using the usbhid-ups driver:

    augtool> match /files/etc/nut/ups.conf/*/driver dummy-ups

C ~

A library is available for C programs, along with pkg-config support.

You can get the compilation and link flags using the following code in your program’s configure script or Makefile:

CFLAGS="`pkg-config --silence-errors --cflags augeas`"
LDFLAGS="`pkg-config --silence-errors --libs augeas`"

Here is a code sample using this library for NUT configuration:

augeas *a = aug_init(NULL, NULL, AUG_NONE);
ret = aug_match(a, "/files/etc/nut/*", &matches_p);
ret = aug_set(a, "/files/etc/nut/ups.conf/augtest/driver", "dummy-ups");
ret = aug_set(a, "/files/etc/nut/ups.conf/augtest/port", "auto");
ret = aug_save(a);

Python

The augeas class abstracts access to the configuration files.

$ python
Python 2.5.1 (r251:54863, Apr  8 2008, 01:19:33)
[GCC 4.3.0 20080404 (Red Hat 4.3.0-6)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import augeas
>>> a = augeas.augeas()
>>> a.match("/files/etc/nut/*")
['/files/etc/nut/upsd.users', '/files/etc/nut/upsmon.conf', '/files/etc/nut/ups.conf', '/files/etc/nut/upsd.conf']
>>> a.set("/files/etc/nut/ups.conf/augtest/driver", "dummy-ups")
True
>>> a.set("/files/etc/nut/ups.conf/augtest/port", "auto")
True
>>> a.save()
True
>>>
$ grep -A 2 augtest /etc/nut/ups.conf
[augtest]
driver=dummy-ups
port=auto

Perl

The Perl binding is available through CPAN and packages.

use Config::Augeas;
my $aug = Config::Augeas->new( root => $aug_root ) ;
my @a = $aug->match("/files/etc/nut/*") ;
my $nb = $aug->count_match("/files/etc/nut/*") ;
$aug->set("/files/etc/nut/ups.conf/augtest/driver", "dummy-ups") ;
$aug->set("/files/etc/nut/ups.conf/augtest/port", "auto") ;
$aug->save ;

Test the conformity testing module

Existing configuration files can be tested for conformity. To do so, use:

$ augparse -I ./ ./test_nut.aug

6.5. Complete configuration wizard example

Here is a Python example that generate a complete and usable standalone configuration:

import augeas

device_name="dev1"
driver_name="usbhid-ups"
port_name="auto"

a = augeas.augeas()

# Generate nut.conf
a.set("/files/etc/nut/nut.conf/MODE", "standalone")

# Generate ups.conf
# FIXME: chroot, driverpath?
a.set(("/files/etc/nut/ups.conf/%s/driver" % device_name), driver_name)
a.set(("/files/etc/nut/ups.conf/%s/port" % device_name), port_name)

# Generate upsd.conf
a.set("/files/etc/nut/upsd.conf/#comment[1]", "just to touch the file!")

# Generate upsd.users
user = "admin"
a.set(("/files/etc/nut/upsd.users/%s/password" % user), "dummypass")
a.set(("/files/etc/nut/upsd.users/%s/actions/SET" % user), "")
# FIXME: instcmds lens should be fixed, as per the above rule
a.set(("/files/etc/nut/upsd.users/%s/instcmds" % user), "ALL")

monuser = "monuser"
monpasswd = "******"
a.set(("/files/etc/nut/upsd.users/%s/password" % monuser), monpasswd)
a.set(("/files/etc/nut/upsd.users/%s/upsmon" % monuser), "primary")

# Generate upsmon.conf
a.set("/files/etc/nut/upsmon.conf/MONITOR/system/upsname", device_name)
# Note: we prefer to omit localhost, not to be bound to a specific
# entry in /etc/hosts, and thus be more generic
#a.set("/files/etc/nut/upsmon.conf/MONITOR/system/hostname", "localhost")
a.set("/files/etc/nut/upsmon.conf/MONITOR/powervalue", "1")
a.set("/files/etc/nut/upsmon.conf/MONITOR/username", monuser)
a.set("/files/etc/nut/upsmon.conf/MONITOR/password", monpasswd)
a.set("/files/etc/nut/upsmon.conf/MONITOR/type", "primary")

# FIXME: glitch on the generated content
a.set("/files/etc/nut/upsmon.conf/SHUTDOWNCMD", "/sbin/shutdown -h +0")

# save config
a.save()
a.close()