There are two basic approaches.
Compile-time configuration
This is the approach taken by many software packagers.
One uses macros. At package generation time, some form of macro preprocessor is run over a macro-ized service unit file, with parameters that represent the configuration. The output, then put into the package, is a tailored service unit file.
An example is in systemd itself. The kmod-static-nodes.service.in
file contains macros:
ExecStart=@KMOD@ static-nodes --format=tmpfiles --output=/run/tmpfiles.d/static-nodes.conf
The large collection of Python scripts that build systemd (named "Meson") contain amonst many other things a regular-expression-based macro preprocessor. This preprocessor is run on kmod-static-nodes.service.in
, replacing the KMOD
macro to produce the kmod-static-nodes.service
that goes into the package. The replacement is a string that is the path to the kmod
executable program image file, searched for at compile time by the Python scripts. (I'm not going to go into details of the large Python script system, as that's well beyond the scope of this answer.)
The disadvantage of this scheme is that one has to create the package on a machine that is laid out the same way as, and contains the same programs as, the target machine on which the package is installed. Every operating system's packages could be different (even from version to version).
Install-time configuration
drop-ins
Another approach would be to have a static service unit file that is configured at install time, by generating drop-in .conf
files that adjust the service unit to the target system. They are not ideal, as of course they are exported to the service process, but environment variables can be used for this.
For example: A static service unit file /usr/lib/systemd/system/wibble.service
could say
ExecStart=/usr/bin/wibble $OPTIONS
The package maintenance script for the install action then creates a /usr/lib/systemd/system/wibble.service.d/20-options.conf
file on the fly at install time containing the tailored options calculated on the current machine at install time rather than on a different machine at package creation time:
Environment=OPTIONS=wobble --jelly -o plate
The disadvantage of this scheme is that the package maintenance script for the deinstall action has to remember to remove this file if the package is being completely purged, and one has to remember to hook this into the configure action so that system administrators can explicitly force regeneration of the drop-in file if they reconfigure or rearrange stuff.
preprocessing
A variation on the aforementioned is to go back to macro preprocessing. Actually ship the macro-ized service unit file, and preprocess it at install time.
If one is not making a package at all, but just shipping everything in a ustar archive for system administrators to install by hand, this is more workable than generated drop-ins.
The disadvantage of this is that macro preprocessing systems like the one in Meson are tightly coupled to the Python script collection and not available as standalone tools. For standalone preprocessing tools one has the likes of m4
, cpp
, and others, none of which are a good fit for pre-processing .INI files and making the sorts of decisions (e.g. "Detect what options the wibble
program takes on this machine." "Where is kmod
on this machine?") that are usually involved.
There is clearly a niche for a tool that no-one has spotted, here — that I have heard of, at any rate.
One can sort-of make do with command -v
to do the path lookup and ex
to do the replacements, in a shell script; but it's non-trivial to (and most people do not) harden that against things like spaces and metacharacters in pathnames, or allow for an escaping mechanism to prevent macro expansion where it is not desired.
Perl has somewhat safer string processing than shell script, but not every operating system has Perl out of the box (c.f. "Is there a unix-like system that doesn't come with Perl?"), necessitating that system administrators be told to ensure that Perl is installed first. Python obviously can do this, given systemd's large Python script collection build system. But then there's the problem of system adminstrators going and changing the version of Python on you, and pulling the rug out from under you (c.f. "apt-get update no longer works after rebuilding corrupted PATH variable" and all of the Ask Ubuntu questions hyperlinked-to there). Other possibilities include TCL.
On the gripping hand …
Sometimes such configurability is unnecessary in the first place. The @KMOD@
macro in systemd is not actually necessary. This (nowadays) would work:
ExecStart=kmod static-nodes --format=tmpfiles --output=/run/tmpfiles.d/static-nodes.conf
Turning a database table (of some sort) into service units? Ship a generator.
Specifying a default enable/disable state? Ship presets.