Name

convert-systemd-units — convert systemd unit files into native service bundles

Synopsis

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 }

Note

This is generally only accessible as a subcommand of system-control(1). It is referred to without qualification in this manual for simplicity.

Description

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.

Service, socket, timer, and target units

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 name.service is specified, then that service unit file is converted into a service bundle directory named name/. Snippet files are in name.service.d/snippet.conf.

  • If name@parameter.service is specified, then the name@.service service unit file is converted using parameter for parameter substitution into a service bundle directory named name@parameter/. Snippet files are in name@.service.d/snippet.conf and name@parameter.service.d/snippet.conf.

  • If name.target is specified, then that target unit file is converted into a target bundle directory named name/. Snippet files are in name.target.d/snippet.conf.

  • If name@parameter.target is specified, then the name@.target target unit file is converted using parameter for parameter substitution into a target bundle directory named name@parameter/. Snippet files are in name@.target.d/snippet.conf and name@parameter.target.d/snippet.conf.

  • If name.timer is specified, then it is combined with a name.service service unit file to make a service bundle directory named name/. Snippet files are in name.timer.d/snippet.conf and name.service.d/snippet.conf.

  • If name@parameter.timer is specified, then the name@.timer timer unit file is combined with a name.service service unit file and converted using parameter for parameter substitution into a service bundle directory named name@parameter/. Snippet files are in name@.timer.d/snippet.conf, name@parameter.timer.d/snippet.conf, and name.service.d/snippet.conf.

  • If name.socket is specified, and it has Accept=true, then it is combined with a name@.service service unit file to make a service bundle directory named name/. Snippet files are in name.socket.d/snippet.conf and name@.service.d/snippet.conf.

  • If name.socket is specified, and it has Accept=false, then it is combined with a name.service service unit file to make a service bundle directory named name/. Snippet files are in name.socket.d/snippet.conf and name.service.d/snippet.conf.

  • If name@parameter.socket is specified, and it has Accept=true, then the name@.socket socket unit file is combined with a name@.service service unit file and converted using parameter for parameter substitution into a service bundle directory named name@parameter/. Snippet files are in name@.socket.d/snippet.conf, name@parameter.socket.d/snippet.conf, and name@.service.d/snippet.conf.

  • If name@parameter.socket is specified, and it has Accept=false, then the name@.socket socket unit file is combined with a name.service service unit file and converted using parameter for parameter substitution into a service bundle directory named name@parameter/. Snippet files are in name@.socket.d/snippet.conf, name@parameter.socket.d/snippet.conf, and 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.

Escaping

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/.)

The limits of automated convertability

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).

Ideal and quirks mode

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.

Extensions to the systemd unit syntax

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=user and RuntimeDirectoryGroup=group settings specify the owning user and group of the runtime directory.

  • The AfterMountsFor=directories setting defines an 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=directories setting defines an 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.

See also

system-control(1)

service bundles, and other subcommands

Further reading

Author

Jonathan de Boyne Pollard