Skip to content

Services

Syntax: service NAME { command = "/path/to/daemon ARGS" }

Service, or daemon, to be monitored and automatically restarted if it exits prematurely. Finit tries to restart services that die, by default 10 times before giving up and marking them as crashed. After which they have to be manually restarted with initctl restart NAME. The limits controlling this are configurable, see Service Options.

Tip

To allow endless restarts, see respawn

For daemons that support it, we recommend appending --foreground, --no-background, -n, -F, or similar command line argument to prevent them from forking off a sub-process in the background. This is the most reliable way to monitor a service.

However, not all daemons support running in the foreground, or they may start logging to the foreground as well, these are forking daemons and are supported using the same syntax as forking sysv services, by naming the file to watch with pidfile. There is an alternative that may be more intuitive, where Finit can also guess the PID file based on the daemon's command name:

service ntpd {
    description = "NTP daemon"
    type        = "forking"
    command     = "ntpd"
}

This example lets BusyBox ntpd daemonize itself. Finit uses the basename of the binary to guess the PID file to watch for the PID: /var/run/ntpd.pid. If Finit guesses wrong, name the file yourself with pidfile = "/path/to/file.pid".

The file belongs to the service: Finit reads it but does not create or remove it. That is the default, and pidfile-create = true is what asks Finit to write the file instead. The one exception is stale cleanup — if the service dies without removing its own pidfile (SIGKILL, OOM, segfault), and the file still names the just-reaped PID, Finit removes it before the next retry. This prevents daemons that refuse to start on an existing pidfile (e.g. dbus-daemon) from getting stuck in a crash-restart loop.

Example:

In the case of ospfd (below), we omit the -d flag (daemonize) to prevent it from forking to the background:

service ospfd {
    description = "OSPF daemon"
    runlevel    = "2345"
    conditions  = { "pid/zebra" }
    command     = "/sbin/ospfd"
}

runlevel denotes the runlevels ospfd is allowed to run in, it is optional and defaults to level 2-4 if omitted.

conditions lists what must be asserted before starting ospfd. In this example Finit waits for another service, zebra, to have created its PID file in /var/run/quagga/zebra.pid. Finit watches all files in /var/run, for each file named *.pid, or */pid, Finit opens it and finds the matching NAME:ID using the PID.

A condition may be prefixed with ~ to propagate a reload of the upstream service to this one, rather than merely pausing and resuming it:

conditions = { "~pid/zebra" }

If ospfd cannot be reloaded with SIGHUP at all, that is a property of ospfd and not of the condition, so it is said directly:

reload-signal = "none"

The legacy format spells that second one as a ! leading the condition list, which is not accepted here. For details, see the Finit Conditions document.

Some services do not maintain a PID file and rather than patching each application Finit provides a workaround. With pidfile-create Finit creates the file when starting and removes it when stopping. The path comes from pidfile, which takes three forms:

pidfile = true              # /var/run/<command basename>.pid
pidfile = "bar"             # a bare name, /var/run/bar.pid
pidfile = "/run/bar.pid"    # an explicit path

Such a file is also used by the Finit condition subsystem, so another service, run or task can depend on pid/bar. Here foo is not started until bar has:

service bar {
    description    = "Bar Service"
    pidfile        = "/run/bar.pid"
    pidfile-create = true
    command        = "bar"
}

service foo {
    description = "Foo Service"
    conditions  = { "pid/bar" }
    command     = "foo"
}

Needless to say, it is better if bar creates its own PID file when it has completed starting up and is ready for service.

As an alternative "readiness" notification, Finit supports both systemd and s6 style notification. This is enabled with the notify key:

  • notify = "systemd" -- tells Finit the service uses the sd_notify() API to signal PID 1 when it has completed its startup and is ready to service events. The sd_notify() API expects NOTIFY_SOCKET to be set to the socket where the application can send "READY=1\n" when it is starting up or has processed a SIGHUP.
  • notify = "s6" -- puts Finit in s6 compatibility mode. Compared to the systemd notification, s6 expect compliant daemons to send "\n" and then close their socket. Finit takes care of "hard-wiring" the READY state as long as the application is running, events across any SIGHUP. Since s6 can give its applications the descriptor number (must be >3) on then command line, Finit provides the following syntax (%n is replaced by Finit with then descriptor number):
    service mdevd {
        runlevel = "S12345789"
        notify   = "s6"
        command  = "mdevd -O 4 -D %n"
    }
    

When a service is ready, either by Finit detecting its PID file, or their respective readiness mechanism has been triggered, Finit creates then service's ready condition which other services can depend on:

$ initctl -v cond get service/mdevd/ready
on

This can be used to synchronize the start of another run/task/service:

task mdevd-coldplug {
    runlevel   = "S"
    conditions = { "service/mdevd/ready" }
    user       = "root"
    group      = "root"
    command    = "mdevd-coldplug"
}

Finit waits for mdevd to notify it, before starting mdevd-coldplug. Notice how both start in runlevel S, and the coldplug task only runs in S. When the system moves to runlevel 2 (the default), coldplug is no longer part of the running configuration (initctl show), this is to ensure that coldplug is not called more than once.

For a detailed description of conditions, and how to debug them, see the Finit Conditions document.

Non-privileged Services

Every run, task, or service can also list the privileges the command should be executed with, using user, group and extra-groups, all optional:

run hello {
    runlevel = "2345"
    user     = "joe"
    group    = "users"
    command  = "logger \"Hello world\""
}

Finit reads the user's supplementary group membership from /etc/group automatically. Any groups the user belongs to will be inherited by the service.

To specify additional supplementary groups beyond those in /etc/group, list them in extra-groups:

service caddy {
    user         = "caddy"
    group        = "caddy"
    extra-groups = { "ssl-cert" }
    command      = "/usr/bin/caddy run"
}

This runs the caddy service as user caddy, with primary group caddy, inheriting any groups caddy is a member of in /etc/group, plus the additional ssl-cert group. This is useful when a service needs access to resources owned by groups not listed in /etc/group.

For multiple instances of the same command, e.g. a DHCP client or multiple web servers, add :ID to the block title, like this:

service httpd:80 {
    description = "Web server"
    runlevel    = "2345"
    command     = "httpd -f -h /http -p 80"
}

service httpd:8080 {
    description = "Old web server"
    runlevel    = "2345"
    command     = "httpd -f -h /http -p 8080"
}

Without the :ID the latter will overwrite the former and only the old web server would be started and supervised.

Note

The line-based format also accepts a bare ID, service :80 ..., deriving the name from the command. There is no block equivalent: the title carries both name and ID.

Conditional Loading

Finit supports conditional loading of blocks. The following example is taken from the system/10-hotplug.conf file in the Finit distribution. Here we only show a simplified subset.

Starting with the udev daemon, which goes by two names depending on how it was built. Both are candidates for the same service:

service udevd {
    pidfile = "udevd"
    command = { "/lib/systemd/systemd-udevd", "-udevd" }
}

When loading the .conf file Finit looks for /lib/systemd/systemd-udevd, and if that is not there it moves on to udevd. A candidate that is not installed is expected, so no warning is logged for the ones that are skipped. The leading - on the last one says it is also fine if none of them are found, in which case the block is dropped quietly and no service named udevd exists.

Note

This needs to be one block. The title is the service identity, so two blocks titled udevd in the same file are two declarations of one service, which Finit rejects. See Duplicate titles.

run udevadm:1 { runlevel = "S" if = "udevd" conditions = { "pid/udevd" } command = "-udevadm settle -t 0" }

This block is only loaded if we know of a service named udevd. Again, we do not warn if udevadm is not found, execution will also stop here until the PID condition is asserted, i.e., Finit detecting udevd has started.

run mdev {
    description = "Populating device tree"
    runlevel    = "S"
    conflicts   = { "udevd" }
    command     = "-mdev -s"
}

If udevd is not available, we try to run mdev, but if that is not found, again we do not warn.

Conditional loading can also be negated, so the previous block can be written as:

run mdev {
    description = "Populating device tree"
    runlevel    = "S"
    if          = "!udevd"
    command     = "-mdev -s"
}

The reason for using conflicts in this example is that a conflict can be resolved. Blocks naming a conflict are rechecked at runtime.

Conditional Execution

Similar to conditional loading of blocks there is conditional runtime execution. This can be confusing at first, since Finit already has a condition subsystem, but this is more akin to the qualification to a runlevel. E.g., a task with runlevel = "123" is qualified to run only in runlevel 1, 2, and 3. It is not considered for other runlevels.

Conditional execution qualify a run/task/service based on a condition. Consider this (simplified) example from the Infix operating system:

run startup {
    runlevel   = "S"
    conditions = { "pid/sysrepo" }
    command    = "confd -b --load startup-config"
}

run failure {
    runlevel   = "S"
    if         = "usr/fail-startup"
    conditions = { "pid/sysrepo" }
    command    = "confd --load failure-config"
}

The two run blocks reside in the same .conf file so Finit runs them in true sequence. If loading the file startup-config fails confd sets the condition usr/fail-startup, thus allowing the next one to load failure-config.

Notice the critical difference between the conditions list and if. The former is a condition for starting; the latter is a condition to check whether a run/task/service is qualified to even be considered. if has a negation of its own, !, which is unrelated to anything in the conditions list.

What if compares against depends on the value. A namespace separator makes it a condition, anything else is a service name:

if Asks
"udevd" is a service by this name known?
"usr/foo" was this condition set?

Both are questions about whether the block belongs in the running configuration at all, usually answered from what bootstrap established. A statement is all of one kind or the other, so the block is rejected if you mix them.

Note

if qualifies, it does not track. A condition asserted or cleared later does not start or stop the service by itself -- that is what the conditions list is for.

Conditional execution can also be negated, so provided the file loaded did the opposite, i.e., set a condition on success, the previous block can be written as:

run failure {
    runlevel   = "S"
    if         = "!usr/startup-ok"
    conditions = { "pid/sysrepo" }
    command    = "confd ..."
}

Variants of one service are often qualified this way, one per platform, and they usually have to supply the same barrier to whatever waits for them. Each variant needs its own title, since a title is an identity, and the shared barrier is named with provides. See Provided Conditions.