create new tag
view all tags

Introduction to LCFG : Part 5

The following examples assume you are using the virtual appliance described in IntroductoryTutorial.

This tutorial follows on from IntroductoryTutorialPart4a. In the following examples we will work on the task of managing the resolv.conf file. Although in this tutorial we're only configuring this for a single machine keep in mind that this could be something you need to do for a large collection of machines. Once you go beyond a small number of machines doing this manually will be inefficient, tedious and there's the potential to miss out some of them.

Getting Started

As with the previous tutorial start by opening several terminal windows. To see the entire process of a change to an LCFG source profile being applied tail the server and client log files - /var/log/lcfg/server and /var/log/lcfg/client. You will also need root access in one terminal so you can modify the source profile, that can be acquired using sudo bash with the password being the same as the username.

For all these examples we will be using the LCFG file component, this is well suited to the task of managing a single file but once things become more complicated (e.g. you also need to manage services) you will want to look for another more specific component that supports that service.

All the files for this tutorial are provided in the lcfg-tutorial package, for Focal they are stored in the /usr/share/lcfg/tutorial/examples/focal/. To ensure you have the latest version run apt update and apt upgrade before starting.

Example 8 - Sub-Classing the file Component

In this tutorial we're going to focus on the task of managing the /etc/resolv.conf file using LCFG. This file is used to configure the DNS resolver, for details see the man page.

An example file might look like:

search inf.ed.ac.uk
options attempts:2 edns0 inet6 ndots:2 timeout:5

It might seem that we could manage this in a similar way to the previous message of the day example as a literal block of text, particularly if we just need exactly the same file on every machine within our network. Ideally however we want to be able to manage individual parts of this configuration via LCFG resources. If, for example, a network uses multiple VLANs it's likely that the file contents will be mostly the same on all machines with a small amount of variation.


This file contains the following configuration:

  • An ordered list of nameservers
  • A search list for host-name lookup.
  • Some options which control the resolver.

Each of those could be simply mapped onto an LCFG resource e.g. nameservers, search and options.

Doing this with simple resource references when the LCFG server processes the profile would be awkward due to the list of nameservers which we need to loop over. As this is a single file with no associated services to manage it makes sense to use a sub-class of the file component to process a template. That will provide a separate namespace and support for more complex resource types which will improve how the configuration is managed.

First a schema needs to be created, the component will be named resolv so this should be named resolv-1.def

This is available in defaults/resolv-1.def, copy that file into place like:

cp /usr/share/lcfg/tutorial/examples/focal/defaults/resolv-1.def /var/lib/lcfg/conf/server/defaults/

If you examine the file you will see that it looks like:

#include "ngeneric-1.def"
#include "mutate.h"

schema 1

!ng_cfdepend      mSET(>file)
!ng_statusdisplay mSET(false)
!ng_reconfig      mSET()

#define FILE_DEFAULT_LIST conf
#include "file_base-2.def"

type_conf       template : perl
tmpl_conf       resolvconf.tmpl
file_conf         /etc/resolv.conf


search <%profile.domain%>



  • The schema resource, this is the version of our schema (1 in this case), it must be an integer and it forms part of the file name (which is resolv-1.def). It's possible to have multiple versions of a schema installed, this makes it easy to manage schema transitions through the usual release-management process.

  • This includes version 2 of the file_base schema and specifies that the tag name for the managed file is conf.

  • This includes the ngeneric schema, some of the resources need to be mutated so that changes to the resolv component resources cause the LCFG client to trigger the file component to do the actual processing.

  • The template is named resolvconf.tmpl and the Perl Template Toolkit will be used for the processing.

  • There are 3 extra resources, none of them have been given any type annotations. Note that the search resource has a default value which references the profile.domain resource, if nothing is explicitly specified in the case of the virtual appliance for this tutorial this would produce search localdomain

Once copied into place the LCFG server will note the creation of this schema file but as it is not yet included in any profiles no changes will be triggered.

Header file

Next a header file is required to actually load the component into your LCFG profile.

If it doesn't already exist, create a local/options directory and install the example resolv.h file:

mkdir -p /var/lib/lcfg/conf/server/include/local/options
cp /usr/share/lcfg/tutorial/examples/focal/include/options/resolv.h /var/lib/lcfg/conf/server/include/local/options

If you examine the contents of that header file you will see it is like:


!profile.components         mADD(resolv)
profile.version_resolv      1

/* Register this component with the main file component */
!file.components            mADD(resolv)

/* Avoid breaking the real file for the tutorial VM */
resolv.file_conf                 /etc/resolv-lcfg.conf



  • This loads version 1 of the resolv component schema into the profile.

  • The default value for the resolv.file_conf resource is overridden, note that no mutation is required here, the default value will only be used when no value is explicitly specified in a header or source profile. This often catches out people, the default value for a resource (as specified in a schema) is NOT used as an initial value.

Once copied into place the LCFG server will note the creation of this header file but as it is not yet included in any profiles no changes will be triggered.


The primary purpose of an LCFG component is to translate the resources into system configuration. When the file component is being used this translation work is all done through templates.

Firstly install the example template:

cp /usr/share/lcfg/tutorial/examples/focal/resolvconf.tmpl /usr/share/lcfg/conf/file

Normally that template file would be provided in that location as part of a package (e.g. RPM or Deb), in this case we do it manually to simulate that distribution process.

Aside: Every LCFG component has a standard directory within /usr/share/lcfg/conf/ where it will search for templates if you specify them using a relative path. You may otherwise store them anywhere if you specify them using an absolute path.

If you examine the template file you will see it looks like:

# This file is managed by the LCFG resolv component. Do not edit.
[% FOREACH server in nameservers.split -%]
nameserver [% server %]
[% END -%]
[% IF options -%]
options [% options %]
[% END -%]
[% IF search -%]
search [% search %]
[% END -%]


  • LCFG resources are mapped directly into variables for the Template.

  • The - at the end of some blocks is used for neatness to avoid inserting extra newline characters (Template Toolkit knows this as POST_CHOMP)

  • The nameservers resource is expected to be a space-separated list of server IP addresses (this way you can mutate it with mADD). The split method will separate the items on whitespace by default.

  • The options and search specifications are inside conditional blocks which will only be true if the resources have a non-empty non-zero value, this avoids setting the option with no value which might not be permitted syntax.

Source Profile

Finally include the header into the LCFG profile for the VM:

This is available in source/example8, either copy that file to overwrite your lcfg-tutorial profile (in /var/lib/lcfg/conf/server/source/) like:

cp /usr/share/lcfg/tutorial/examples/focal/source/example8 /var/lib/lcfg/conf/server/source/lcfg-tutorial

Or directly edit the lcfg-tutorial source profile, remove any previous configuration and add the following:

/* Basic example profile */

#include <local/os/ubuntu/minimal_focal.h>
#include <lcfg/hw/virtualbox.h>
#include <lcfg/options/desktop.h>
#include <lcfg/options/lcfg-server.h>

#include <local/options/resolv.h>

/* eof */

Once you have modified the profile check the server and client logs. Once the change has reached the client you should see that it has accepted the new profile and that the /etc/resolv-lcfg.conf file has been changed.

The file should now look like:

# This file is managed by the LCFG resolv component. Do not edit.
search localdomain

As usual the resources can be queried using qxprof

qxprof -a resolv

Example 9 : Using the new component

At this point you have a fully functional, if somewhat, basic component to manage the /etc/resolv.conf file. Try mutating a few resources in the source profile so that it generates something useful.

This is available in source/example9, either copy that file to overwrite your lcfg-tutorial profile (in /var/lib/lcfg/conf/server/source/) like:

cp /usr/share/lcfg/tutorial/examples/focal/source/example9 /var/lib/lcfg/conf/server/source/lcfg-tutorial

Or directly edit the lcfg-tutorial source profile to add something like the following:

!resolv.nameservers mADD(
!resolv.nameservers mADD(
!resolv.nameservers mADD(
!resolv.search mSET(ed.ac.uk)

That produces a configuration file which would be suitable for a machine on the ed.ac.uk network, you can substitute settings for your own domain where more appropriate.

Note that those resources could alternatively be added to the local/options/resolv.conf header which would allow them to be easily shared between all source profiles for your local network.

You could also try modifying the template file. The client doesn't monitor that file so changes do not trigger the file component to do any work. You can force a reconfiguration using om like this:

om file configure

-- squinney - 2021-04-08

Topic revision: r3 - 2021-04-23 - squinney
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2021 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback