From b9ad9bcb21e9ebb5e66eb2948192a53e08f80a4a Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Jul 2026 11:39:38 +0200 Subject: [PATCH] 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 --- doc/build.md | 6 +- doc/conditions.md | 77 ++++++++--- doc/config/cgroups.md | 189 +++++++++++++++------------ doc/config/env.md | 13 +- doc/config/index.md | 111 ++++++++++++++-- doc/config/logging.md | 17 ++- doc/config/rescue.md | 10 +- doc/config/runlevels.md | 12 +- doc/config/service-env.md | 15 ++- doc/config/service-opts.md | 232 +++++++++++++++++++-------------- doc/config/service-sync.md | 19 ++- doc/config/service-wrappers.md | 7 +- doc/config/services.md | 230 ++++++++++++++++++++++---------- doc/config/sysv.md | 14 +- doc/config/task-and-run.md | 36 +++-- doc/config/templating.md | 10 +- doc/config/tty.md | 52 ++++++-- doc/example.md | 39 ++++-- doc/features.md | 14 +- doc/service.md | 39 ++++-- 20 files changed, 790 insertions(+), 352 deletions(-) diff --git a/doc/build.md b/doc/build.md index 6950fc8a..ae25f7b8 100644 --- a/doc/build.md +++ b/doc/build.md @@ -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 diff --git a/doc/conditions.md b/doc/conditions.md index aac0eadf..036278d6 100644 --- a/doc/conditions.md +++ b/doc/conditions.md @@ -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] /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] 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 name:svc_b /sbin/svc_b -- Needs A (barrier) - service 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] 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 diff --git a/doc/config/cgroups.md b/doc/config/cgroups.md index 89512799..6e96824f 100644 --- a/doc/config/cgroups.md +++ b/doc/config/cgroups.md @@ -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`. diff --git a/doc/config/env.md b/doc/config/env.md index 1868a976..b4064a0d 100644 --- a/doc/config/env.md +++ b/doc/config/env.md @@ -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. diff --git a/doc/config/index.md b/doc/config/index.md index 86621d3a..7f11d84d 100644 --- a/doc/config/index.md +++ b/doc/config/index.md @@ -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. diff --git a/doc/config/logging.md b/doc/config/logging.md index 5220a465..57427412 100644 --- a/doc/config/logging.md +++ b/doc/config/logging.md @@ -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. diff --git a/doc/config/rescue.md b/doc/config/rescue.md index 4f70cff7..a690c150 100644 --- a/doc/config/rescue.md +++ b/doc/config/rescue.md @@ -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. \ No newline at end of file diff --git a/doc/config/runlevels.md b/doc/config/runlevels.md index 5246fd45..16f95085 100644 --- a/doc/config/runlevels.md +++ b/doc/config/runlevels.md @@ -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 diff --git a/doc/config/service-env.md b/doc/config/service-env.md index d298d400..6c7f0817 100644 --- a/doc/config/service-env.md +++ b/doc/config/service-env.md @@ -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. diff --git a/doc/config/service-opts.md b/doc/config/service-opts.md index add707bb..e5bb5bfd 100644 --- a/doc/config/service-opts.md +++ b/doc/config/service-opts.md @@ -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 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 `. +If a service should not be automatically started, set `manual-start`. +It can then be started at any time with `initctl start `: - 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:` -- 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. diff --git a/doc/config/service-sync.md b/doc/config/service-sync.md index deaa5059..46aabd15 100644 --- a/doc/config/service-sync.md +++ b/doc/config/service-sync.md @@ -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 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. diff --git a/doc/config/service-wrappers.md b/doc/config/service-wrappers.md index 327fd756..f0d125a8 100644 --- a/doc/config/service-wrappers.md +++ b/doc/config/service-wrappers.md @@ -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:` diff --git a/doc/config/services.md b/doc/config/services.md index a65b037c..7dbe49b7 100644 --- a/doc/config/services.md +++ b/doc/config/services.md @@ -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] /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: - - `` -- `ospfd` does not support `SIGHUP` (noreload) - - `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd` - - `` -- 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/.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 ``, 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 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] @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 :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 confd -b --load startup-config - run [S] if: name:failure confd --load failure-config + run startup { + runlevel = "S" + conditions = { "pid/sysrepo" } + command = "confd -b --load startup-config" + } + + run failure { + runlevel = "S" + if = "" + 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 `` condition and -`if:`. 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: name:failure confd ... + run failure { + runlevel = "S" + if = "" + conditions = { "pid/sysrepo" } + command = "confd ..." + } diff --git a/doc/config/sysv.md b/doc/config/sysv.md index 4167a500..e9378bfb 100644 --- a/doc/config/sysv.md +++ b/doc/config/sysv.md @@ -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] /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 diff --git a/doc/config/task-and-run.md b/doc/config/task-and-run.md index 1ec06223..9f4994c6 100644 --- a/doc/config/task-and-run.md +++ b/doc/config/task-and-run.md @@ -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] diff --git a/doc/config/templating.md b/doc/config/templating.md index f55a0335..4faed67f 100644 --- a/doc/config/templating.md +++ b/doc/config/templating.md @@ -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: ``` diff --git a/doc/config/tty.md b/doc/config/tty.md index 1ffaae07..fd0ee76a 100644 --- a/doc/config/tty.md +++ b/doc/config/tty.md @@ -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 \ No newline at end of file + 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" + } \ No newline at end of file diff --git a/doc/example.md b/doc/example.md index 1708cb35..527961e5 100644 --- a/doc/example.md +++ b/doc/example.md @@ -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 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 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 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. diff --git a/doc/features.md b/doc/features.md index 82e5fe78..5f604a77 100644 --- a/doc/features.md +++ b/doc/features.md @@ -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. diff --git a/doc/service.md b/doc/service.md index 31235701..e4d37d44 100644 --- a/doc/service.md +++ b/doc/service.md @@ -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