doc: convert the documentation to the block format

The syntax overview no longer describes a line-based format, since that
is not what the rest of the documentation shows.  It now covers the
grammar, the two naming conventions, the nine aliases, and the leading
'-' on a path, and it says plainly that both formats are still read and
told apart per file by content.  Without that, a reader with an
existing configuration is left wondering what happened to it.

service-opts.md was a list of modifiers to place between a directive
and its command, so it needed rewriting rather than translating: there
are no positions left to describe.  It is now grouped by what the
settings do.

conditions.md needed correcting.  It presented '!' as a condition
prefix alongside '~'.  It is neither a condition nor a negation, it is
a flag on the block that means one thing on a service and another on a
run or task, so it is spelled reload-signal and required here, and the
page maps the old form to both.

Two things the pages claimed are not true.  The kill delay range is
1-300, not 1-60, and stop and reload scripts are no longer run without
a timeout.

ChangeLog.md keeps its line-based examples.  Those sit in historical
release entries, and rewriting them in a syntax that did not exist at
the time would misdate the format.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-07-30 15:21:31 +02:00
parent ecac1e58e3
commit b9ad9bcb21
20 changed files with 790 additions and 352 deletions
+5 -1
View File
@@ -171,7 +171,11 @@ Notice the `--` separator.
To debug startup issues, in particular issues with getty/login, add
the following to your Finit .conf file:
tty [12345789] notty noclear
tty board {
runlevel = "12345789"
notty = true
noclear = true
}
The `notty` option ensures reusing the stdin/stdout set up by the
kernel. Remember, this is only for debugging and would leave your
+60 -17
View File
@@ -14,12 +14,21 @@ specified separated by comma. Multiple conditions are logically AND'ed
during evaluation, i.e. all conditions must be satisfied in order for a
service to run.
Two special prefixes can be used inside the angle brackets:
One prefix can be used on a condition:
- `!` -- service does not support `SIGHUP` (noreload), or run/task
should not block runlevel changes (i.e., bootstrap)
- `~` -- propagate reload from this dependency, see below
The line-based format also accepts a leading `!` on the list, which is
not a condition and not a negation. It is a flag on the block, and it
means two unrelated things depending on where it sits: a service does
not support `SIGHUP` (noreload), or a run/task should not block
runlevel changes, i.e. bootstrap. Each of those is its own key here:
| Line-based | Block format |
|---|---|
| `<!...>` on a service or sysv | `reload-signal = "none"` |
| `<!...>` on a run or task | `required = false` |
Finit guarantees by default that all run/tasks run (at least) once
per runlevel. For most tasks this is a good default, for example
checking SSH host keys or loading keymap. However, for conditions
@@ -27,7 +36,12 @@ that are unlikely to happen it is not. (See example below.)
### Example
service [2345] <pid/setupd,pid/zebra> /sbin/netd -- Network monitor
service netd {
description = "Network monitor"
runlevel = "2345"
conditions = { "pid/setupd", "pid/zebra" }
command = "/sbin/netd"
}
In this example the Network monitor daemon `netd` is not started until
both the `pid/setupd` *and* `pid/zebra` conditions are satisfied. A
@@ -42,15 +56,28 @@ being created, i.e., the service's default readiness notification.
Another example is `dropbear`, it does not support `SIGHUP`, but we can
also see optional sourcing of arguments from an environment file:
service [2345789] <!> env:-/etc/default/dropbear dropbear -F -R $DROPBEAR_ARGS -- Dropbear SSH daemon
service dropbear {
description = "Dropbear SSH daemon"
runlevel = "2345789"
reload-signal = "none"
envfile = "-/etc/default/dropbear"
command = "dropbear -F -R $DROPBEAR_ARGS"
}
Finally, the weird "block runlevel changes" example. Here we see what
happens when Finit receives `SIGPWR`, sent from a power daemon like
[powstatd(8)][]. A condition is asserted and a user can set up their
own task to act on it. We do not want this task to block Finit from
moving to the next runlevel after bootstrap, so we set `<!>`:
moving to the next runlevel after bootstrap, so we set
`required = false`:
task [S0123456789] <!sys/pwr/fail> name:pwrfail initctl poweroff -- Power failure, shutting down
task pwrfail {
description = "Power failure, shutting down"
runlevel = "S0123456789"
conditions = { "sys/pwr/fail" }
required = false
command = "initctl poweroff"
}
Propagating Reload in Dependencies
@@ -65,13 +92,23 @@ not care if it reloads its config.
For services that need to react when their upstream reloads, the `~`
prefix propagates the reload from the dependency:
service <pid/svc_a> name:svc_b /sbin/svc_b -- Needs A (barrier)
service <!~pid/svc_b> name:svc_c /sbin/svc_c -- Propagate reload from B
service svc_b {
description = "Needs A (barrier)"
conditions = { "pid/svc_a" }
command = "/sbin/svc_b"
}
service svc_c {
description = "Propagate reload from B"
conditions = { "~pid/svc_b" }
reload-signal = "none"
command = "/sbin/svc_c"
}
Here, `<~pid/svc_b>` means: propagate a reload of `svc_b` to `svc_c`.
When `svc_b` reloads, `svc_c` will be restarted (because of `!`,
noreload) instead of merely resumed. If `svc_c` supported `SIGHUP`
(no `!` prefix), it would be sent `SIGHUP` instead.
When `svc_b` reloads, `svc_c` will be restarted, because of
`reload-signal = "none"`, instead of merely resumed. If `svc_c`
supported `SIGHUP`, it would be sent `SIGHUP` instead.
This is similar to systemd's `PropagatesReloadTo=` directive, but
declared on the consumer side rather than the provider side.
@@ -103,7 +140,12 @@ services, or run/task jobs, on external site-dependent stimuli.
**Example:**
service [2345] <usr/foo> alarm --arg foo -- Foo alarm
service alarm {
description = "Foo alarm"
runlevel = "2345"
conditions = { "usr/foo" }
command = "alarm --arg foo"
}
For convenience, prefixing with `usr/` is allowed, but any other slashes
or period characters are disallowed. E.g., to trigger the `Foo alarm`,
@@ -126,8 +168,9 @@ Static (one-shot) conditions, like `usr/`, never enter the `flux` state.
> "touch" or recreate, their PID file on `SIGHUP`. This can be done by
> calling `utimensat()` on the PID file. Provided, of course, that the
> service supports reloading on `SIGHUP`, otherwise it will be restarted
> by Finit when they instead exit on the signal. For such services, use
> `<!>` to tell Finit the service does not support `SIGHUP`.
> by Finit when they instead exit on the signal. For such services,
> set `reload-signal = "none"` to tell Finit the service does not
> support `SIGHUP`.
Built-in Conditions
@@ -307,8 +350,8 @@ restarts that would otherwise occur because a depending service was sent
Services with the `~` prefix are an exception to this rule: when their
conditions return to `on` after being in `flux`, the reload is propagated
-- the service is reloaded (SIGHUP) or restarted (noreload `!`) instead
of simply being resumed.
-- the service is reloaded (SIGHUP), or restarted if it has
`reload-signal = "none"`, instead of simply being resumed.
Therefore, any plugin that supplies Finit with conditions must ensure
that their state is updated after each reconfiguration. This can be
+109 -80
View File
@@ -23,77 +23,66 @@ Finit provides three different cgroup directives for controlling resource alloca
Top-level Cgroup Definition
----------------------------
**Syntax:** `cgroup NAME settings`
**Syntax:** `cgroup NAME { settings }`
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
cgroup init cpu.weight:100
cgroup user cpu.weight:100
cgroup system cpu.weight:9800
cgroup init { cpu.weight = 100 }
cgroup user { cpu.weight = 100 }
cgroup system { cpu.weight = 9800 }
Adding an extra cgroup `maint/` will require you to adjust the weight of
the above three. We leave `init/` and `user/` as-is reducing weight of
`system/` to 9700.
cgroup system cpu.weight:9700
cgroup system { cpu.weight = 9700 }
# Example extra cgroup 'maint'
cgroup maint cpu.weight:100
cgroup maint { cpu.weight = 100 }
By default, the `system/` cgroup is selected for almost everything. The
`init/` cgroup is reserved for PID 1 itself and its closest relatives.
The `user/` cgroup is for local TTY logins spawned by getty.
Global Cgroup Selector
----------------------
Joining a Cgroup
----------------
**Syntax:** `cgroup.NAME[,options]` (standalone directive)
**Syntax:** `cgroup NAME { settings }` (inside a service block)
To select a different top-level cgroup, e.g. `maint/`, for a group of
run/task/service directives in a `.conf` file, use the `cgroup.NAME`
directive as a standalone line:
Every block says which group it joins, by name. An empty block joins
without changing anything:
cgroup.maint
service [...] <...> /path/to/foo args -- description
service [...] <...> /path/to/bar args -- description
service foo {
cgroup maint {}
command = "/path/to/foo args"
}
Both services will run in the `maint/` cgroup.
service bar {
cgroup maint {}
command = "/path/to/bar args"
}
You can also include options with the global selector:
Both services run in the `maint/` cgroup. Settings inside the block
apply to that service alone:
cgroup.system,delegate
service [...] <...> /path/to/foo args -- description
service foo {
cgroup maint {
cpu.max = 10000
memory.max = 655360
}
command = "/path/to/foo args"
}
Per-Service Cgroup Option
--------------------------
**Syntax:** `cgroup.NAME[,options]` or `cgroup:options` (service option)
To override the cgroup for a specific service, use the `cgroup.NAME`
option within the service directive:
service [...] <...> cgroup.maint /path/to/foo args -- description
This form also allows per-service limits. Two syntaxes are supported:
**New comma-separated syntax (recommended):**
service [...] <...> cgroup.maint,cpu.max:10000,mem.max:655360 /path/to/foo args -- description
**Old colon-separated syntax (legacy):**
service [...] <...> cgroup.maint:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
You can also apply options to the current default cgroup (without changing it)
using the `cgroup:options` syntax:
service [...] <...> cgroup:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
Both syntaxes work identically. The new comma-separated syntax is recommended
as it's more consistent with other option parsing in Finit.
> [!NOTE]
> The legacy format has a standalone `cgroup.NAME` line that selects a
> group for every stanza following it in the file, and a `cgroup:options`
> form that applies settings to whichever group is current without
> naming it. Neither has an equivalent here, by design: a block that
> joins a group says so itself, so the group cannot depend on what came
> earlier in the file.
Note the `mem.` exception to the rule: every cgroup setting maps directly to
cgroup v2 syntax. I.e., `cpu.max` maps to the file `/sys/fs/cgroup/maint/foo/cpu.max`.
@@ -108,40 +97,66 @@ configuration filename (without the `.conf` extension). For example, a
service defined in `system/10-hotplug.conf` would create a cgroup at
`/sys/fs/cgroup/system/10-hotplug/` by default.
To use a more descriptive name (recommended for clarity), you can specify
`name:` in the cgroup directive:
To use a more descriptive name (recommended for clarity), set `name`
inside the cgroup block:
service [...] <...> cgroup.system,name:udevd /lib/systemd/systemd-udevd -- Device event daemon
service udevd {
description = "Device event daemon"
cgroup system { name = "udevd" }
command = "/lib/systemd/systemd-udevd"
}
This creates the cgroup at `/sys/fs/cgroup/system/udevd/` instead.
The syntax supports combining the name override with other options:
`name` combines with any other setting:
service [...] <...> cgroup.system,name:udevd,cpu.max:10000 /lib/systemd/systemd-udevd -- Device event daemon
service udevd {
description = "Device event daemon"
cgroup system {
name = "udevd"
cpu.max = 10000
}
command = "/lib/systemd/systemd-udevd"
}
Or with delegation:
service [2345] @podman:podman \
cgroup.containers,name:podman,delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
service podman {
description = "Podman API"
runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
name = "podman"
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
A daemon using `SCHED_RR` currently needs to run outside the default cgroups.
A daemon using `SCHED_RR` currently needs to run outside the default
cgroups.
service [...] <...> cgroup.root /path/to/daemon arg -- Real-Time process
service rt {
description = "Real-Time process"
cgroup root {}
command = "/path/to/daemon arg"
}
Cgroup Delegation
-----------------
For services that need to create their own child cgroups (container runtimes
like Docker, Podman, systemd-nspawn, LXC), use the `delegate` option:
like Docker, Podman, systemd-nspawn, LXC), set `delegate`:
service [2345] @dockerd:dockerd \
cgroup.system,delegate /usr/bin/dockerd -- Docker daemon
Or with the old colon syntax:
service [2345] @dockerd:dockerd \
cgroup.system:delegate /usr/bin/dockerd -- Docker daemon
service dockerd {
description = "Docker daemon"
runlevel = "2345"
user = "dockerd"
group = "dockerd"
cgroup system { delegate = true }
command = "/usr/bin/dockerd"
}
This allows the container runtime to:
@@ -159,32 +174,46 @@ When delegation is enabled, Finit:
**Requirements:**
- The service should specify `@user:group` for proper ownership
- The service should specify `user` and `group` for proper ownership
- Controllers are delegated from the parent cgroup
**Example with additional config (new syntax):**
**Example with additional config:**
service [2345] @podman:podman \
cgroup.containers,delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
service podman {
description = "Podman API"
runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
**Or with old syntax:**
service [2345] @podman:podman \
cgroup.containers:delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
Both examples delegate the cgroup while also setting a 4GB memory limit.
This delegates the cgroup while also setting a 4GB memory limit.
**Container template example:**
Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix)
for running rootful podman container instances using delegation:
sysv log:prio:local1,tag:%i kill:30 pid:!/run/container:%i.pid \
pre:0,/usr/sbin/container cleanup:0,/usr/sbin/container \
cgroup.system,delegate \
[2345] <!> :%i container -n %i -- container %i
sysv container:%i {
description = "container %i"
runlevel = "2345"
reload-signal = "none"
pidfile = "/run/container:%i.pid"
stop-timeout = 30
log { priority = "local1" identity = "%i" }
exec-start-pre = "/usr/sbin/container"
exec-start-pre-timeout = 0
exec-cleanup = "/usr/sbin/container"
exec-cleanup-timeout = 0
cgroup system { delegate = true }
command = "container -n %i"
}
This template uses `sysv` type with delegation, demonstrating that cgroup
delegation works with different service types, not just `service`.
+7 -6
View File
@@ -6,15 +6,16 @@ and any `*.conf`, was added. It is worth noting that these are global
and *shared with all* services -- the only way to have a service-local
environment is detailed in [Services Environment](service-env.md).
The syntax for global environment variables is straight forward. In
Finit v4.4 the `set` keyword was added for completeness, but the old
syntax (without the `set ` prefix) is still honored:
Global environment variables go in an `environment` block, `env` for
short:
set foo=bar
set baz="qux"
environment {
foo = "bar"
baz = "qux"
}
On reload of .conf files, all tracked environment variables are cleared
so if `foo=bar` is removed from `finit.conf`, or any `finit.d/*.conf`
so if `foo` is removed from `finit.conf`, or any `finit.d/*.conf`
file, it will no longer be used by Finit or any new (!) started
run/tasks or services. The environment of already started processes can
not be changed.
+98 -13
View File
@@ -6,24 +6,109 @@ the navigation menu.
Configuration File Syntax
--------------------------
The file format is line based, empty lines and comments, lines starting
with `#`, are ignored. A configuration directive starts with a keyword
followed by a space and the rest of the line is treated as the value.
As of Finit v4.4, configuration directives can be broken up in multiple
lines using the continuation character `\`, and trailing comments are
also allowed. Example:
A `.conf` file is a series of blocks. Every setting is a key inside
one, so nothing has to be remembered by position:
```aconf
# Escape \# chars if you want them literal in, e.g., descriptions
service name:sysklogd [S123456789] \
env:-/etc/default/sysklogd \
syslogd -F $SYSLOGD_ARGS \
-- System log daemon \# 1 # Comments allowed
service sysklogd {
description = "System log daemon"
runlevel = "S123456789"
envfile = "-/etc/default/sysklogd"
command = "syslogd -F $SYSLOGD_ARGS"
}
```
Values are quoted strings, bare words, or integers. Lists use braces,
and a block may carry a title, which becomes the service identity:
```aconf
# shell comment
// C++ comment
/* C comment */
key = "value" # string
number = 20 # integer
flag = true # boolean
list = { "one", "two" } # string list
block title { key = "value" } # titled section
path = "${HOME}/thing" # environment expansion
include("/etc/finit.d/extra.conf") # include another file
```
Bare words work wherever a string is expected, so `memory.max = 65M`
and `restart = always` need no quotes.
### Two conventions
Keys are kebab-case, never `snake_case` or `CamelCase`: `restart-sec`,
`stop-timeout`, `reboot-watchdog`.
Keys that take a list are plural: `conditions`, `conflicts`,
`capabilities`, `modules`, `extra-groups`. Two imperatives keep their
singular form because they are verbs rather than nouns: `mknod` and
`include`.
### Short forms
Nine keys have an alias. An alias may abbreviate the canonical name,
or preserve the spelling the line-based format used; it never renames.
| Canonical | Alias | Canonical | Alias |
|---|---|---|---|
| `description` | `desc` | `manual-start` | `manual` |
| `conditions` | `cond` | `remain-after-exit` | `remain` |
| `capabilities` | `caps` | `stop-signal` | `halt` |
| `modules` | `mod` | `stop-timeout` | `kill` |
| `envfile` | `env` | | |
### Optional paths
A leading `-` on a path means carry on if it is not there:
```aconf
service foo {
envfile = "-/etc/default/foo" # skip the file if missing
command = "-/usr/sbin/foo" # skip the whole block if missing
}
```
That is the only mark meaning "optional". Two others mean something
else: `!` on `runlevel` inverts the set, e.g. `runlevel = "!12345"`,
and `~` on a condition propagates a reload from the service it names.
Both are covered where they apply.
Both Formats Are Read
---------------------
Finit also reads the line-based format it has always used, where a
stanza is a keyword followed by values whose meaning comes from their
position:
```aconf
service [S123456789] env:-/etc/default/sysklogd syslogd -F $SYSLOGD_ARGS -- System log daemon
```
That format still works and is not going away. It is frozen at the
Finit 4.x feature set, so new settings appear only in the block format,
and the documentation is written in blocks throughout.
There is no new file extension and no marker line. Every file is still
`*.conf`, and Finit decides which format a file is in by reading it: if
it parses as blocks it is a block file, otherwise it goes to the
line-based parser. A file is one format or the other, never a mix.
Because the two are told apart before either parser commits, a typo
reports its own file and line instead of being mistaken for the other
format:
```
parse error: /etc/finit.d/foo.conf:3: no such option 'commnad'
```
The .conf files `/etc/finit.conf` and `/etc/finit.d/*` support many
directives. Some are restricted, e.g., only available at bootstrap,
settings. Some are restricted, e.g., only available at bootstrap,
runlevel `S`. Read on in [Files & Layout](files.md) for more on how
to structure your .conf files.
+14 -3
View File
@@ -38,12 +38,19 @@ Log rotation is controlled using the global `log` setting.
**Example:**
service log:prio:user.warn,tag:ntpd /sbin/ntpd pool.ntp.org -- NTP daemon
service ntpd {
description = "NTP daemon"
log {
priority = "user.warn"
identity = "ntpd"
}
command = "/sbin/ntpd pool.ntp.org"
}
Output Buffering
----------------
When using the `log` directive, Finit redirects the service's stdout and
When using the `log` block, Finit redirects the service's stdout and
stderr to a pipe connected to a logger process. Programs detect this as
non-interactive output (i.e., `isatty()` returns false) and typically
switch from line-buffered to fully-buffered mode.
@@ -53,7 +60,11 @@ directly, so this is rarely an issue. However, if a service's log
messages appear delayed or batched, you can force line-buffered output
by wrapping the command with `stdbuf`:
service log /usr/bin/stdbuf -oL /path/to/command -- My service
service myservice {
description = "My service"
log { }
command = "/usr/bin/stdbuf -oL /path/to/command"
}
The `-oL` option forces line-buffered output, and `-o0` forces unbuffered
output. See `stdbuf(1)` for details.
+8 -2
View File
@@ -48,7 +48,10 @@ system administrator.
The bundled default `rescue.conf` contains nothing more than:
runlevel 1
tty [12345] rescue
tty rescue {
runlevel = "12345"
rescue = true
}
The `tty` has the `rescue` option set, which works similar to the board
bring-up tty option `notty`. The major difference being that `sulogin`
@@ -57,6 +60,9 @@ is started to query for root/admin password. If `sulogin` is not found,
If Finit cannot find `/lib/finit/rescue.conf` it defaults to:
tty [12345] rescue
tty rescue {
runlevel = "12345"
rescue = true
}
There is no way to exit the *fallback* rescue mode.
+10 -2
View File
@@ -26,8 +26,16 @@ the lifetime of your system.
Example:
task [S] /lib/console-setup/console-setup.sh
service [S12345] env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_ARGS
task console-setup {
runlevel = "S"
command = "/lib/console-setup/console-setup.sh"
}
service rsyslogd {
runlevel = "S12345"
envfile = "-/etc/default/rsyslog"
command = "rsyslogd -n $RSYSLOGD_ARGS"
}
When bootstrap has completed, Finit moves to runlevel 2. This can be
changed in `/etc/finit.conf` using the `runlevel N` directive, or by a
+11 -4
View File
@@ -13,7 +13,12 @@ file `/etc/default/foo`. Like this:
* `/etc/finit.conf`:
service [2345] env:-/etc/default/foo foo -n $FOO_OPTIONS -- Example foo daemon
service foo {
description = "Example foo daemon"
runlevel = "2345"
envfile = "-/etc/default/foo"
command = "foo -n $FOO_OPTIONS"
}
Here the service `foo` is started with `-n`, to make sure it runs in the
foreground, and the with the options found in the environment file. With
@@ -22,6 +27,8 @@ the `ps` command we can see that the process is started with:
foo -n --extra-arg=bar -s -x
> [!NOTE]
> The leading `-` in `env:` determines if Finit should treat a missing
> environment file as blocking the start of the service or not. When
> `-` is used, a missing environment file does *not* block the start.
> The leading `-` on `envfile` determines if Finit should treat a
> missing environment file as blocking the start of the service or not.
> When `-` is used, a missing environment file does *not* block the
> start. The same mark works on `command`, where it means carry on if
> the binary is missing.
+134 -98
View File
@@ -1,21 +1,22 @@
Service Options
===============
The run/task/tty/service/sysv directives take modifiers, or options, to
control their behavior. This section lists them with their limitations.
All modifiers must be placed between the directive and its command.
The `run`, `task`, `tty`, `service` and `sysv` blocks take settings as
keys. This section lists them with their limitations.
The name of a service, shown by the `initctl` tool, defaults to the
basename of the service executable. It can be changed with the `name:`
option:
The name of a service, shown by the `initctl` tool, is the block title.
It defaults to the basename of the executable if the title is omitted:
name:<service-name>
service sshd {
command = "/usr/sbin/sshd -D"
}
For multiple instances of a service, with the same `name`, set the
identifier `:ID` to prevent Finit from replacing previous instances:
For multiple instances of a service, with the same name, add an
identifier after a colon to prevent Finit from replacing previous
instances:
service name:ssdp :eth1 ssdpd eth1 -- Windows discovery on eth1
service name:ssdp :eth2 ssdpd eth2 -- Windows discovery on eth2
service ssdp:eth1 { command = "ssdpd eth1" }
service ssdp:eth2 { command = "ssdpd eth2" }
The [`initctl`](../initctl.md) tool will list these two services as:
@@ -23,107 +24,140 @@ The [`initctl`](../initctl.md) tool will list these two services as:
- ssdp:eth2
Conflicting services that must be prevented from starting, use the
`conflict:` option:
`conflicts` key:
service [S12345789] udevd -- Device event management daemon
run [S] conflict:udevd mdev -s -- Populating device tree
service udevd { command = "udevd" }
run mdev {
runlevel = "S"
conflicts = { "udevd" }
command = "mdev -s"
}
Multiple conflicting services can be separated using `,`:
The list may name several, and an instance is named the same way it is
declared:
service :1 abc
service :2 abc
service conflict:abc:1,abc:2 cde
service abc:1 { command = "abc 1" }
service abc:2 { command = "abc 2" }
service cde { conflicts = { "abc:1", "abc:2" } command = "cde" }
If a service should not be automatically started, it can be configured
as manual with the optional `manual` argument. The service can then be
started at any time by running `initctl start <service>`.
If a service should not be automatically started, set `manual-start`.
It can then be started at any time with `initctl start <service>`:
manual:yes
service lldpd {
manual-start = true
command = "/usr/sbin/lldpd -d"
}
Other run/task/service options are:
Other run/task/service settings are:
* `caps:...` -- see the [Linux Capabilities](capabilities.md) section
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty)
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading)
* `notify:...` -- see [Service Synchronization](service-sync.md)
* `if:...` -- see [Conditional Execution](services.md#conditional-execution)
* `type:forking` -- see description of the [service](services.md) directive
* `capabilities` -- see the [Linux Capabilities](capabilities.md) section
* `cgroup NAME {}` -- see the [Cgroups](cgroups.md) section
* `envfile` -- see the [Service Environment](service-env.md) section
* `log {}` -- see [Redirecting Output](logging.md#redirecting-output)
* `tty` -- see [Controlling TTY](tty.md#controlling-tty)
* `notify` -- see [Service Synchronization](service-sync.md)
* `if` -- see [Conditional Execution](services.md#conditional-execution)
* `type = "forking"` -- see description of the [service](services.md) block
* a leading `-` on `command` -- see [Conditional Loading](services.md#conditional-loading)
As mentioned previously, services are automatically started, restarted,
and stopped, depending on the configuration and conditions. Within the
confines of that the following options are available:
Restarting
----------
* `restart:NUM` -- number of times Finit tries to restart a crashing
service, default: 10, max: 255. When this limit is reached the
service is marked *crashed* and must be restarted manually with
`initctl restart NAME`
* `restart_sec:SEC` -- number of seconds before Finit tries to restart
a crashing service, default: 2 seconds for the first five retries,
then back-off to 5 seconds. The maximum of this configured value
and the above (2 and 5) will be used
* `restart:always` -- no upper limit on the number of times Finit
tries to restart a crashing service. Same as `restart:-1`
* `norestart` -- dont restart on failures, same as `restart:0`
* `respawn` -- bypasses the `restart` mechanism completely, allows
endless restarts. Useful in many use-cases, but not what `service`
was originally designed for so not the default behavior
* `remain:yes` -- for `run` and `task` only. Prevents the task from
re-running on runlevel re-entry and ensures the `post:` script runs
when the task is explicitly stopped or leaves its valid runlevels.
Similar to systemd's `RemainAfterExit=yes`. See [Task and Run](task-and-run.md)
for more details
* `oncrash:reboot` -- when all retries have failed, and the service
has *crashed*, if this option is set the system is rebooted
* `oncrash:script` -- similarly, but instead of rebooting, call the
`post:script` action with exit code `crashed`, see below
* `reload:'script [args]'` -- some services do not support `SIGHUP` but
may have other ways to update the configuration of a running daemon.
When `reload:script` is defined it is preferred over `SIGHUP`. Like
systemd, Finit sets `$MAINPID` as a convenience to scripts, which in
effect also allow `reload:'kill -HUP $MAINPID'`
* `stop:'script [args]'` -- some services may require alternate methods
to be stopped. If a `stop:script` is defined it is preferred over
`SIGTERM` and `stop`, for `service` and `sysv`, respectively.
Similar to `reload:script`, Finit sets `$MAINPID`
Services are automatically started, restarted, and stopped, depending
on the configuration and conditions. Within the confines of that the
following settings are available:
> [!CAUTION]
> Both `reload:script` and `stop:script` are called as PID 1, without
> any timeout! Meaning, it is up to you to ensure the script is not
> blocking for seconds at a time or never terminates.
* `restart-max = NUM` -- number of times Finit tries to restart a
crashing service, default: 10, max: 255. When this limit is
reached the service is marked *crashed* and must be restarted
manually with `initctl restart NAME`
* `restart-sec = SEC` -- number of seconds before Finit tries to
restart a crashing service, default: 2 seconds for the first five
retries, then back-off to 5 seconds. The maximum of this
configured value and the above (2 and 5) will be used
* `restart = "always"` -- no upper limit on the number of times Finit
tries to restart a crashing service
* `restart = "never"` -- do not restart on failures. `false` is
accepted as a synonym, and `true` selects the default policy
* `respawn = true` -- bypasses the `restart` mechanism completely,
allowing endless restarts. Exiting is treated as normal work
rather than failure, so the service is restarted at once instead of
being counted and delayed. This is how a `tty` behaves; it is not
what `service` was designed for, so it is not the default
* `remain-after-exit = true` -- for `run` and `task` only. Prevents
the task from re-running on runlevel re-entry and ensures the
`exec-stop-post` script runs when the task is explicitly stopped or
leaves its valid runlevels. This is systemd's `RemainAfterExit=`.
See [Task and Run](task-and-run.md) for more details
* `oncrash = "reboot"` -- when all retries have failed, and the
service has *crashed*, the system is rebooted
* `oncrash = "script"` -- similarly, but instead of rebooting, call
the `exec-stop-post` script with exit code `crashed`, see below
When stopping a service (run/task/sysv/service), either manually or when
moving to another runlevel, Finit starts by sending `SIGTERM`, to allow
the process to shut down gracefully (unless a `stop:'script'` is used).
However, if the process has not been collected within 3 seconds, Finit
will send `SIGKILL`. To stop the process using a different signal than
`SIGTERM`, use `halt:SIGNAL`, e.g., `halt:SIGPWR`. To change the delay
between the stop signal and KILL, use the option `kill:<1-60>`, e.g.,
`kill:10` to wait 10 seconds before sending `SIGKILL`.
Stopping and reloading
----------------------
Services, including the `sysv` variant, support pre/post/ready and
cleanup scripts:
When stopping a service, either manually or when moving to another
runlevel, Finit starts by sending `SIGTERM` to let the process shut
down gracefully. If it has not been collected within 3 seconds,
`SIGKILL` follows. Both ends of that are configurable:
* `pre:[0-3600,]script` -- called before the sysv/service is stated
* `post:[0-3600,]script` -- called after the sysv/service has stopped
* `ready:[0-3600,]script` -- called when the sysv/service is ready
* `cleanup:[0-3600,]script` -- called when run/task/sysv/service is removed
* `stop-signal = "SIGPWR"` -- send this instead of `SIGTERM`
* `stop-timeout = 10` -- seconds to wait before `SIGKILL`, 1-300
The optional number (0-3600) is the timeout before Finit kills the
script, it defaults to the kill delay value and can be disabled by
setting it to zero. These scripts run as the same `@USER:GROUP` as the
service itself, with any `env:file` sourced. The scripts are executed
from the `$HOME` of the given user. The scripts are not called with any
argument, but get a set of environment variables:
Some services need more than a signal:
* `exec-stop = "script [args]"` -- run instead of sending the stop
signal, and instead of `stop` for `sysv`
* `exec-reload = "script [args]"` -- run instead of sending `SIGHUP`.
Like systemd, Finit sets `$MAINPID` as a convenience to scripts,
which in effect also allows `exec-reload = "kill -HUP $MAINPID"`
If a daemon cannot be reloaded with a signal at all, say so and Finit
restarts it instead:
service dropbear {
reload-signal = "none"
command = "/usr/sbin/dropbear -R -F"
}
`SIGHUP` is the default and the only other accepted value, with or
without the `SIG` prefix and in any case.
Scripts
-------
Services, including the `sysv` variant, support six lifecycle scripts:
* `exec-start-pre` -- called before the sysv/service is started
* `exec-start-ready` -- called when the sysv/service is ready
* `exec-stop` -- called instead of the stop signal
* `exec-stop-post` -- called after the sysv/service has stopped
* `exec-reload` -- called instead of `SIGHUP`
* `exec-cleanup` -- called when run/task/sysv/service is removed
Each takes a matching `-timeout`, in seconds, 0-3600, after which Finit
kills the script. It defaults to the `stop-timeout` value and can be
disabled by setting it to zero:
service foo {
exec-start-pre = "/etc/foo/pre.sh"
exec-start-pre-timeout = 10
command = "/usr/sbin/foo"
}
These scripts run as the same user and group as the service itself,
with any `envfile` sourced. They are executed from the `$HOME` of the
given user. The scripts are not called with any argument, but get a
set of environment variables:
* `SERVICE_IDENT=foo:1`
* `SERVICE_NAME=foo`
* `SERVICE_ID=1`
The `post:script` is called with an additional set of environment
variables. Yes, the text is correct, the naming was an accident:
The `exec-stop-post` script is called with an additional set of
environment variables. Yes, the text is correct, the naming was an
accident:
- `EXIT_CODE=[exited,signal,crashed]`: normal exit, signaled, or
crashed
@@ -131,11 +165,13 @@ variables. Yes, the text is correct, the naming was an accident:
the program, if it exited normally, or the signal name (`HUP`,
`TERM`, etc.) if it exited due to signal
When a run/task/sys/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `post:script` always
runs when the process has stopped, and the `cleanup:script` runs when
the the stanza has been removed from the runlevel.
When a run/task/sysv/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `exec-stop-post`
script always runs when the process has stopped, and `exec-cleanup`
runs when the stanza has been removed from the runlevel.
> [!IMPORTANT]
> These script actions are intended for setup, cleanup, and readiness
> notification. It is up to the user to ensure the scripts terminate.
> notification. A script that outlives its timeout is killed, so pick
> one that suits the work, or set it to zero to opt out and take
> responsibility for the script terminating.
+13 -6
View File
@@ -38,10 +38,10 @@ This will be made the default in Finit 5.0. In this mode of operation,
every service needs to explicitly declare their readiness notification,
like this:
service notify:pid watchdogd
service notify:systemd foo
service notify:s6 bar
service notify:none qux
service watchdogd { notify = "pid" command = "watchdogd" }
service foo { notify = "systemd" command = "foo" }
service bar { notify = "s6" command = "bar" }
service qux { notify = "none" command = "qux" }
The `notify:none` syntax is for completeness in systems which run in
`readiness pid` mode (default). Services declared with `notify:none`
@@ -50,8 +50,15 @@ will transition to ready as soon as Finit has started them, e.g.,
To synchronize two services the following condition can be used:
service notify:pid watchdogd
service <service/watchdogd/ready> stress-ng --cpu 8
service watchdogd {
notify = "pid"
command = "watchdogd"
}
service stress-ng {
conditions = { "service/watchdogd/ready" }
command = "stress-ng --cpu 8"
}
For details on the syntax and options, see below.
+6 -1
View File
@@ -14,7 +14,12 @@ example employs a wrapper script in `/etc/start.d`.
* `/etc/finit.d/available/program.conf`:
service [235] <!> /etc/start.d/program -- Example Program
service program {
description = "Example Program"
runlevel = "235"
reload-signal = "none"
command = "/etc/start.d/program"
}
* `/etc/start.d/program:`
+160 -70
View File
@@ -19,20 +19,25 @@ 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, using
the `pid:!/path/to/pidfile.pid` syntax. There is an alternative syntax
that may be more intuitive, where Finit can also guess the PID file
based on the daemon's command name:
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 type:forking ntpd -- NTP daemon
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, you have to submit the
full `pid:!/path/to/file.pid`.
`/var/run/ntpd.pid`. If Finit guesses wrong, name the file yourself
with `pidfile = "/path/to/file.pid"`.
With `pid:!/path`, the file belongs to the service: Finit reads it
but does not create or remove it. The one exception is *stale*
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
@@ -44,56 +49,75 @@ from getting stuck in a crash-restart loop.
In the case of `ospfd` (below), we omit the `-d` flag (daemonize) to
prevent it from forking to the background:
service [2345] <pid/zebra> /sbin/ospfd -- OSPF daemon
service ospfd {
description = "OSPF daemon"
runlevel = "2345"
conditions = { "pid/zebra" }
command = "/sbin/ospfd"
}
`[2345]` denote the runlevels `ospfd` is allowed to run in, they are
optional and default to level 2-5 if omitted.
`<...>` is the condition for starting `ospfd`. In this example Finit
`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` before starting `ospfd`. Finit watches
*all* files in `/var/run`, for each file named `*.pid`, or `*/pid`,
Finit opens it and find the matching `NAME:ID` using the PID.
The condition can be prefixed with `!` and/or `~`:
A condition may be prefixed with `~` to propagate a reload of the
upstream service to this one, rather than merely pausing and resuming
it:
- `<!pid/zebra>` -- `ospfd` does not support `SIGHUP` (noreload)
- `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd`
- `<!~pid/zebra>` -- both: noreload and propagate reload
conditions = { "~pid/zebra" }
For details, see the [Finit Conditions](../conditions.md) document.
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](../conditions.md) document.
Some services do not maintain a PID file and rather than patching each
application Finit provides a workaround. A `pid` keyword can be set
to have Finit automatically create (when starting) and later remove
(when stopping) the PID file. The file is created in the `/var/run`
directory using the `basename(1)` of the service. The default can be
modified with an optional `pid:`-argument:
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:
pid[:[/path/to/]filename[.pid]]
pidfile = true # /var/run/<command basename>.pid
pidfile = "bar" # a bare name, /var/run/bar.pid
pidfile = "/run/bar.pid" # an explicit path
For example, by adding `pid:/run/bar.pid` to the service `/sbin/bar`,
that PID file will, not only be created and removed automatically, but
also be used by the Finit condition subsystem. So a service/run/task
can depend on `<pid/bar>`, like this foo will not be started until bar
has started:
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 pid:/run/bar.pid bar -- Bar Service
service <pid/bar> foo -- Foo Service
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 can be enabled by using the `notify`
option:
and s6 style notification. This is enabled with the `notify` key:
* `notify:systemd` -- tells Finit the service uses the `sd_notify()`
* `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
* `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
@@ -101,7 +125,11 @@ option:
(must be >3) on then command line, Finit provides the following
syntax (`%n` is replaced by Finit with then descriptor number):
service [S12345789] notify:s6 mdevd -O 4 -D %n
service mdevd {
runlevel = "S12345789"
notify = "s6"
command = "mdevd -O 4 -D %n"
}
[sd_notify()]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
[s6 expect]: https://skarnet.org/software/s6/notifywhenup.html
@@ -115,7 +143,13 @@ then service's ready condition which other services can depend on:
This can be used to synchronize the start of another run/task/service:
task [S] <service/mdevd/ready> @root:root mdevd-coldplug
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
@@ -131,20 +165,29 @@ Non-privileged Services
-----------------------
Every `run`, `task`, or `service` can also list the privileges the
`/path/to/cmd` should be executed with. Prefix the command with
`@USR[:GRP[,SUPP,...]]`, where group and supplementary groups are
optional, like this:
command should be executed with, using `user`, `group` and
`extra-groups`, all optional:
run [2345] @joe:users logger "Hello world"
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`,
append them after the primary group, separated by commas:
To specify additional supplementary groups beyond those in
`/etc/group`, list them in `extra-groups`:
service @caddy:caddy,ssl-cert /usr/bin/caddy run
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`,
@@ -152,14 +195,22 @@ 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` somewhere between the `run`, `task`,
`service` keyword and the command, like this:
multiple web servers, add `:ID` to the block title, like this:
service :80 [2345] httpd -f -h /http -p 80 -- Web server
service :8080[2345] httpd -f -h /http -p 8080 -- Old web server
service httpd:80 {
description = "Web server"
runlevel = "2345"
command = "httpd -f -h /http -p 80"
}
Without the `:ID` to the service the latter will overwrite the former
and only the old web server would be started and supervised.
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.
Conditional Loading
@@ -169,35 +220,58 @@ Finit support conditional loading of stanzas. The following example is
take from the `system/hotplug.conf` file in the Finit distribution.
Here we only show a simplified subset.
Starting with the `nowarn` option.
Starting with the leading `-` on `command`.
service nowarn name:udevd pid:udevd /lib/systemd/systemd-udevd
service nowarn name:udevd pid:udevd udevd
service udevd {
pidfile = "udevd"
command = "-/lib/systemd/systemd-udevd"
}
When loading the .conf file Finit looks for `/lib/systemd/systemd-udevd`
if that is not found Finit automatically logs a warning. The `nowarn`
option disables this warning so that the second line can be evaluated,
service udevd {
pidfile = "udevd"
command = "-udevd"
}
When loading the .conf file Finit looks for
`/lib/systemd/systemd-udevd`, and if that is not found it logs a
warning. The leading `-` says a missing binary is expected here, so
the stanza is skipped quietly and the second block can be evaluated,
which also provides a service named `udevd`.
run nowarn if:udevd <pid/udevd> :1 udevadm settle -t 0
run udevadm:1 {
runlevel = "S"
if = "udevd"
conditions = { "pid/udevd" }
command = "-udevadm settle -t 0"
}
This line 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 nowarn conflict:udevd [S] mdev -s -- Populating device tree
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 statements can also be negated, so the previous stanza
can also be written as:
Conditional loading statements can also be negated, so the previous
stanza can also be written as:
run nowarn if:!udevd [S] mdev -s -- Populating device tree
run mdev {
description = "Populating device tree"
runlevel = "S"
if = "!udevd"
command = "-mdev -s"
}
The reason for using `conflict` in this example is that a conflict can be
resolved. Stanzas marked with `conflict:foo` are rechecked at runtime.
resolved. Stanzas naming a conflict are rechecked at runtime.
Conditional Execution
@@ -212,21 +286,37 @@ runlevel. E.g., a `task [123]` is qualified to run only in runlevel 1,
Conditional execution qualify a run/task/service based on a condition.
Consider this (simplified) example from the Infix operating system:
run [S] name:startup <pid/sysrepo> confd -b --load startup-config
run [S] if:<usr/fail-startup> name:failure <pid/sysrepo> confd --load failure-config
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 statements 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 run statement
to load `failure-config`.
Notice the critical difference between the `<pid/sysrepo>` condition and
`if:<usr/fail-startup>`. The former is a condition for starting and the
latter is a condition to check if a run/task/service is qualified to
even be considered.
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.
Conditional execution statements can also be negated, so provided the
file loaded did the opposite, i.e., set a condition on success, the
previous stanza can also be written as:
run [S] if:<!usr/startup-ok> name:failure <pid/sysrepo> confd ...
run failure {
runlevel = "S"
if = "<!usr/startup-ok>"
conditions = { "pid/sysrepo" }
command = "confd ..."
}
+10 -4
View File
@@ -24,9 +24,10 @@ calls `init-script restart` on `initctl reload`. Similar to how
`service` stanzas work.
Forking services started with `sysv` scripts can be monitored by Finit
by declaring the PID file to look for: `pid:!/path/to/pidfile.pid`.
Notice the leading `!`, it signifies Finit should not try to create the
file, but rather watch that file for the resulting forked-off PID. This
by declaring the PID file to look for: `pidfile = "/path/to/file.pid"`.
Finit does not create that file, it watches it for the resulting
forked-off PID, which is the default; `pidfile-create = true` is what
asks Finit to write it instead. This
syntax also works for forking daemons that do not have a command line
option to run it in the foreground, more on this below in `service`.
@@ -64,7 +65,12 @@ Start/Stop Scripts
For syntax details, see [SysV Init Scripts](#sysv-init-scripts), above.
Here follows an example taken from a Debian installation:
sysv [2345] <pid/syslogd> /etc/init.d/openbsd-inetd -- OpenBSD inet daemon
sysv inetd {
description = "OpenBSD inet daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
command = "/etc/init.d/openbsd-inetd"
}
The init script header could be parsed to extract `Default-Start:` and
other parameters for the `sysv` command to Finit. There is currently no
+24 -12
View File
@@ -40,27 +40,35 @@ One-shot like 'run', but starts in parallel with the next command.
Both `run` and `task` commands are run in a shell, so basic pipes and
redirects can be used:
task [s] echo "foo" | cat >/tmp/bar
task bar {
runlevel = "S"
command = "echo \"foo\" | cat >/tmp/bar"
}
Please note, `;`, `&&`, `||`, and similar are *not supported*. Any
non-trivial constructs are better placed in a separate shell script.
remain:yes
----------
remain-after-exit
-----------------
By default, a `run` or `task` will re-run each time its runlevel is
entered, and its `post:` script does not run on completion.
entered, and its `exec-stop-post` script does not run on completion.
With `remain:yes`, the task runs once and does not re-run on runlevel
re-entry:
With `remain-after-exit`, the task runs once and does not re-run on
runlevel re-entry:
task [2345] remain:yes /usr/sbin/setup-firewall -- Firewall setup
task firewall {
description = "Firewall setup"
runlevel = "2345"
remain-after-exit = true
command = "/usr/sbin/setup-firewall"
}
This has the following effects:
* The task does not re-run on runlevel re-entry
* The `post:` script runs when:
* The `exec-stop-post` script runs when:
- The task is explicitly stopped (`initctl stop NAME`)
- The task leaves its valid runlevels (e.g., runlevel change)
@@ -72,12 +80,16 @@ This is useful for tasks that set up persistent state where:
**Example:** Setting up firewall rules with cleanup on shutdown:
```
task [2345] remain:yes \
post:/usr/sbin/teardown-firewall \
/usr/sbin/setup-firewall -- Firewall setup
task firewall {
description = "Firewall setup"
runlevel = "2345"
remain-after-exit = true
exec-stop-post = "/usr/sbin/teardown-firewall"
command = "/usr/sbin/setup-firewall"
}
```
The firewall rules are created once. The `post:` script runs when
The firewall rules are created once. The `exec-stop-post` script runs when
entering runlevel 0 (halt) or 6 (reboot), or on explicit stop.
> [!NOTE]
+7 -3
View File
@@ -5,15 +5,19 @@ Finit comes with rudimentary support for templating, similar to that of
systemd. Best illustrated with an example:
$ initctl show avahi-autoipd@
service :%i avahi-autoipd --syslog %i -- ZeroConf for %i
service avahi-autoipd:%i {
description = "ZeroConf for %i"
command = "avahi-autoipd --syslog %i"
}
To enable ZeroConf for, e.g., `eth0`, use
$ initctl enable avahi-autoipd@eth0.conf
The enabled symlink will be set up to `avahi-autoipd@.conf` and every
instance of `%i` will in the instantiated directive be replaced with
`eth0`. Inspect the resulting instantiated template with `initctl show
instance of `%i` will be replaced with `eth0` before the file is
parsed, so it works in the block title, in any value, and in the
command line alike. Inspect the resulting instantiated template with `initctl show
avahi-autoipd:eth0` and check the status of a running instance with:
```
+44 -8
View File
@@ -19,7 +19,13 @@ The default baud rate is 0, i.e., keep kernel default.
**Example:**
tty [12345] /dev/ttyAMA0 115200 noclear vt220
tty ttyAMA0 {
runlevel = "12345"
device = "/dev/ttyAMA0"
baud = 115200
term = "vt220"
noclear = true
}
The second `tty` syntax variant is for using an external getty, like
agetty or the BusyBox getty.
@@ -38,8 +44,16 @@ the user to press enter before starting getty.
**Example:**
tty [12345] /sbin/getty -L 115200 /dev/ttyAMA0 vt100
tty [12345] /sbin/agetty -L ttyAMA0 115200 vt100 nowait
tty getty {
runlevel = "12345"
command = "/sbin/getty -L 115200 /dev/ttyAMA0 vt100"
}
tty agetty {
runlevel = "12345"
command = "/sbin/agetty -L ttyAMA0 115200 vt100"
nowait = true
}
The `noclear` option disables clearing the TTY after each session.
Clearing the TTY when a user logs out is usually preferable.
@@ -69,13 +83,21 @@ can be omitted to keep the kernel default.
**Example:**
tty [12345] @console noclear vt220
tty console {
runlevel = "12345"
device = "@console"
term = "vt220"
noclear = true
}
On really bare bones systems, or for board bringup, Finit can give you a
shell prompt as soon as bootstrap is done, without opening any device
node:
tty [12345789] notty
tty board {
runlevel = "12345789"
notty = true
}
This should of course not be enabled on production systems. Because it
may give a user root access without having to log in. However, for
@@ -83,7 +105,10 @@ board bringup and system debugging it can come in handy.
One can also use the `service` stanza to start a stand-alone shell:
service [12345] /bin/sh -l
service shell {
runlevel = "12345"
command = "/bin/sh -l"
}
Controlling TTY for Services
----------------------------
@@ -109,5 +134,16 @@ TTY device.
**Example:**
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
task [S] tty:@console my-setup-script -- Board bringup on console
service foo {
description = "Foo on serial console"
runlevel = "2345"
tty = "/dev/ttyS0"
command = "/usr/sbin/foo"
}
task setup {
description = "Board bringup on console"
runlevel = "S"
tty = "@console"
command = "my-setup-script"
}
+31 -8
View File
@@ -98,29 +98,52 @@ Some components are optional: runlevel(s), condition(s) and description,
making it easy to create simple start scripts and still possible for more
advanced uses as well:
service /usr/sbin/sshd -D
service sshd {
command = "/usr/sbin/sshd -D"
}
Dependencies are handled using [conditions](conditions.md). One of
the most common conditions is to wait for basic networking to become
available:
service <net/route/default> nginx -- High performance HTTP server
service nginx {
description = "High performance HTTP server"
conditions = { "net/route/default" }
command = "nginx"
}
Here is another example where we instruct Finit to not start BusyBox
`ntpd` until `syslogd` has started properly. Finit waits for `syslogd`
to create its PID file, by default `/var/run/syslogd.pid`.
service [2345] log <!pid/syslogd> ntpd -n -N -p pool.ntp.org
service [S12345] syslogd -n -- Syslog daemon
service ntpd {
runlevel = "2345"
conditions = { "pid/syslogd" }
reload-signal = "none"
log { }
command = "ntpd -n -N -p pool.ntp.org"
}
Notice the `log` keyword, BusyBox `ntpd` uses `stderr` for logging when
run in the foreground. With `log` Finit redirects `stdout` + `stderr`
to the system log daemon using the command line `logger(1)` tool.
service syslogd {
description = "Syslog daemon"
runlevel = "S12345"
command = "syslogd -n"
}
Notice the empty `log` block, BusyBox `ntpd` uses `stderr` for logging
when run in the foreground. With it Finit redirects `stdout` +
`stderr` to the system log daemon using the command line `logger(1)`
tool.
A service, or task, can have multiple dependencies listed. Here we wait
for *both* `syslogd` to have started and basic networking to be up:
service [2345] log <pid/syslogd,net/route/default> ntpd -n -N -p pool.ntp.org
service ntpd {
runlevel = "2345"
conditions = { "pid/syslogd", "net/route/default" }
log { }
command = "ntpd -n -N -p pool.ntp.org"
}
If either condition fails, e.g. loss of networking, `ntpd` is stopped
and as soon as it comes back up again `ntpd` is restarted automatically.
+9 -5
View File
@@ -212,13 +212,17 @@ A fourth group also exists, the `root` group. It is also _reserved_ and
primarily intended for RT tasks. If you have RT tasks they need to be
declared as such in their service stanza like this:
service [...] <...> cgroup.root /path/to/foo args -- description
service foo {
cgroup root {}
command = "/path/to/foo args"
}
or
Every block names the group it joins, so a second RT task says so too:
cgroup.root
service [...] <...> /path/to/foo args -- description
service [...] <...> /path/to/bar args -- description
service bar {
cgroup root {}
command = "/path/to/bar args"
}
See the [Cgroups](config/cgroups.md) section for more information, e.g.,
how to configure per-group limits.
+30 -9
View File
@@ -27,13 +27,22 @@ Finit can *not* start and monitor a daemon that:
### Forks to bg w/ PID file
There are two syntax variants, type 1 and type 2. The former is the
traditional one used also for `sysv` start/stop scripts, and the latter
is inspired by systemd, with a twist -- it lets Finit guess the pifdile
to look for based on the standard path and the basename of the command.
There are two variants. The former names the pidfile to watch, as for
`sysv` start/stop scripts, and the latter is inspired by systemd, with a
twist -- it lets Finit guess the pidfile based on the standard path and
the basename of the command.
service pid:!/run/serv.pid serv -- Forking service, type 1
service type:forking serv -- Forking service, type 2
service serv {
description = "Forking service, type 1"
pidfile = "/run/serv.pid"
command = "serv"
}
service serv {
description = "Forking service, type 2"
type = "forking"
command = "serv"
}
In this example the resulting files to watch for are `/run/serv.pid` and
`/var/run/serv.pid`, respectively. On most modern Linux systems this is
@@ -41,17 +50,29 @@ the same directory (`/var/run` is a symlink to `../run`).
### Runs in fg w/ PID file
service serv -n -p -- Foreground service w/ PID file
service serv {
description = "Foreground service w/ PID file"
command = "serv -n -p"
}
### Runs in fg w/o PID file
Same as previous, but we tell Finit to create the PID file, because we
need it to synchronize start/stop of a dependent service.
service pid:/run/serv.pid serv -n -- Foreground service w/o PID file
service serv {
description = "Foreground service w/o PID file"
pidfile = "/run/serv.pid"
pidfile-create = true
command = "serv -n"
}
### Runs in fg w/ custom PID file
service pid:/run/servy.pid serv -n -p -P /run/servy.pid -- Foreground service w/ custom PID file
service serv {
description = "Foreground service w/ custom PID file"
pidfile = "/run/servy.pid"
command = "serv -n -p -P /run/servy.pid"
}
[1]: config/service-sync.md