6

I want to publish an app just should be registered as systemd service.

Alongside the app, I want to provide a script to register the app as a systemd service.

So currently, I publishing the app with a myapp.service template file, and a script that copies the myapp.service and updates his values.

But I don't like this solution.

Can I create a .service file from the command-line? Something like systemd new -name myapp -description "my description" -after network.target -exec path/to/exec?

2

3 Answers 3

7

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.

2

The systemd-run can be used to start a new systemd service from the command line:

systemd-run [OPTIONS...] {COMMAND} [ARGS...]

see systemd-run --help and man systemd-run

It is not persistent systemd service, you need to write the unit file.

Example from RHEL docs: Starting a New Service with systemd-run

systemd-run --unit=toptest --slice=test top -b

The unit file will be written under /run/systemd/transient/ as toptest.service.

2
  • 4
    "you need to write the unit file" that you gloss over is the very part that the questioner is asking about: how to generate a unit file whose contents vary from system to system.
    – JdeBP
    Commented Feb 23, 2020 at 10:13
  • Requests to add an option to persist the unit file are marked as a duplicate of each other; the one currently open is ignored for 5.5 years...
    – Nickolay
    Commented May 14 at 13:05
0

To include the template inside your shell script, you can use heredoc. For your example systemd new -name myapp -description "my description" -after network.target -exec path/to/exec:

sudo tee /etc/systemd/system/myapp.service <<EOF
[Unit]
Description=my description
After=network.target
[Service]
ExecStart=path/to/exec
[Install]
WantedBy=multi-user.target
EOF

In bash the contents of the heredoc are subject to the usual expansion rules (i.e. you can use $VARIABLES and $(subshells)), making for easy templating.

To fit it into one line, you can do (found here):

printf '%s\n' [Service] "ExecStart=path/to/exec" [Unit] "After=network-online.target" [Install] WantedBy=multi-user.target | sudo tee /etc/systemd/system/myapp.service

...which is admittedly still a bit unwieldy.

In any case don't forget to:

sudo systemctl daemon-reload
sudo systemctl enable myapp.service
sudo systemctl start myapp.service

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .