Skip to content

Templates for configuration files in Univention

Dieser Beitrag ist auch in deutscher Sprache verfügbar: Vorlagen für Konfigurationsdateien in Univention

Those of you who know us will remember that we’re good friends with Univention. They offer a Linux distribution, enhanced by powerful and easy-to-use administrative tools, aiming directly at the heart of IT infrastructures: providing cross-platform domain services. I really dig that combination.

Those of you who know me a bit will also understand why. Linux is an open system. I can modify it. I can analyze its inner workings. Well alright, I can break it just as easily, but a system without any risk — where would be the fun in that?

But hold on for a moment — does that actually fit? „Standardized“, „easy to administer“, „flexible“ and „open“? Univention’s convinced: of course it does.

One of the cogs in the system bridging those requirements is Univention’s templating system for configuration files. That process combines template files with administrative settings and produces the actual configuration files. It is used for everything that’s managed centrally in Univention: web servers such as Apache, email servers like Dovecot and Postfix, services for Windows provided by Samba or CUPS.

And that’s what this post is all about: how does that mechanism work in the first place? First and foremost: how can I customize such managed configuration files — preferably without shooting myself in the foot? This is actually a regularly occurring question in Univention’s forum and the inspiration for digging deeper and writing this blog post.

Here we go!

Fictional scenario: my Postfix configuration needs an addition.

First try: as an experienced Linux admin I dive right in and modify Postfix’s own configuration file main.cf directly. A quick reload of Postfix’s configuration, a short test, it works. Great! Couple of days later: it doesn’t work. Ouch, my foot!

So what did I overlook? Well, the totally obvious message right at the beginning of the file, of course: „Warning: This file is auto-generated and might be overwritten by univention-config-registry“. My first conclusion is therefore: hands off whenever you see that warning! Otherwise your foot will look like mine in no time.

Next idea: let’s see where this ominous univention-config-registry gets its input for my file from. After some quick grepping I strike gold: oh, there are files in /etc/univention/templates/files/etc/postfix.

So here’s my second try: inserting my changes in one of those files. But that doesn’t seem to affect anything — the original file /etc/postfix/main.cf still doesn’t contain my changes. Maybe I have to give a little shove somehow so that the file is re-created? Oh well. After another quick search I try ucr commit /etc/postfix/main.cf — and this time my change appears in the!

Let’s get to the point:

A configuration file is created from either a single, big template file or from multiple partial template files. The templates are always located in one of the sub-directories of /etc/univention/templates/files. Those templates have to be registered with the templating system so that the system knows which template files form which configuration file. This information is stored in files in the directory /etc/univention/templates/info. Yet another file, /etc/univention/registry.info/variables, describes which variables exist in the first place.

File names and locations

As mentioned above there are two modes: a single, big template file (called file) and several smaller template files (called multifile).

For the first mode, file, there’s only one source template file for each destination file. In this case the template’s file name matches the configuration file’s name: e.g. the configuration file /etc/krb5.conf is created from /etc/univention/templates/files/etc/krb5.conf.

For the second mode, multifile, there’s a sub-directory whose name matches the configuration file’s name with an additional .d suffix. All partial templates reside in that sub-directory. For example, the aforementioned /etc/postfix/main.cf is generated from all the partial templates files in /etc/univention/templates/files/etc/postfix/main.d.

Partial templates are evaluated in alphabetical order. A convention is to use numeric prefixes to force a particular order for the source files — e.g. 10_general is evaluated before 80_deliver. Other than that there are no further restrictions on the template’s file name; the extension doesn’t matter either.

Embedding variables and Python code

Templates are dynamic: they can include values directly from the Univention Config Registry. Additionally they can contain arbitrary Python code that generates the desired output.

The syntax for embedding simple variables looks like this: @%@variable@%@, e.g. @%@ldap/base@%@ for the LDAP directory’s base DN. Python code blocks are marked by two specially-formatted lines that look like this: @!@. The Python code can access the Config Registry via the special object configRegistry. At the moment Python 2.7 is used for interpreting the code.

Everything outside of Python code blocks is copied verbatim into the output file — safe for the variable substitution, of course.

Creating destination files

The tool used to create the destination file (actual configuration file) is the same one used for querying or modifying the Config Registry: univention-config-registry. Lazybones such as myself will prefer its shorter form: ucr. Creating the destination file is done with: ucr commit Zieldateinamen, e.g. ucr commit /etc/postfix/main.cf /etc/postfix/master.cf. You have to use the absolute path to the destination file to be created, not the path to the templates it is created from. A simple ucr commit without a file name causes all registered destination files to be created.

Restarting the services is something that ucr doesn’t do, by the way. So keep in mind to do that manually instead of putting your feet up just yet.

Registering variables

If you want to introduce a new variable, you’ll have to let the system know about it. That’s what the files in /etc/univention/registry.info/variables/ are there for. Their format is INI-like. They don’t just provide the variable’s name but also human-readable descriptions and explanations that are output when you search for variables, e.g. with ucr search ….

The names of all files in that directory have to end in .cfg. Otherwise they’re skipped.

Registering templates

Just like variables, templates have to be registered so that ucr knows about them. Otherwise they’re simply ignored. That registration is done via files in the directory /etc/univention/templates/info. There are two important restrictions for the names of those files: the name must end in .info (otherwise the file will be skipped), and the base name must be the same for both, the variable registration file and the template registration file. The second part only applies if there actually is a variable registration file, of course.

You should not modify existing files. You can and should add new ones instead. Using a unique prefix that makes sense to you (e.g. my-) can be helpful for keeping track of your additions.

These files contain the following pieces of information:

  1. Which mode is used (Type: file and Type: multifile respectively)?
  2. What’s the destination file’s path and name (e.g. Multifile: etc/postfix/main.cf)?
  3. Which variables are used by the templates?
  4. Which partial templates have to be read for mode multifile?

Last but not least you have to let the system know that both, the .info and the .cfg exist. This is done by executing ucr register MyFile'sBaseName once, e.g. ucr register my-postfix.

Due to the information from step 3 in the list above ucr knows which destination files to recreate automatically whenever a variable is changed. That only works if the variable has been registered properly via the aforementioned .cfg file and the call to ucr register….

Avoiding catastrophes during upgrades

During system upgrades new template files may be installed.

The safest way of modifying templates is by adding your own partial files for templates of type multifile. They should be registered via your own .info files. Just use one of the existing files such as univention-mail-postfix.info as an example and you’ll do just fine.

For templates of type file there’s no way around modifying that template file itself. After each system upgrade you should run the command univention-check-templates. It checks if there are any modified template files for which a newer version provided by Univention exists. If that’s the case, you have to merge both files manually.

Is that really necessary?

Before doing all mentioned steps you should ask yourself if modifying the template is really necessary. Univention provides a lot of variables that allow you to set quite a number of configuration options already. There are two ways how you can find out about them:

  1. ucr search SearchTerm — this is a search through all registered and documented variables. Unfortunately not all variables are registered properly. Therefore:
  2. A direct search in the template files in /etc/univention/templates/files/… themselves. Just apply common sense for figuring out if setting the desired configuration option via a variable is supported already.

Example

Let’s assume I want to change Postfix’s milter_default_action option. I’ve already looked through all variables and templates and determined that there’s currently no support for changing it via the default variables. Therefore I start by creating an info file, e.g. /etc/univention/templates/info/my-postfix.info:

Type: subfile
Multifile: etc/postfix/main.cf
Subfile: etc/postfix/main.cf.d/99_my_milter
Variables: mail/postfix/milter_default_action

Next I create a new partial template file, e.g. called /etc/univention/templates/files/etc/postfix/main.cf.d/99_my_milter, with the following content:

@!@
print 'milter_default_action = %s' % configRegistry.get('mail/postfix/milter_default_action', 'tempfail')
@!@

As that variable mail/postfix/milter_default_action doesn’t exist yet, I also create a registration file for it (/etc/univention/registry.info/variables/my-postfix.cfg):

[mail/postfix/milter_default_action]
Description[de]=Legt fest, wie Postfix auf Milter-Anwendungsfehler reagiert (default=tempfail)
Description[en]=This parameter specifies how Postfix handles Milter application errors (default=tempfail)
Type=str
Categories=service-mail

A quick call to ucr register my-postfix and the registration’s complete.

Almost done: I can now set that variable via ucr set mail/postfix/milter_default_action=reject and see that ucr recreates Postfix’s configuration file immediately. Now all that’s left is reloading the Postfix daemon, and I’m finally done.