Coordinate Scripts With systemd

Author: akeil
Date: 2013-12-14
Version: 1

Use systemd to execute scripts, controlling the order of execution.

Assuming we have:
  • a single preparation unit - prep.service
  • multiple "task" units - task-N.service
  • a single cleanup script - done.service

We want to run the prep unit once, then run all our task units and finally the done unit once.

Defining dependencies between tasks is relatively simple when each task is represented by a systemd unit [1]. Dependencies are defined using Requires= or Wants= and order of execution is defined with After= and Before=.

The only thing left is to make sure that the "preparation" and "finish" steps are executed only once, even if multiple tasks are run (and not once for each task).

To achieve this a .target is created and all tasks are associated to that target.

Note

Steps:
  1. Create .service files for the commands that must be executed before
  2. Create .service for commands that should be executed after
  3. Create as single .target for all tasks
  4. Create a .service for each task, make it WantedBy= the target.

Target Unit

A systemd target [2] is used to group several units together.

The target file goes into /etc/systemd/system and looks like this:

# tasks.target ---------------------------------------
[Unit]
Description = Tasks Target

The target can be started manually with:

# systemctl start tasks.target

The task units are associated with the target using WantedBy=.

This means, if we enable one or more task units with

# systemctl enable task-1.service task-2.service

... starting the tasks.target will start all of the associated units.

This allows us to start all tasks with a single invocation which in turn means that we execute any number of tasks and the prep and done units only once.

Task Units

Each task unit specifies the prep.service as a precondition using Requires= and After=. It also specifies the done.service using Wants= and Before= to have it run after the tasks.

# task.service ---------------------------------------
[Unit]
Description = A task unit
Requires = prep.service
After = prep.service
Wants = done.service
Before = done.service

[Service]
User = someuser
Group = somegroup
Type = oneshot
ExecStart = /usr/local/bin/script.sh

[Install]
WantedBy = tasks.target

The Requires=prep.service directive means that the task is not started if the prep.service failed. Using Wants=prep.service would try to start the prep unit first but continue with the tasks regardless of whether preparation was successful or not.

The User= and Group= properties can be used to run the command under a different user than root.

Not all tasks have to specify the same dependencies. It is also possible that tasks depend on each other.

Prep and Done Units

The prep.service and the done.service are plain units and do not define any dependencies.

the prep unit should be configured with Type=oneshot to have it complete before the first task can be started.

Service Type

By default, Before= and After= only control the order in which services are started. To make sure that the prep unit completes before the tasks are started, use Type=oneshot in the [Service] definition.

From the systemd service [3] documentation:

Behavior of oneshot is similar to simple; however, it is expected that the process has to exit before systemd starts follow-up units.

The same goes for the task units if the done unit should only be executed after the tasks are complete.