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 committed 2026-07-30 15:21:31 +02:00
1 parent ecac1e58e3
commit b9ad9bcb21
20 files changed
+790 -352

No files matched your search

+5 -1
View File
@@ -171,7 +171,11 @@ Notice the `--` separator.
To debug startup issues, in particular issues with getty/login, add To debug startup issues, in particular issues with getty/login, add
the following to your Finit .conf file: 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 The `notty` option ensures reusing the stdin/stdout set up by the
kernel. Remember, this is only for debugging and would leave your 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 during evaluation, i.e. all conditions must be satisfied in order for a
service to run. 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 - `~` -- 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 Finit guarantees by default that all run/tasks run (at least) once
per runlevel. For most tasks this is a good default, for example per runlevel. For most tasks this is a good default, for example
checking SSH host keys or loading keymap. However, for conditions 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 ### 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 In this example the Network monitor daemon `netd` is not started until
both the `pid/setupd` *and* `pid/zebra` conditions are satisfied. A 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 Another example is `dropbear`, it does not support `SIGHUP`, but we can
also see optional sourcing of arguments from an environment file: 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 Finally, the weird "block runlevel changes" example. Here we see what
happens when Finit receives `SIGPWR`, sent from a power daemon like happens when Finit receives `SIGPWR`, sent from a power daemon like
[powstatd(8)][]. A condition is asserted and a user can set up their [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 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 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 `~` For services that need to react when their upstream reloads, the `~`
prefix propagates the reload from the dependency: prefix propagates the reload from the dependency:
service <pid/svc_a> name:svc_b /sbin/svc_b -- Needs A (barrier) service svc_b {
service <!~pid/svc_b> name:svc_c /sbin/svc_c -- Propagate reload from 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`. 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 `!`, When `svc_b` reloads, `svc_c` will be restarted, because of
noreload) instead of merely resumed. If `svc_c` supported `SIGHUP` `reload-signal = "none"`, instead of merely resumed. If `svc_c`
(no `!` prefix), it would be sent `SIGHUP` instead. supported `SIGHUP`, it would be sent `SIGHUP` instead.
This is similar to systemd's `PropagatesReloadTo=` directive, but This is similar to systemd's `PropagatesReloadTo=` directive, but
declared on the consumer side rather than the provider side. 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:** **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 For convenience, prefixing with `usr/` is allowed, but any other slashes
or period characters are disallowed. E.g., to trigger the `Foo alarm`, 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 > "touch" or recreate, their PID file on `SIGHUP`. This can be done by
> calling `utimensat()` on the PID file. Provided, of course, that the > calling `utimensat()` on the PID file. Provided, of course, that the
> service supports reloading on `SIGHUP`, otherwise it will be restarted > service supports reloading on `SIGHUP`, otherwise it will be restarted
> by Finit when they instead exit on the signal. For such services, use > by Finit when they instead exit on the signal. For such services,
> `<!>` to tell Finit the service does not support `SIGHUP`. > set `reload-signal = "none"` to tell Finit the service does not
> support `SIGHUP`.
Built-in Conditions 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 Services with the `~` prefix are an exception to this rule: when their
conditions return to `on` after being in `flux`, the reload is propagated conditions return to `on` after being in `flux`, the reload is propagated
-- the service is reloaded (SIGHUP) or restarted (noreload `!`) instead -- the service is reloaded (SIGHUP), or restarted if it has
of simply being resumed. `reload-signal = "none"`, instead of simply being resumed.
Therefore, any plugin that supplies Finit with conditions must ensure Therefore, any plugin that supplies Finit with conditions must ensure
that their state is updated after each reconfiguration. This can be 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 Top-level Cgroup Definition
---------------------------- ----------------------------
**Syntax:** `cgroup NAME settings` **Syntax:** `cgroup NAME { settings }`
# Top-level cgroups and their default settings. All groups mandatory # Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup # but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are # 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the # as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names. # cgroup v2 controller default names.
cgroup init cpu.weight:100 cgroup init { cpu.weight = 100 }
cgroup user cpu.weight:100 cgroup user { cpu.weight = 100 }
cgroup system cpu.weight:9800 cgroup system { cpu.weight = 9800 }
Adding an extra cgroup `maint/` will require you to adjust the weight of 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 the above three. We leave `init/` and `user/` as-is reducing weight of
`system/` to 9700. `system/` to 9700.
cgroup system cpu.weight:9700 cgroup system { cpu.weight = 9700 }
# Example extra cgroup 'maint' # 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 By default, the `system/` cgroup is selected for almost everything. The
`init/` cgroup is reserved for PID 1 itself and its closest relatives. `init/` cgroup is reserved for PID 1 itself and its closest relatives.
The `user/` cgroup is for local TTY logins spawned by getty. 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 Every block says which group it joins, by name. An empty block joins
run/task/service directives in a `.conf` file, use the `cgroup.NAME` without changing anything:
directive as a standalone line:
cgroup.maint service foo {
service [...] <...> /path/to/foo args -- description cgroup maint {}
service [...] <...> /path/to/bar args -- description 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 foo {
service [...] <...> /path/to/foo args -- description cgroup maint {
cpu.max = 10000
memory.max = 655360
}
command = "/path/to/foo args"
}
Per-Service Cgroup Option > [!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`
**Syntax:** `cgroup.NAME[,options]` or `cgroup:options` (service option) > form that applies settings to whichever group is current without
> naming it. Neither has an equivalent here, by design: a block that
To override the cgroup for a specific service, use the `cgroup.NAME` > joins a group says so itself, so the group cannot depend on what came
option within the service directive: > earlier in the file.
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 `mem.` exception to the rule: every cgroup setting maps directly to 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`. 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 service defined in `system/10-hotplug.conf` would create a cgroup at
`/sys/fs/cgroup/system/10-hotplug/` by default. `/sys/fs/cgroup/system/10-hotplug/` by default.
To use a more descriptive name (recommended for clarity), you can specify To use a more descriptive name (recommended for clarity), set `name`
`name:` in the cgroup directive: 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. 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: Or with delegation:
service [2345] @podman:podman \ service podman {
cgroup.containers,name:podman,delegate,mem.max:4G \ description = "Podman API"
/usr/bin/podman system service -- 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 Cgroup Delegation
----------------- -----------------
For services that need to create their own child cgroups (container runtimes 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 \ service dockerd {
cgroup.system,delegate /usr/bin/dockerd -- Docker daemon description = "Docker daemon"
runlevel = "2345"
Or with the old colon syntax: user = "dockerd"
group = "dockerd"
service [2345] @dockerd:dockerd \ cgroup system { delegate = true }
cgroup.system:delegate /usr/bin/dockerd -- Docker daemon command = "/usr/bin/dockerd"
}
This allows the container runtime to: This allows the container runtime to:
@@ -159,32 +174,46 @@ When delegation is enabled, Finit:
**Requirements:** **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 - Controllers are delegated from the parent cgroup
**Example with additional config (new syntax):** **Example with additional config:**
service [2345] @podman:podman \ service podman {
cgroup.containers,delegate,mem.max:4G \ description = "Podman API"
/usr/bin/podman system service -- 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:** This delegates the cgroup while also setting a 4GB memory limit.
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.
**Container template example:** **Container template example:**
Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix) Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix)
for running rootful podman container instances using delegation: for running rootful podman container instances using delegation:
sysv log:prio:local1,tag:%i kill:30 pid:!/run/container:%i.pid \ sysv container:%i {
pre:0,/usr/sbin/container cleanup:0,/usr/sbin/container \ description = "container %i"
cgroup.system,delegate \ runlevel = "2345"
[2345] <!> :%i container -n %i -- container %i 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 This template uses `sysv` type with delegation, demonstrating that cgroup
delegation works with different service types, not just `service`. 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 and *shared with all* services -- the only way to have a service-local
environment is detailed in [Services Environment](service-env.md). environment is detailed in [Services Environment](service-env.md).
The syntax for global environment variables is straight forward. In Global environment variables go in an `environment` block, `env` for
Finit v4.4 the `set` keyword was added for completeness, but the old short:
syntax (without the `set ` prefix) is still honored:
set foo=bar environment {
set baz="qux" foo = "bar"
baz = "qux"
}
On reload of .conf files, all tracked environment variables are cleared 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 file, it will no longer be used by Finit or any new (!) started
run/tasks or services. The environment of already started processes can run/tasks or services. The environment of already started processes can
not be changed. not be changed.
+98 -13
View File
@@ -6,24 +6,109 @@ the navigation menu.
Configuration File Syntax Configuration File Syntax
-------------------------- --------------------------
The file format is line based, empty lines and comments, lines starting A `.conf` file is a series of blocks. Every setting is a key inside
with `#`, are ignored. A configuration directive starts with a keyword one, so nothing has to be remembered by position:
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:
```aconf ```aconf
# Escape \# chars if you want them literal in, e.g., descriptions service sysklogd {
service name:sysklogd [S123456789] \ description = "System log daemon"
env:-/etc/default/sysklogd \ runlevel = "S123456789"
syslogd -F $SYSLOGD_ARGS \ envfile = "-/etc/default/sysklogd"
-- System log daemon \# 1 # Comments allowed 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 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 runlevel `S`. Read on in [Files & Layout](files.md) for more on how
to structure your .conf files. to structure your .conf files.
+14 -3
View File
@@ -38,12 +38,19 @@ Log rotation is controlled using the global `log` setting.
**Example:** **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 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 stderr to a pipe connected to a logger process. Programs detect this as
non-interactive output (i.e., `isatty()` returns false) and typically non-interactive output (i.e., `isatty()` returns false) and typically
switch from line-buffered to fully-buffered mode. 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 messages appear delayed or batched, you can force line-buffered output
by wrapping the command with `stdbuf`: 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 The `-oL` option forces line-buffered output, and `-o0` forces unbuffered
output. See `stdbuf(1)` for details. 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: The bundled default `rescue.conf` contains nothing more than:
runlevel 1 runlevel 1
tty [12345] rescue tty rescue {
runlevel = "12345"
rescue = true
}
The `tty` has the `rescue` option set, which works similar to the board The `tty` has the `rescue` option set, which works similar to the board
bring-up tty option `notty`. The major difference being that `sulogin` 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: 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. There is no way to exit the *fallback* rescue mode.
+10 -2
View File
@@ -26,8 +26,16 @@ the lifetime of your system.
Example: Example:
task [S] /lib/console-setup/console-setup.sh task console-setup {
service [S12345] env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_ARGS 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 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 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`: * `/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 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 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 foo -n --extra-arg=bar -s -x
> [!NOTE] > [!NOTE]
> The leading `-` in `env:` determines if Finit should treat a missing > The leading `-` on `envfile` determines if Finit should treat a
> environment file as blocking the start of the service or not. When > missing environment file as blocking the start of the service or not.
> `-` is used, a missing environment file does *not* block the start. > 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 Service Options
=============== ===============
The run/task/tty/service/sysv directives take modifiers, or options, to The `run`, `task`, `tty`, `service` and `sysv` blocks take settings as
control their behavior. This section lists them with their limitations. keys. This section lists them with their limitations.
All modifiers must be placed between the directive and its command.
The name of a service, shown by the `initctl` tool, defaults to the The name of a service, shown by the `initctl` tool, is the block title.
basename of the service executable. It can be changed with the `name:` It defaults to the basename of the executable if the title is omitted:
option:
name:<service-name> service sshd {
command = "/usr/sbin/sshd -D"
}
For multiple instances of a service, with the same `name`, set the For multiple instances of a service, with the same name, add an
identifier `:ID` to prevent Finit from replacing previous instances: identifier after a colon to prevent Finit from replacing previous
instances:
service name:ssdp :eth1 ssdpd eth1 -- Windows discovery on eth1 service ssdp:eth1 { command = "ssdpd eth1" }
service name:ssdp :eth2 ssdpd eth2 -- Windows discovery on eth2 service ssdp:eth2 { command = "ssdpd eth2" }
The [`initctl`](../initctl.md) tool will list these two services as: 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 - ssdp:eth2
Conflicting services that must be prevented from starting, use the Conflicting services that must be prevented from starting, use the
`conflict:` option: `conflicts` key:
service [S12345789] udevd -- Device event management daemon service udevd { command = "udevd" }
run [S] conflict:udevd mdev -s -- Populating device tree 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 abc:1 { command = "abc 1" }
service :2 abc service abc:2 { command = "abc 2" }
service conflict:abc:1,abc:2 cde service cde { conflicts = { "abc:1", "abc:2" } command = "cde" }
If a service should not be automatically started, it can be configured If a service should not be automatically started, set `manual-start`.
as manual with the optional `manual` argument. The service can then be It can then be started at any time with `initctl start <service>`:
started at any time by running `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 * `capabilities` -- see the [Linux Capabilities](capabilities.md) section
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section * `cgroup NAME {}` -- see the [Cgroups](cgroups.md) section
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section * `envfile` -- see the [Service Environment](service-env.md) section
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output) * `log {}` -- see [Redirecting Output](logging.md#redirecting-output)
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty) * `tty` -- see [Controlling TTY](tty.md#controlling-tty)
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading) * `notify` -- see [Service Synchronization](service-sync.md)
* `notify:...` -- see [Service Synchronization](service-sync.md) * `if` -- see [Conditional Execution](services.md#conditional-execution)
* `if:...` -- see [Conditional Execution](services.md#conditional-execution) * `type = "forking"` -- see description of the [service](services.md) block
* `type:forking` -- see description of the [service](services.md) directive * a leading `-` on `command` -- see [Conditional Loading](services.md#conditional-loading)
As mentioned previously, services are automatically started, restarted, Restarting
and stopped, depending on the configuration and conditions. Within the ----------
confines of that the following options are available:
* `restart:NUM` -- number of times Finit tries to restart a crashing Services are automatically started, restarted, and stopped, depending
service, default: 10, max: 255. When this limit is reached the on the configuration and conditions. Within the confines of that the
service is marked *crashed* and must be restarted manually with following settings are available:
`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`
> [!CAUTION] * `restart-max = NUM` -- number of times Finit tries to restart a
> Both `reload:script` and `stop:script` are called as PID 1, without crashing service, default: 10, max: 255. When this limit is
> any timeout! Meaning, it is up to you to ensure the script is not reached the service is marked *crashed* and must be restarted
> blocking for seconds at a time or never terminates. 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 Stopping and reloading
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`.
Services, including the `sysv` variant, support pre/post/ready and When stopping a service, either manually or when moving to another
cleanup scripts: 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 * `stop-signal = "SIGPWR"` -- send this instead of `SIGTERM`
* `post:[0-3600,]script` -- called after the sysv/service has stopped * `stop-timeout = 10` -- seconds to wait before `SIGKILL`, 1-300
* `ready:[0-3600,]script` -- called when the sysv/service is ready
* `cleanup:[0-3600,]script` -- called when run/task/sysv/service is removed
The optional number (0-3600) is the timeout before Finit kills the Some services need more than a signal:
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 * `exec-stop = "script [args]"` -- run instead of sending the stop
service itself, with any `env:file` sourced. The scripts are executed signal, and instead of `stop` for `sysv`
from the `$HOME` of the given user. The scripts are not called with any * `exec-reload = "script [args]"` -- run instead of sending `SIGHUP`.
argument, but get a set of environment variables: 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_IDENT=foo:1`
* `SERVICE_NAME=foo` * `SERVICE_NAME=foo`
* `SERVICE_ID=1` * `SERVICE_ID=1`
The `post:script` is called with an additional set of environment The `exec-stop-post` script is called with an additional set of
variables. Yes, the text is correct, the naming was an accident: environment variables. Yes, the text is correct, the naming was an
accident:
- `EXIT_CODE=[exited,signal,crashed]`: normal exit, signaled, or - `EXIT_CODE=[exited,signal,crashed]`: normal exit, signaled, or
crashed 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`, the program, if it exited normally, or the signal name (`HUP`,
`TERM`, etc.) if it exited due to signal `TERM`, etc.) if it exited due to signal
When a run/task/sys/service is removed (disable + reload) it is first When a run/task/sysv/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `post:script` always stopped and then removed from the runlevel. The `exec-stop-post`
runs when the process has stopped, and the `cleanup:script` runs when script always runs when the process has stopped, and `exec-cleanup`
the the stanza has been removed from the runlevel. runs when the stanza has been removed from the runlevel.
> [!IMPORTANT] > [!IMPORTANT]
> These script actions are intended for setup, cleanup, and readiness > 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, every service needs to explicitly declare their readiness notification,
like this: like this:
service notify:pid watchdogd service watchdogd { notify = "pid" command = "watchdogd" }
service notify:systemd foo service foo { notify = "systemd" command = "foo" }
service notify:s6 bar service bar { notify = "s6" command = "bar" }
service notify:none qux service qux { notify = "none" command = "qux" }
The `notify:none` syntax is for completeness in systems which run in The `notify:none` syntax is for completeness in systems which run in
`readiness pid` mode (default). Services declared with `notify:none` `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: To synchronize two services the following condition can be used:
service notify:pid watchdogd service watchdogd {
service <service/watchdogd/ready> stress-ng --cpu 8 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. 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`: * `/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:` * `/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 However, not all daemons support running in the foreground, or they may
start logging to the foreground as well, these are forking daemons and start logging to the foreground as well, these are forking daemons and
are supported using the same syntax as forking `sysv` services, using are supported using the same syntax as forking `sysv` services, by
the `pid:!/path/to/pidfile.pid` syntax. There is an alternative syntax naming the file to watch with `pidfile`. There is an alternative that
that may be more intuitive, where Finit can also guess the PID file may be more intuitive, where Finit can also guess the PID file based on
based on the daemon's command name: 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 This example lets BusyBox `ntpd` daemonize itself. Finit uses the
basename of the binary to guess the PID file to watch for the PID: 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 `/var/run/ntpd.pid`. If Finit guesses wrong, name the file yourself
full `pid:!/path/to/file.pid`. with `pidfile = "/path/to/file.pid"`.
With `pid:!/path`, the file belongs to the service: Finit reads it The file belongs to the service: Finit reads it but does not create or
but does not create or remove it. The one exception is *stale* 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 cleanup — if the service dies without removing its own pidfile
(SIGKILL, OOM, segfault), and the file still names the just-reaped (SIGKILL, OOM, segfault), and the file still names the just-reaped
PID, Finit removes it before the next retry. This prevents daemons 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 In the case of `ospfd` (below), we omit the `-d` flag (daemonize) to
prevent it from forking to the background: 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 `runlevel` denotes the runlevels `ospfd` is allowed to run in, it is
optional and default to level 2-5 if omitted. optional and defaults to level 2-4 if omitted.
`<...>` is the condition for starting `ospfd`. In this example Finit `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 waits for another service, `zebra`, to have created its PID file in
`/var/run/quagga/zebra.pid` before starting `ospfd`. Finit watches `/var/run/quagga/zebra.pid` before starting `ospfd`. Finit watches
*all* files in `/var/run`, for each file named `*.pid`, or `*/pid`, *all* files in `/var/run`, for each file named `*.pid`, or `*/pid`,
Finit opens it and find the matching `NAME:ID` using the 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) conditions = { "~pid/zebra" }
- `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd`
- `<!~pid/zebra>` -- both: noreload and propagate reload
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 Some services do not maintain a PID file and rather than patching each
application Finit provides a workaround. A `pid` keyword can be set application Finit provides a workaround. With `pidfile-create` Finit
to have Finit automatically create (when starting) and later remove creates the file when starting and removes it when stopping. The path
(when stopping) the PID file. The file is created in the `/var/run` comes from `pidfile`, which takes three forms:
directory using the `basename(1)` of the service. The default can be
modified with an optional `pid:`-argument:
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`, Such a file is also used by the Finit condition subsystem, so another
that PID file will, not only be created and removed automatically, but service, run or task can depend on `pid/bar`. Here foo is not started
also be used by the Finit condition subsystem. So a service/run/task until bar has:
can depend on `<pid/bar>`, like this foo will not be started until bar
has started:
service pid:/run/bar.pid bar -- Bar Service service bar {
service <pid/bar> foo -- Foo Service 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 Needless to say, it is better if `bar` creates its own PID file when it
has completed starting up and is ready for service. has completed starting up and is ready for service.
As an alternative "readiness" notification, Finit supports both systemd As an alternative "readiness" notification, Finit supports both systemd
and s6 style notification. This can be enabled by using the `notify` and s6 style notification. This is enabled with the `notify` key:
option:
* `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 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 service events. The [sd_notify()][] API expects `NOTIFY_SOCKET`
to be set to the socket where the application can send `"READY=1\n"` to be set to the socket where the application can send `"READY=1\n"`
when it is starting up or has processed a `SIGHUP`. 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"` systemd notification, [s6 expect][] compliant daemons to send `"\n"`
and then close their socket. Finit takes care of "hard-wiring" the and then close their socket. Finit takes care of "hard-wiring" the
READY state as long as the application is running, events across any 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 (must be >3) on then command line, Finit provides the following
syntax (`%n` is replaced by Finit with then descriptor number): 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 [sd_notify()]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
[s6 expect]: https://skarnet.org/software/s6/notifywhenup.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: 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`. 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 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 Every `run`, `task`, or `service` can also list the privileges the
`/path/to/cmd` should be executed with. Prefix the command with command should be executed with, using `user`, `group` and
`@USR[:GRP[,SUPP,...]]`, where group and supplementary groups are `extra-groups`, all optional:
optional, like this:
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` Finit reads the user's supplementary group membership from `/etc/group`
automatically. Any groups the user belongs to will be inherited by automatically. Any groups the user belongs to will be inherited by
the service. the service.
To specify additional supplementary groups beyond those in `/etc/group`, To specify additional supplementary groups beyond those in
append them after the primary group, separated by commas: `/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 This runs the `caddy` service as user `caddy`, with primary group
`caddy`, inheriting any groups `caddy` is a member of in `/etc/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`. 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 For multiple instances of the same command, e.g. a DHCP client or
multiple web servers, add `:ID` somewhere between the `run`, `task`, multiple web servers, add `:ID` to the block title, like this:
`service` keyword and the command, like this:
service :80 [2345] httpd -f -h /http -p 80 -- Web server service httpd:80 {
service :8080[2345] httpd -f -h /http -p 8080 -- Old web server description = "Web server"
runlevel = "2345"
command = "httpd -f -h /http -p 80"
}
Without the `:ID` to the service the latter will overwrite the former service httpd:8080 {
and only the old web server would be started and supervised. 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 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. take from the `system/hotplug.conf` file in the Finit distribution.
Here we only show a simplified subset. 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 udevd {
service nowarn name:udevd pid:udevd udevd pidfile = "udevd"
command = "-/lib/systemd/systemd-udevd"
}
When loading the .conf file Finit looks for `/lib/systemd/systemd-udevd` service udevd {
if that is not found Finit automatically logs a warning. The `nowarn` pidfile = "udevd"
option disables this warning so that the second line can be evaluated, 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`. 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, 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 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 until the PID condition is asserted, i.e., Finit detecting udevd has
started. 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 If `udevd` is not available, we try to run `mdev`, but if that is not
found, again we do not warn. found, again we do not warn.
Conditional loading statements can also be negated, so the previous stanza Conditional loading statements can also be negated, so the previous
can also be written as: 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 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 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. Conditional execution qualify a run/task/service based on a condition.
Consider this (simplified) example from the Infix operating system: Consider this (simplified) example from the Infix operating system:
run [S] name:startup <pid/sysrepo> confd -b --load startup-config run startup {
run [S] if:<usr/fail-startup> name:failure <pid/sysrepo> confd --load failure-config 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 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 in true sequence. If loading the file `startup-config` fails confd sets
the condition `usr/fail-startup`, thus allowing the next run statement the condition `usr/fail-startup`, thus allowing the next run statement
to load `failure-config`. to load `failure-config`.
Notice the critical difference between the `<pid/sysrepo>` condition and Notice the critical difference between the `conditions` list and `if`.
`if:<usr/fail-startup>`. The former is a condition for starting and the The former is a condition for starting; the latter is a condition to
latter is a condition to check if a run/task/service is qualified to check whether a run/task/service is qualified to even be considered.
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 Conditional execution statements can also be negated, so provided the
file loaded did the opposite, i.e., set a condition on success, the file loaded did the opposite, i.e., set a condition on success, the
previous stanza can also be written as: 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. `service` stanzas work.
Forking services started with `sysv` scripts can be monitored by Finit Forking services started with `sysv` scripts can be monitored by Finit
by declaring the PID file to look for: `pid:!/path/to/pidfile.pid`. by declaring the PID file to look for: `pidfile = "/path/to/file.pid"`.
Notice the leading `!`, it signifies Finit should not try to create the Finit does not create that file, it watches it for the resulting
file, but rather watch that file for the resulting forked-off PID. This 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 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`. 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. For syntax details, see [SysV Init Scripts](#sysv-init-scripts), above.
Here follows an example taken from a Debian installation: 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 The init script header could be parsed to extract `Default-Start:` and
other parameters for the `sysv` command to Finit. There is currently no 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 Both `run` and `task` commands are run in a shell, so basic pipes and
redirects can be used: 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 Please note, `;`, `&&`, `||`, and similar are *not supported*. Any
non-trivial constructs are better placed in a separate shell script. 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 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 With `remain-after-exit`, the task runs once and does not re-run on
re-entry: 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: This has the following effects:
* The task does not re-run on runlevel re-entry * 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 is explicitly stopped (`initctl stop NAME`)
- The task leaves its valid runlevels (e.g., runlevel change) - 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: **Example:** Setting up firewall rules with cleanup on shutdown:
``` ```
task [2345] remain:yes \ task firewall {
post:/usr/sbin/teardown-firewall \ description = "Firewall setup"
/usr/sbin/setup-firewall -- 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. entering runlevel 0 (halt) or 6 (reboot), or on explicit stop.
> [!NOTE] > [!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: systemd. Best illustrated with an example:
$ initctl show avahi-autoipd@ $ 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 To enable ZeroConf for, e.g., `eth0`, use
$ initctl enable avahi-autoipd@eth0.conf $ initctl enable avahi-autoipd@eth0.conf
The enabled symlink will be set up to `avahi-autoipd@.conf` and every The enabled symlink will be set up to `avahi-autoipd@.conf` and every
instance of `%i` will in the instantiated directive be replaced with instance of `%i` will be replaced with `eth0` before the file is
`eth0`. Inspect the resulting instantiated template with `initctl show 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: 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:** **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 The second `tty` syntax variant is for using an external getty, like
agetty or the BusyBox getty. agetty or the BusyBox getty.
@@ -38,8 +44,16 @@ the user to press enter before starting getty.
**Example:** **Example:**
tty [12345] /sbin/getty -L 115200 /dev/ttyAMA0 vt100 tty getty {
tty [12345] /sbin/agetty -L ttyAMA0 115200 vt100 nowait 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. The `noclear` option disables clearing the TTY after each session.
Clearing the TTY when a user logs out is usually preferable. Clearing the TTY when a user logs out is usually preferable.
@@ -69,13 +83,21 @@ can be omitted to keep the kernel default.
**Example:** **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 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 shell prompt as soon as bootstrap is done, without opening any device
node: node:
tty [12345789] notty tty board {
runlevel = "12345789"
notty = true
}
This should of course not be enabled on production systems. Because it 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 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: 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 Controlling TTY for Services
---------------------------- ----------------------------
@@ -109,5 +134,16 @@ TTY device.
**Example:** **Example:**
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console service foo {
task [S] tty:@console my-setup-script -- Board bringup on console 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 making it easy to create simple start scripts and still possible for more
advanced uses as well: 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 Dependencies are handled using [conditions](conditions.md). One of
the most common conditions is to wait for basic networking to become the most common conditions is to wait for basic networking to become
available: 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 Here is another example where we instruct Finit to not start BusyBox
`ntpd` until `syslogd` has started properly. Finit waits for `syslogd` `ntpd` until `syslogd` has started properly. Finit waits for `syslogd`
to create its PID file, by default `/var/run/syslogd.pid`. to create its PID file, by default `/var/run/syslogd.pid`.
service [2345] log <!pid/syslogd> ntpd -n -N -p pool.ntp.org service ntpd {
service [S12345] syslogd -n -- Syslog daemon 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 service syslogd {
run in the foreground. With `log` Finit redirects `stdout` + `stderr` description = "Syslog daemon"
to the system log daemon using the command line `logger(1)` tool. 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 A service, or task, can have multiple dependencies listed. Here we wait
for *both* `syslogd` to have started and basic networking to be up: 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 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. 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 primarily intended for RT tasks. If you have RT tasks they need to be
declared as such in their service stanza like this: 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 bar {
service [...] <...> /path/to/foo args -- description cgroup root {}
service [...] <...> /path/to/bar args -- description command = "/path/to/bar args"
}
See the [Cgroups](config/cgroups.md) section for more information, e.g., See the [Cgroups](config/cgroups.md) section for more information, e.g.,
how to configure per-group limits. 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 ### Forks to bg w/ PID file
There are two syntax variants, type 1 and type 2. The former is the There are two variants. The former names the pidfile to watch, as for
traditional one used also for `sysv` start/stop scripts, and the latter `sysv` start/stop scripts, and the latter is inspired by systemd, with a
is inspired by systemd, with a twist -- it lets Finit guess the pifdile twist -- it lets Finit guess the pidfile based on the standard path and
to look for based on the standard path and the basename of the command. the basename of the command.
service pid:!/run/serv.pid serv -- Forking service, type 1 service serv {
service type:forking serv -- Forking service, type 2 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 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 `/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 ### 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 ### Runs in fg w/o PID file
Same as previous, but we tell Finit to create the PID file, because we Same as previous, but we tell Finit to create the PID file, because we
need it to synchronize start/stop of a dependent service. 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 ### 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 [1]: config/service-sync.md