convert-systemd-units — convert systemd unit files into native service bundles
system-control
{convert-systemd-units} [--bundle-root root
] [--alt-escape] [--etc-bundle] [--local-bundle] [--supervise-in-run] [--escape-instance] [--escape-prefix] [--no-systemd-quirks] [--no-generation-comment] { name
.target | name
@parameter
.target | name
.socket | name
@parameter
.socket | name
.timer | name
@parameter
.timer | name
.service | name
@parameter
.service }
This is generally only accessible as a subcommand of system-control(1). It is referred to without qualification in this manual for simplicity.
This subcommand of system-control(1) takes some systemd unit files and generates from them a service bundle in the current directory (or in the root
directory if the --bundle-root command-line option is used) that contains scripts, dependencies, and autoboot dependency configuration information.
The bundle is not enabled by the conversion process, but can be started and enabled as it stands, with the start and preset subcommands, just like any other service bundle.
The systemd unit files are determined from the argument. They comprise the main unit file and a collection of snippet files in a related subdirectory whose contents are applied on top of it before conversion.
If
is specified, then that service unit file is converted into a service bundle directory named name
.service
.
Snippet files are in name
/
.
name
.service.d/snippet
.conf
If
is specified, then the name
@parameter
.service
service unit file is converted using name
@.serviceparameter
for parameter substitution into a service bundle directory named
.
Snippet files are in name
@parameter
/
and name
@.service.d/snippet
.conf
.
name
@parameter
.service.d/snippet
.conf
If
is specified, then that target unit file is converted into a target bundle directory named name
.target
.
Snippet files are in name
/
.
name
.target.d/snippet
.conf
If
is specified, then the name
@parameter
.target
target unit file is converted using name
@.targetparameter
for parameter substitution into a target bundle directory named
.
Snippet files are in name
@parameter
/
and name
@.target.d/snippet
.conf
.
name
@parameter
.target.d/snippet
.conf
If
is specified, then it is combined with a name
.timer
service unit file to make a service bundle directory named name
.service
.
Snippet files are in name
/
and name
.timer.d/snippet
.conf
.
name
.service.d/snippet
.conf
If
is specified, then the name
@parameter
.timer
timer unit file is combined with a name
@.timer
service unit file and converted using name
.serviceparameter
for parameter substitution into a service bundle directory named
.
Snippet files are in name
@parameter
/
, name
@.timer.d/snippet
.conf
, and name
@parameter
.timer.d/snippet
.conf
.
name
.service.d/snippet
.conf
If
is specified, and it has name
.socketAccept=true
, then it is combined with a
service unit file to make a service bundle directory named name
@.service
.
Snippet files are in name
/
and name
.socket.d/snippet
.conf
.
name
@.service.d/snippet
.conf
If
is specified, and it has name
.socketAccept=false
, then it is combined with a
service unit file to make a service bundle directory named name
.service
.
Snippet files are in name
/
and name
.socket.d/snippet
.conf
.
name
.service.d/snippet
.conf
If
is specified, and it has name
@parameter
.socketAccept=true
, then the
socket unit file is combined with a name
@.socket
service unit file and converted using name
@.serviceparameter
for parameter substitution into a service bundle directory named
.
Snippet files are in name
@parameter
/
, name
@.socket.d/snippet
.conf
, and name
@parameter
.socket.d/snippet
.conf
.
name
@.service.d/snippet
.conf
If
is specified, and it has name
@parameter
.socketAccept=false
, then the
socket unit file is combined with a name
@.socket
service unit file and converted using name
.serviceparameter
for parameter substitution into a service bundle directory named
.
Snippet files are in name
@parameter
/
, name
@.socket.d/snippet
.conf
, and name
@parameter
.socket.d/snippet
.conf
.
name
.service.d/snippet
.conf
Files found upon opening to be other than regular files are errors.
snippet
files that begin with a dot are skipped, and not opened, per the usual convention.
There is no defined ordering for snippet files relative to one another.
The snippet subdirectory is required to be in the same location as the associated unit files.
Unit files can reference other services.
The assumption is that all service and target bundles are under one common root
in umbrella service
and target
subdirectories; so dependencies and orderings can be relative pathnames.
The --local-bundle option changes this assumption, such that service and target bundles are assumed to not be reachable with relative pathnames, and absolute pathnames to subdirectories of /var/service-bundles/services/
and /etc/service-bundles/targets/
are used.
The --supervise-in-run option handles the case for special early bootstrap service bundles that live in /etc/service-bundles/services/
, defaulting the bundle to be marked as an EarlySupervise
bundle whose supervise/
directory lives in a tmpfs.
The systemd dual escaped+unescaped forms are available in parameter substitution.
Normally parameter
is taken to be the escaped form and is unescaped during parameter substitution.
This is generally used where parameter
represents something in the filesystem, but has had to be pre-escaped in order to result in a service name without pathname components.
If the --escape-instance option is used, then parameter
is instead taken to be the original, unescaped, form and will be escaped into its service name form.
Normally name
is taken to be the escaped form and is unescaped during parameter substitution.
If the --escape-prefix option is used, then name
is instead taken to be the unescaped form and will be escaped into its service name form.
The normal escaping algorithm is the conventional systemd one that escapes /
and -
.
If the --alt-escape option is used, then an alternative escaping algorithm that escapes /
, \
, ,
, :
, and @
is used instead.
The --alt-escape and --escape-instance options are generally used together, such that the parameter
is the unescaped service bundle directory name and the escaped form is (for example) an account name in an account database.
(e.g. The account name ossec_aagentd-log
being the scaped form of the parameter
in cyclog@ossec@agentd/
.)
Conversion of every possible systemd unit file to a service bundle requires a human being. This subcommand converts systemd units that fall within certain bounds, which (given that most services are fairly simple) should be the majority of systemd units in existence. These bounds are:
The service must be a simple, forking, oneshot, or dbus service. Notify services are not converted. For best results, avoid having forking services in the first place, and change them into simple services wherever possible. service-manager(1) has no dealings with a Desktop Bus, so dbus services are treated as simple services.
Only TCP, UDP, local domain datagram, local domain stream, local domain sequential packet, FIFO, and netlink datagram sockets are converted.
The run
/service
script makes use of udp-socket-listen(1), tcp-socket-listen(1), local-datagram-socket-listen(1), local-stream-socket-listen(1), local-seqpacket-socket-listen(1), fifo-listen(1), netlink-datagram-socket-listen(1), tcp-socket-accept(1), local-stream-socket-accept(1), and local-seqpacket-socket-accept(1) to spawn services, either to listen to the accepting socket or to respond on connecting sockets.
Some of the more esoteric mechanisms, and any undocumented settings, in systemd are not converted. The converter will print a warning for any setting in the unit files that it does not use for conversion. These will include:
Specialized options for conditionally enabling the service, such as conditionpathexists
and conditiondirectorynotempty
, are not converted.
These are almost always in practice used to make services run on specific platforms and not on others, or dependent from the presence of installed packages, and should be replaced with hooks into the preset mechanism.
Other highly specialized options that even systemd recommends against, such as fsckpassno
, are not converted.
Specialized options for TTY management, such as standardinput=tty-force
, are not converted.
Instead of these, employ the vc-get-tty(1), open-controlling-tty(1), and related utilities in the run
script.
Only the SIGHUP
substitute for SIGTERM
via killsignal
is converted.
In practice, this is the primary use case in services that are interactive login shells.
Linux-only mechanisms such as capabilityboundingset
and I/O scheduling are not converted.
This is merely down to a lack of known chaining commands for manipulating those mechanisms.
Attempts to use %m
are intentionally ignored.
Conversion does not necessarily happen on the machine where the service bundle will actually end up being used, and hardwiring machine IDs into service bundles creates a problem with bundle portability.
Machine IDs should be obtained at runtime with machineenv(7), and careful thought should be given to whether the service should even be employing a machine ID at all.
See machine-id(7).
Import can operate in either "ideal" mode or "quirks" mode. The default is quirks mode, which is the better mode for real systemd unit files. It replicates several quirks of systemd:
It was undocumented for a long time that systemd always sets the HOME
, USER
, and LOGNAME
environment variables whenever a User=
directive says to change the user account.
To this, quirks mode adds the SHELL
environment variable, even though systemd has not customarily implicitly set that variable too.
Ideal mode does not set these environment variables implicitly, since dedicated user accounts for running services do not necessarily have home directories and suchlike.
A User=
directive specifies setting all of the supplementary groups for a user account in addition to its primary group.
The daemontools convention is to just set the primary group when switching to the dedicated unprivileged account for a service, which is the case in ideal mode.
It's not documented, but systemd always changes directory when it starts a service.
It explicitly changes to the root directory when no WorkingDirectory=
directive is given.
The daemontools convention is for services to be run in their service directories, which is the case in ideal mode.
A small number of dæmons have been found to rely upon the undocumented systemd behaviour of being in the root directory.
A fair few directives can occur in both a socket unit or its associated service unit.
The default behaviour in systemd in the absence of a Restart=
directive is to never auto-restart a service.
The daemontools convention is for services to always auto-restart, this having been the norm since the 1990s; and this is the default in ideal mode.
For an Accept=no
(i.e. listening) socket service, systemd implicitly applies any redirection that is applied to standard output to standard error as well.
So (say) StandardOutput=socket
attaches both standard output and standard error to the listening socket.
This coupling is usually not what one wants.
The UCSPI and daemontools norm is for standard error to be independent from standard output and to require its own explicit redirection, which is the case in ideal mode.
When one is using StandardInput=tty
, systemd requires that standard output and error be explicitly directed to the terminal device.
Usually, terminal login services being a very common example of this, one wants all three directed to a terminal.
In ideal mode, the redirection of standard input implies redirecting standard output and error as well.
(Standard error can be retained at its original place with the StandardError=log
extension.)
The --no-systemd-quirks command line option turns off these systemd quirks and sets "ideal" mode. Alternatively, one can turn off some quirks with various unit file directives:
The systemdWorkingDirectory=
setting (defaulting to true
in quirks mode) if false
causes the generated bundle to not bother changing directory to the root.
Set this on services and targets where the dæmon program is happy to run in the service
directory.
The systemdUserEnvironment=
setting (defaulting to true
in quirks mode) if false
causes the generated bundle to not bother setting the user environment variables.
Set this on services and targets where the dæmon program has no need for the HOME
, USER
, LOGNAME
, or SHELL
environment variables.
This is the case for most dæmon programs.
The systemdUserGroups=
setting (defaulting to true
in quirks mode) if false
causes the generated bundle to not bother setting the suplementary user groups.
Set this on services and targets where the dæmon program has no need for supplementary group access.
This is the case for almost all dæmon programs.
There are several extensions to the systemd unit specification provided by the conversion process, to allow a unit file to specify things that systemd doesn't have but that service bundles have:
The ExecStart=
setting does not require that the program be named with an absolute pathname, which systemd requires with a very limited set of exceptions.
Generated run
scripts will use nosh(1), which searches the PATH
.
(systemd does not search PATH
, and only searches in a fixed, hardwired, set of directories.)
This makes it possible, for example, for a single service unit file to just say ExecStart=cupsd
without needing to have two different unit files, one saying ExecStart=/usr/sbin/cupsd
for Linux systems and one saying ExecStart=/usr/local/sbin/cupsd
for BSD systems.
The LimitMemory=
setting translates to the -m option of softlimit(1), for setting multiple "memory" resource limits in one go.
The MachineEnvironment=
setting (defaulting to false
) if true
causes the generated bundle to set the machine environment variables with machineenv(1).
The EnvironmentAppendPath=
setting acts like Environment
except that the specified environment variables are augmented with appendpath(1) rather than replaced with setenv(1).
The EnvironmentDirectory=
setting specifies a directory to be read by envdir(1).
Set this on a service that is configured via an envdir
environment directory.
The FullEnvironmentDirectory=
setting specifies that the --full command line option to envdir(1) be applied when reading the environment directory.
(This is not usually applicable.)
The EnvironmentUserOnly=
setting (defaulting to false
) if true
causes the generated bundle to call envuidgid(1) but not setuidgid(1).
Set this on a service where the main dæmon program changes its own process UID and GID, but expects to be told via environment variables what UID and GID to run as.
The EarlySupervise=
setting (defaulting to true
for "etc" bundles and false
for others) if true
causes the generated bundle to use a supervise
directory in /run/service-bundles/early-supervise/
.
This is for avoiding access to /var
before it is mounted, during the bootstrap process, and is mainly used for "sysinit" services and system targets that are in /etc
.
The StoppedBy=
setting defines a stopped-by
relationship to other services, stored in the generated bundle.
This is the inverse of the conflicts
relationship.
By default, unless the DefaultDependencies=false
setting is used, services are stopped by the "shutdown" target in addition to anything specified in this setting.
The JailID=
setting specifies the ID of a jail in which to run the service, as if by jexec(1).
The FIB=
setting specifies the FIB number (routing table number) for the service, as if by setfib(1).
The ProcessGroupLeader=
setting (defaulting to false
) if true
causes the generated bundle to run the dæmon as a process group leader with setpgrp(1).
The SessionLeader=
setting (defaulting to false
) if true
causes the generated bundle to run the dæmon as a session leader with setsid(1).
The TTYPrompt=
setting (defaulting to false
) if true
causes the generated bundle to invoke login-prompt(1).
The BannerLine=
setting causes the generated bundle to invoke line-banner(1) to write the specified banner line before dæmon invocation.
The BannerFile=
setting causes the generated bundle to invoke login-banner(1) to process the specified banner file(s) before dæmon invocation.
The ExecRestartPre=
setting allows one to specify extra commands to be run in the (automatically generated) restart
script.
The UCSPIRules=
setting (defaulting to false
) applies ucspi-socket-rules-check(1) to all accepted connections.
The LogUCSPIRules=
setting (defaulting to false
) causes ucspi-socket-rules-check(1) to print information about access failures.
The NUMAInterleave=
, NUMAMemBind=
, NUMACPUNodeBind=
, NUMAPhysCPUBind=
, NUMALocalAlloc=
, and NUMAPreferred=
settings cause the generated bundle to invoke numactl(1).
The JVMVersions=
, JVMOperatingSystems=
, and JVMManufacturers=
settings cause the generated bundle to invoke find-matching-jvm(1).
Each setting's value is a space-separated list, which is turned into zero or more equivalent command-line options.
The JVMDefault=
setting (defaulting to false
) if true
causes the generated bundle to invoke find-default-jvm(1).
The StandardError=log
setting prevents StandardInput=tty
from redirecting standard error.
The RuntimeDirectoryOwner=
and user
RuntimeDirectoryGroup=
settings specify the owning user and group of the runtime directory.
group
The AfterMountsFor=
setting defines an directories
after
relationship to the mount@
services for each directory
and its parents up to but not including the root directory, stored in the generated bundle.
The WantsMountsFor=
setting defines an directories
wants
relationship to the mount@
services for each directory
and its parents up to but not including the root directory, stored in the generated bundle.