NUT configuration management with Augeas
========================================

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.

.From link:http://augeas.net[Augeas homepage]:
[quote]
____
_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.

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 link:http://augeas.net[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'.

[[augeas_user]]

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.


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


Start testing and using
-----------------------

Augeas provides many tools and
link:http://augeas.net/download.html[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 link:http://augeas.net/docs/index.html[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


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()
--------------------------------------------------------------------------------
