Debian Configlets

Jeff Licquia

Eric Gillespie, Jr.

John R. Daily

$Progeny: book.xml,v 1.17 2002/02/13 18:29:16 epg Exp $

Table of Contents
1. Introduction
1.1. Overview
2. Architecture
2.1. Components
3. Writing a configlet
3.1. Files
3.2. Python code
3.3. Multi-Page Configlets
4. Front End Programming
4.1. Introduction
4.2. Writing a Front End
4.2.1. The BasicConfigGroup Class
4.2.2. Starting Configlets
4.2.3. Interacting with Debconf
A. API Reference
List of Examples
3-1. Etherconf gnome_setup
3-2. Etherconf attributes and registration
3-3. Etherconf load_debconf
3-4. Etherconf report_debconf

Chapter 1. Introduction


Chapter 2. Architecture

2.1. Components

Package maintainers may provide main.py and main.glade files to take advantage of the configlet infrastructure; see the API documentation for further details.

/usr/sbin/update-configlets invokes scripts installed by any existing configlet front ends in order to register new configlets.

A GNOME configlet druid, /usr/sbin/configlet-druid, provides a front-end wizard for all installed configlets.

The configlet module is the only component which may communicate directly with debconf; it does so via debconf-communicate(1).

/usr/bin/configlet-capplet is symlinked to configlet-specific scripts that are invoked by the GNOME Control Center.

The important Python classes to be re-used by configlet-druid and code supplied by package maintainers is found in /usr/lib/python2.1/site-packages/configlet.py.

configlet.py contains the Configlet base class, which individual configlets inherit from, as well as a simple container class which implements basic functionality for dynamically loading and instantiating configlets installed as subdirectories of a common configlet directory.

/usr/bin/test-configlets is a simple configlet front-end.


Chapter 3. Writing a configlet

3.1. Files

Configlets consist of several files, all contained in a common directory. The minimal files included in this directory are:

These are the only two files directly referenced by the infrastructure classes. The developer is free to include new files or subdirectories in addition to these two. For example, some configlets may include icons or other graphics.

The Glade file has two requirements:

  • It must contain widgets that correspond to the configlet pages that the configlet needs to display. Each of these widgets must have a name that will be used by the configlet API. Since these widgets will be inserted into a parent object created by the front ends, they should not be toplevel objects; they may be wrapped by a toplevel to allow them to be edited in Glade, as long as those toplevel objects can be discarded at runtime. As a special case, single-page configlets may name their page widget mainwidget; this will enable some automatic handling by the API.

  • The configlet page widgets must not be set visible by default; they are set visible as a result of the configlet module when appropriate.


3.2. Python code

The Python file contains the code for the configlet. It must follow these rules:

If your configlet is a front end to Debconf questions (most are), you will also want to define load_debconf and report_debconf methods. load_debconf receives as the list of all Debconf values, not just those pertaining to the packages this configlet configures. It is up to the configlet to determine which values it is interested in.

Example 3-3. Etherconf load_debconf


    def load_debconf(self, dcdata):
        self.mail_config = []

        for i in dcdata:
            (template, question, value) = re.split(r"\s+", i, 2)
            (package, varname) = re.split("/", question, 1)

            if package == "postfix":
                self.mail_config.append(i)

            if package != "etherconf":
                continue

            if value == '""' or value == "none":
                value = ""

            if varname == "INT-devices":
                # We don't need to read this one, but we do need to
                # set it.  See debconf() below.
                pass
            elif varname == "hostname":
                if value:
                    self.hostname_entry.set_text(value)
            elif varname == "domainname":
                if value:
                    self.domainname_entry.set_text(value)
            elif varname == "nameservers":
                if value:
                    self.nameservers_entry.set_text(value)

            else:
                # If a ValueError occurs, this is not a dhcp-p:eth0
                # type question (which is what we're after), so it
                # doesn't matter what it is, we're simply not
                # interested.
                try:
                    (varname, device) = re.split(":", varname, 1)
                except ValueError:
                    continue

                info = self.get_device_info(device)
                if varname == "configure":
                    if value == "true":
                        info.configure = TRUE
                    else:
                        info.configure = FALSE
                elif varname == "removable":
                    if value == "true":
                        info.removable = TRUE
                    else:
                        info.removable = FALSE
                elif varname == "dhcp-p":
                    if value == "true":
                        info.dhcp = TRUE
                    else:
                        info.dhcp = FALSE
                elif varname == "dhcphost":
                    if value:
                        info.dhcphost = value
                elif varname == "ipaddr":
                    if value:
                        info.ip = value
                elif varname == "netmask":
                    if value:
                        info.netmask = value
                elif varname == "gateway":
                    if value:
                        info.gateway = value

        self.setup_menu(self.devices)
        self.change_device(self.devices[0])

        

Configlet directories must be located in a standard place, /usr/share/configlets/, where front ends can find them easily. Note that the directory name under /usr/share/configlets is arbitrary, but should be descriptive and unlikely to risk a collision with other configlets, and must not contain spaces or other unusual characters.

Here is a sample hierarchy.


 /usr/share/configlets
   |
   +--etherconf
   |   |
   |   +--main.glade
   |   +--main.py
   |
   +--timezoneconf
   |   |
   |   +--main.glade
   |   +--main.py
   |
 (and so on)
      

Any package providing a configlet must call update-configlets --install directory_name in its postinst and update-configlets --remove directory_name in its prerm; this allows the configlet system to register configlets with front ends that require registration of some kind. This will require that the package either test for /usr/sbin/update-configlets in the postinst or declare a dependency on the configlet package.


Chapter 4. Front End Programming


4.2. Writing a Front End

Front ends generally perform a standard set of tasks in the same order.


4.2.1. The BasicConfigGroup Class

For many of these functions, the API provides helper functions that handle the most common cases. These functions are provided as methods to the BasicConfigGroup class. Most front ends will need to merely create an instance of this class, or optionally inherit from it and create an instance of the derived class.

A BasicConfigGroup object presents the interface of a Python sequence, containing a set of configlets in priority-sorted order. This sequence is created by the constructor using a directory path; this directory is search for subdirectories containing configlets, and each configlet found is instantiated and added to the sequence. The directory defaults to /usr/share/configlets, which means that the group will instantiate all configlets installed in the standard location by default.

In addition to the normal sequence interface, BasicConfigGroup provides other helper methods which act on all configlets within the group. These will be described below.


4.2.2. Starting Configlets

All configlets must be started using the start_configlet function. It takes one argument: the directory in which the configlet resides. It returns a fully instantiated configlet object.

Configlets are only meant to be instantiated once. If start_configlet is called more than once for the same directory, it will return a reference to the object it created previously, rather than creating a new one.

If you use BasicConfigGroup, it uses start_configlet to start all of the configlets it finds. Thus, you do not need to call start_configlet yourself. Be aware, however, that the configlets in BasicConfigGroup have been created this way, and in particular cannot be "recreated" outside the group.


4.2.3. Interacting with Debconf

In order for the configlets to be useful, they need to be fed data from the debconf database, and they need to be able to feed changes back to debconf. The front end is responsible for doing this, using two classes from the API: the privileged runner classes and the DebConf class.

The privileged runner classes are intended to provide an interface for front ends to perform tasks as root. They have a simple interface: a run function, that takes the command to run as the only argument. No return value is provided; run will throw a RuntimeError exception if there is any problem running the command. Because the interface is so simple, the privileged runner classes do not inherit; a class only needs to provide a run method in order to be a privileged runner.

The API provides two privileged runner classes: SimplePrivilegedRunner and GnomeSudoPrivilegedRunner. The default privileged runner, SimplePrivilegedRunner, simply assumes that the front end already has root privileges and runs commands accordingly. GnomeSudoPrivilegedRunner uses the gnome-sudo command to obtain root to run the command; this provides a nice graphical password dialog to the user when root privilege is needed and caches the successful authentication for subsequent commands. See the gnome-sudo documentation for setting this up properly.

The DebConf class does the actual work of getting and setting the debconf information. A proper DebConf object for the version of debconf can be obtained by calling get_debconf, passing in the list of packages that the configlets configure. (This information can be retrieved by calling get_packages on the configlet, and calling get_packages on a BasicConfigGroup will get all of the packages configured by all of the configlets in the group.) As the DebConf class uses a privileged runner class to do its work, you should set an appropriate privileged runner class for it by calling the set_privileged_runner method, passing in the privileged runner object as an argument.

Once the DebConf object is set up, it provides three methods: get, set, and commit. The get method returns an array of strings containing the debconf data in the format the configlets need. The set method takes a similarly formatted array of strings as debconf data and sets it in the database. The commit method takes it upon itself to cause the configured packages to reread their debconf data and reconfigure themselves accordingly.

Once debconf information is read, it must be passed to the configlets. This is done by taking the data returned from the get method and passing it to the configlet's load_debconf method. The configlet will search through the data, finding the appropriate values it needs and saving them internally in some way.

Saving debconf information involves several more steps. First, all configlets must be told to prepare for shutdown; this is done with the on_gnome_close method. Then, call report_debconf on each configlet, merge the results into a single array of strings, and pass that array to DebConf's set method. Finally, call DebConf.commit to cause the packages to reconfigure.

The BasicConfigGroup class does much of this work for you. By calling the load_all_debconf method, you can cause the group to load the debconf information and send it to all of the configlets in the group. Similarly, the save_and_commit_all_debconf method will read all of the debconf information from the configlets, save it to the debconf database, and commit it. The BasicConfigGroup takes care of creating a DebConf object and managing it for you; you should, however, create an appropriate privileged runner and set it for the group by calling the group's set_privileged_runner method on it. As an alternative, you can get access to the group's DebConf object with the get_debconf method; you can then iterate across all of the configlets in the group to load or save data, or use the group's load_debconf and report_debconf methods, which do the iteration for you.


Appendix A. API Reference

The API reference is here.