Service-Level Settings Format

Author: Jacob Peddicord <jpeddicord@ubuntu.com>
Description:SLS format 0.8 introduction and specification

Contents

Introduction

Are you the maintainer (or interested developer) of a system service? Do you want to make certain aspects of your service easily configurable by system administrators and users, without the need to edit config files? You've come to the right place.

Service-Level Settings (SLS) is a small XML format that describes tunable settings about a service. It contains information on setting names and types, where and how they are stored, and how they should be presented to a user. This information is all loaded by jobservice, a system job management daemon. jobservice parses this information and sends details to client programs, such as jobs-admin, for rendering to the user.

This document describes the format of an SLS definition with examples and further instructions. After reading this, you should be able to write your own in no time. If you can't, then I've failed as a documentation writer and you should email me directly for help. My email is located at the top of this document.

If you've written a SLS file and want me to look over it or give some pointers, feel free to contact me.

Examples are provided throughout the document. This is intended to be read from the top, but if you just want to start hacking you may be able to jump straight to the Type Reference for a quick reference.

XML Format

I'm going to assume you have at least basic knowledge of XML. If you don't know what XML is, just think of HTML, but more mundane. A basic SLS XML file looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE settings PUBLIC
 "-//Ubuntu//DTD jobservice Service-Level Settings 0.8//EN"
 "http://people.ubuntu.com/~jpeddicord/SLS/0.8/sls.dtd">
<settings>
    <setting name="setting_name" type="setting_type">
        <description>Some descriptive name</description>
        <data>
            <parse file="some_filename">%n=%s</parse>
        </data>
        <values>
            <value name="true">
                <raw>1</raw>
                <description>A description for this value</description>
            </value>
            <value name="false">
                <raw>0</raw>
            </value>
            ...
        </values>
    </setting>

    ...
</settings>

Let's break this down a little. The first four lines are standard XML practice: the XML and DOCTYPE tags. Always keep these present in your file. Nothing bad should happen with jobservice if they're not there, but the standards police may arrive on your doorstep and take you away.

Next up is the <settings> element. This must be present or nothing's going to read your file.

Inside the <settings> tag is a <setting> (singular) tag. Any number of <setting> tags can be present inside <settings>. A setting tag has two required attributes: name and type. name must be a unique name for this setting within the file. type tells jobservice what it should be expecting. We'll talk more about types shortly.

Inside an individual <setting> tag are three elements: <description>, <data>, and <values>. Only <description> is actually required, meaning this is valid:

<setting name="group_warning" type="label">
    <description>The following settings may break things.</description>
</setting>

That's usually not very useful, though, as it doesn't really do anything. (It's really only for the label type, which I'll describe later.)

<data> tells jobservice where it can find and save the actual settings it changes. This will be covered in the Data section below.

<values> describes possible values a particular setting can take. Covered in the Setting Values section.

Data

Single File

The data section tells jobservice where to look for the setting value and where to store it when it changes. 90% of the time, it will look something like this:

<data>
    <parse file="/etc/config/file">SOME_OPTION = %s</parse>
</data>

This tells the parser to look in /etc/config/file for a line that has SOME_OPTION =, followed by the actual value it will pick up. So, if that file has SOME_OPTION = sega in it, then the value returned by jobservice will be sega. When the user changes this value, it is written back to the file in the same place.

Keep in mind that values are only parsed on a single line. If you need multiple line support, you may want to use a helper script (see External Helpers).

At this point, unless your configuration is more complex, you may want to skip the rest of this section and continue to Setting Values.

Multiple Locations

Multiple <parse> tags are accepted. In apache2, for example, changing the server port requires it to be configured in multiple locations. Such a setup could be written as the following (simplified, this isn't actually correct for apache):

<data>
    <parse file="/etc/apache2/ports.conf">Listen %s</parse>
    <parse file="/etc/apache2/sites-available/000-default.conf">VirtualHost *:%s</parse>
</data>

With the above, when a value is changed, it is written to both of those files. (Note that jobservice only reads the value from the first, however.)

External Helpers

There are very rare cases where simply writing to a file is not enough. For that, there is the ability to call on external programs or helper scripts to fetch and set the value:

<data>
    <parse get="/usr/lib/jobservice/my-helper">SomeSetting %s</parse>
    <parse set="/usr/lib/jobservice/my-helper">SomeSetting=%s</parse>
</data>

This calls my-helper and parses STDOUT for SomeSetting %s as is done for normal files. When saving, my-helper is called with STDIN set to SomeSetting=%s (with %s replaced with the value, of course), again much like how things are written with normal files. The scripts given in get/set must have absolute paths or jobservice may not be able to find them.

Other than that, there are no real limitations: you can use extra arguments to scripts, get and set can be different programs, and you can even mix this with regular files as in the above examples. These are all rather advanced, however, and using regular files should cover most use cases.

Additionally, any instance of %j in the get/set attributes or parse body will be replaced with the full job name. This can be useful when multiple instances of a job use the same SLS file and you need to differentiate between them.

The "%n" shortcut

One very handy shortcut you can use inside <parse> tags is %n, which is replaced with the name of the setting before parsing. So:

<setting name="MySetting" type="str">
    <data>
        <parse file="/some/file">MySetting = "%s"</parse>
    ...

is identical to:

<setting name="MySetting" type="str">
    <data>
        <parse file="/some/file">%n = "%s"</parse>
    ...

This can make your setting files more concise and reduce the work needed to write them (even if just by a small amount).

Static values

Lastly, a data tag can also have a fixed value. This is really only useful for the exec type, but you may find other uses for it:

<data val="gufw" />

In this case the tag is self-closing. As previously mentioned, if you're using a label type, you can omit the <data> tag entirely.

Setting Values

The <values> tag is a tricky thing to get, but is a very powerful tool to use once you know how to wield it. Similar to <settings>, <values> contains any number of <value> tags, including none at all.

The usage of a <value> tag varies greatly with the setting type (see the Type Reference), but I'll give you a basic overview here. Let's start by example:

<values>
    <value name="true">
        <raw>1</raw>
    </value>
    <value name="false">
        <raw>0</raw>
    </value>
</values>

This is common to see for a bool type. Basically, this states that when the value for this is determined to be true (i.e. the checkbox in the interface is active), then a value of 1 will be written to the file (see Data for information on how). When the value is false, a 0 is written.

The key point to understand here is that the value parser is a translator between the user and the raw file data. The text in the name attribute is what is sent to and received from the client, and the raw tag text is what is written to or read from the file. Any value sent from the user or read from a file can be translated. This allows you to do some neat things, but also very nasty things. While you could translate str values from the user, this would be a very bad thing to do.

We'll call this value translation in other parts of this document.

<description> can be used inside value tags when necessary (depends on the type):

<value name="two">
    <description>Second item</description>
    <raw>2</raw>
</value>

When writing value sections, be sure to pay close attention to what is needed for the type of setting you are using. Don't stray from that and you should be fine.

Value Constraints

Values allow some constraints to be suggested. This is the responsibility of the client program to implement, and can even be ignored -- but that might compromise the stability of your system if an invalid value is entered.

Constraints are just attributes on the main <values> tag:

<values min="80" max="600" />

The above would be useful for an int setting. Note that constraints can be used regardless if there are values present or if the tag is empty (and thus self-closing, as in this example).

Again, pay attention to what constraints are available for the specific type you are working with and you should have no problems.

Type Reference

This is the main portion of this document. Different types have different requirements, and types effect the display of information to the user in client programs. If you're looking for a specific type, just locate the relevant section and read the details for information on how to use it.

bool

A bool type is a simple true/false setting. This is represented in jobs-admin as a checkbox.

Values

true and false are the client values, and need to be translated into raw values for writing.

Example

<setting name="enabled" type="bool">
    <description>Enable this setting</description>
    <data>
        <parse file="/etc/myfile">SettingEnabled %s</parse>
    </data>
    <values>
        <value name="true">
            <raw>yes</raw>
        </value>
        <value name="false">
            <raw>no</raw>
        </value>
    </values>
</setting>

This creates a setting of type bool named enabled with a description of "Enable this setting." The data is stored in /etc/myfile in the format SettingEnabled %s. true is translated into yes (SettingEnabled yes is written) and false becomes no.

int/float

An int corresponds to an unsigned integer, and float corresponds to a decimal. jobs-admin renders these as GtkSpinButton widgets.

Values

It is not necessary to provide translated values for these types. However, constraints may be used on an empty <values> tag.

Constraints
min
Minimum value accepted.
max
Maximum value accepted.

Example

<setting type="int" name="Port">
    <description>Port</description>
    <data>
        <parse file="/etc/ssh/sshd_config">%n %s</parse>
    </data>
    <values min="0" max="65535"/>
</setting>

A setting named Port of type int is created. Its description is simply "Port." The value is written to /etc/ssh/sshd_config using the format %n %s (or: Port %s). A minimum value of 0 and a maximum of 65535 is set.

str

A str is a simple text string. jobs-admin renders this as a text box. If a client program isn't sure how to render another type, it may treat it as a str.

Value translation should almost never be used here.

Example

<setting type="str" name="server">
    <description>NTP Server</description>
    <data>
        <parse file="/etc/ntp.conf">%n %s</parse>
    </data>
</setting>

Hopefully simple enough: a str setting named server with a description of NTP Server. Stored in /etc/ntp.conf as server %s.

label

Perhaps the easiest setting to use, the label just displays some text. This cannot be configured by the user: it is for informational purposes.

Example

<setting type="label" name="some_info">
    <description>Careful: fire is dangerous.</description>
</setting>

This will insert "Careful: fire is dangerous." as a setting. Because settings are displayed in the order present in the XML, you can use this to group some settings or show additional information about a setting.

choice

A choice setting requires one of its values be selected. Rendered in jobs-admin as a combo box.

Values

Value translation isn't important here, as most clients will render the description instead of the client value. You can make the value name the same as the raw text or different; it shouldn't matter for most cases.

The <description> tag inside a <value> is strongly suggested here, as it will likely be rendered by the client:

<value name="some_choice">
    <description>Some choice option</description>
    <raw>choice_rawdata</raw>
</value>

Example

<setting type="choice" name="default_input_policy">
    <description>Default inbound policy</description>
    <data>
        <parse file="/etc/default/ufw">DEFAULT_INPUT_POLICY="%s"</parse>
    </data>
    <values>
        <value name="drop">
            <raw>DROP</raw>
            <description>Drop</description>
        </value>
        <value name="accept">
            <raw>ACCEPT</raw>
            <description>Accept</description>
        </value>
    </values>
</setting>

Renders a choice setting named default_input_policy. Stored in /etc/default/ufw in DEFAULT_INPUT_POLICY="%s". Two choices are available: Drop, and Accept. Selecting Drop will store DEFAULT_INPUT_POLICY="DROP" in the file, and DEFAULT_INPUT_POLICY="ACCEPT" for Accept.

file/dir

A file or dir setting points to a file or directory on the system. Normally, this is rendered as a GtkFileChooserButton in jobs-admin, however it may render as a textbox if the exist constraint is false.

Values

Value translation should not be used. However, constraints are available:

Constraints
exist
If true, requires that the selected file or directory already exists on the system. If false, any filesystem location may be specified, existing or not. Defaults to true.

Example

<setting type="dir" name="log_location">
    <description>Log location</description>
    <data>
        <parse file="/etc/someservice.conf">%n %s</parse>
    </data>
    <values exist="false" />
</setting>

Creates a dir setting named log_location, stored in /etc/someservice.conf as log_location %s. The specified location doesn't have to exist; which implies that it is created on-demand by the service.

user/group

Much like a choice setting, but using system users or groups instead of configurable choices.

Values

Value translation should not be used, but constraints are available:

Constraints
useid
If true, the client should send and recieve values as numerical user IDs. If false, user or group names should be used. Defaults to false.

Example

<setting type="group" name="SystemGroup">
    <description>System Group</description>
    <data>
        <parse file="/etc/cups/cupsd.conf">%n %s</parse>
    </data>
    <values useid="false" />
</setting>

A group setting named SystemGroup. Stored in /etc/cups/cupsd.conf as SystemGroup %s. Because useid is false, the entire <values> tag may be omitted.

exec

Suggests to the client program that another application should be run to manage this particular service. The client program is responsible for launching this process, in fact, it may choose not to show settings of this type at all. jobs-admin presents these as a description followed by a "Launch" button to start the application.

Uses a static value with the shell-interpreted command to launch:

<data val="someapplication" />

Alternatively, if values are provided, the client program should check for the existence of each, deferring to the next if one is not available.

Values

If not using a static value in the data section, use values to list the commands that should be run. If one is not available, the next will be presented, until there are no more options left.

Example

<setting type="exec" name="manage">
    <_description>Manage CUPS</_description>
    <data val="xdg-open 'http://localhost:631/'"/>
    <values>
        <value name="system-config-printer">
            <_description>Configure printers</_description>
        </value>
        <value name="xdg-open 'http://localhost:631/'">
            <_description>Launch web interface</_description>
        </value>
    </values>
</setting>

An exec setting named manage that suggests that the user launch system-config-printer to manage printers. If that is not available, the web interface will be suggested instead. Note that the action to launch is in the value name. The text of <raw> tags is not passed over D-Bus, so you need to specify the action in the name.

Translation

Because <description> tags contain text that is displayed to the user, this text may be translated. Translations must be shipped inside the SLS file using xml:lang notation for alternate languages:

<setting name="stuff" type="label">
    <description>Hello!</description>
    <description xml:lang="es_ES">¡Hola!</description>
    ...

The intltool suite is capable of generating translatable XML files from gettext .po files. If you can't support the translation infrastructure needed here, we may be able to ship the SLS definition and translations with jobservice itself. Read on.

Distribution

Once you've gotten your SLS definition written, working, and hopefully translated, it's time to ship it out to your users.

With your service

If you are able to, I recommend simply shipping the SLS definition with your system service. It won't introduce any additional dependencies, as it is just a single XML file that is loaded only when needed.

To do so, just have your installation script or package install your SLS file to /usr/share/jobservice/sls. So, if you want to ship your apache2.xml SLS, it should go in /usr/share/jobservice/sls/apache2.xml. If the sls directory doesn't already exist, it should be created.

When jobservice starts up, it will read in your SLS file and display your settings. That's it!

With jobservice

If, for some reason, you don't want to ship your SLS file with your service, we may be able to ship it with jobservice directly. This may be useful if you are unable to properly support the translation infrastructure needed for internationalizing SLS description tags. Email your SLS definition to me (my email is at the top of this document) or branch lp:jobservice and add your SLS definition to it for merging.

Your SLS file will then be shipped as a "default" set of settings. If you choose to ship these in your own service later, these will be automatically overriden. We'll add the description tags for translation.

End

If you've survived this far, congratulations! You are now an SLS guru. If you need me to look over your SLS file, need assistance, or just want to chat, feel free to email me, ping me on twitter (@jpeddicord), identi.ca (@jacob), or on irc.freenode.net (nick jacob). Other contact methods are on my website.

Keep tabs on jobservice and jobs-admin for updates and new features, as well!