convert-systemd-units 1 administrator commands nosh
convert-systemd-units convert systemd unit files into native service bundles
`system-control` convert-systemd-units --bundle-root root --escape-format escfmt --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 --escape-format option controls the escaping algorithm, according to the value of escfmt:

`systemd`

  This is the default escaping algorithm if no escaping algorithm is specified, and is the conventional systemd one that escapes `/` and `-`.

`account`

  This is an alternative algorithm where the escaped form is suitable for an account name in the system account database. It is usually paired with the --escape-instance option, where the parameter is both an unescaped account name and a name of something in the filesystem (e.g. a log directory to go with that account). It is usually paired with the --escape-prefix option, where the name is both an unescaped account name and a name of something in the filesystem (e.g. a command name).

  On most systems this algorithm is similar to the `systemd` algorithm except that it escapes `/`, `\`, `,`, `:`, and `@`. (e.g. `ossec@agentd-log` is converted to `ossec_aagentd-log`.)

  If the escaped form would be over the allowable length for a system account name, a method that truncates to initial letters and uses a base-36 encoded DJBT33 hash of the rest is used instead. For most operating systems, where `L_cuserid` and cuserid 3 are relics and the account database in fact has no account name field length limit, because it is a Berkeley DB file, this will not be the case.

### 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-terminal 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 `TTYType=` setting (defaulting to `true` in quirks mode) if `false` causes the generated bundle to not also set the `TERM7` environment variable when the `TTYPath=` setting is used.

- 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

- A real-world worked example of setting up and running a service with nosh

- How to convert systemd unit files to an s6 installation

- Converting systemd units in the nosh Guide

## Author

Jonathan de Boyne Pollard