240 lines
13 KiB

Usage of slew
In addition to the functionalities provided by s6/s6-rc, distributions often
want the following features in an init/rc framework:
* (Nearly) ready-for-use definitions for longruns and oneshots.
* Instanced supervision (eg. `dhcpc.eth0' / `dhcpc.wlan0').
* Optional dependencies (like those in [1]).
* Package-specific automatic manipulation of definitions.
[1] <>.
While there are projects that provide longrun definitions, oneshots and
service dependencies are largely unconsidered; and while some of them (eg.
supervision-scripts [2]) provide templates for `./run' scripts, we also want to
be able to generate definitions for logging services as well as corresponding
`./producer-for' / `./consumer-for' files in an automated way.
[2] <>.
Fortunately, due to the filesystem-based input interface of s6/s6-rc (and other
daemontools-like init/rc frameworks in general), it is very easy to write
preprocessors for s6-rc service definitions using a combination of well-known
Unix utilities. And by using a multi-pass preprocessing scheme like the
nanopass framework [3] (note the similar relations between scheme <-> nanopass,
process attributes <-> Bernstein chainloading and service definitions <-> s6-rc
preprocessors), we are able to not only write the preprocessor as tiny passes,
but also ship package-specific passes that can be plugged into the preprocessor.
[3] <>.
During the preparation of this project, it quickly became obvious that execline
is, while useful for writing stage 1/3 scripts, not really expressive enough for
some `./run' / `./finish' scripts (eg. those for instanced supervision), let
alone oneshots which are often even more complex. For this reason, the shell is
chosen as the language for most scripts in this project; and to avoid some
problems inherent in the Bourne shell [4], slew uses rc(1).
[4] <>.
The booting and halting scripts
The `init' and `run' directories contain the init system of slew, and shall be
installed at `/etc/slew'. The actual stage 1 init executable is `init/rc.boot',
which can be linked from `/sbin/init' or specified on the kernel command line.
`rc.boot' exec()s into `s6-svscan' (stage 2), which exec()s into its `finish'
script linked to `init/rc.halt', the stage 3 executable, on shutdown. At the
beginning and end of stage 2, `init/rc.init' and `init/rc.fin' are executed
respectively. The `crash' script of `s6-svscan' is linked to `init/rc.crash'.
When `rc.fin' is executed at the end of stage 2, its $1 will be set to `reboot',
`halt' or `poweroff' depending on the signal handler that triggered its execution
(cf. `run/service/.s6-svscan').
Before exec()ing into `s6-svscan', `rc.boot' copies everything in
`/etc/slew/run' into `/run' as is (so if your distribution needs `/run/sshd'
for sshd to run, you can create an empty `sshd' directory in `/etc/slew/run',
perhaps with necessary permission restrictions), and sets the environment using
`s6-envdir' according to the `/etc/slew/env' directory if the latter exists.
The catch-all logger is located at `/run/service/s6-svscan.log', and logs to
`/run/uncaught-logs' as the `_catchlog' user.
Apart from doing other well-known tasks, `rc.halt' also triggers the
`init/save_log.rc' s6-log processor to save logs from `/run/uncaught-logs' to
the `/var/log/init' directory before unmounting filesystems. The `local'
oneshot shipped with slew (see below for more details) takes care of triggering
`init/save_log.rc' after the booting procedure is mostly completed.
`rc.init' requires the compiled s6-rc service database to be located at (or
linked to) `/etc/slew/db/compiled', and to contain two bundles called `default'
and `consoles'; `consoles' should contain the getty services, and `default'
should contain all the rest that are expected to be started automatically during
Unlike s6-linux-init, slew follows the busybox init(8) signal convention, and
`SIGUSR1' and `SIGUSR2' sent to `s6-svscan' will respectively trigger halt and
poweroff, instead of the other way around. Additionally, if some service(s)
fail to start during booting, and `/run/slew/reboot' exists, `rc.init' will
trigger reboot automatically; this can be useful for certain scenarios (eg.
when required by fsck(8)) where automatic reboot is desirable.
slew uses a configuration file, `lib/slew.rc', that controls some (but not all)
aspects of init/rc system in a centralised way (like `rc.conf' in some other
init/rc systems); cf. the comments therein for detailed usage of it.
Service definitions and the preprocessor
The `base', `main' and `lib' directories contain the service definitions, the
preprocessor, as well as ancillary utilities and configuration files; they shall
be installed at `/etc/slew', perhaps with unused service definitions (and
related files) omitted. The `base' directory contains the basic definitions,
which are linked into the `main' directory; the `lib' directory contains the
preprocessor, ancillary utilities and configuration files including `slew.rc'.
The `main' directory additionally contains the bundles `machine', `services'
`default', `consoles', and some bundles which are used by the preprocessor. The
`default' bundle shipped with slew depends on `machine' and `services', as well
as the `local' oneshot which itself depends on `machine' and `services'; in this
way, slew can somewhat emulate sysvinit runlevels. In order to customise the
list of automatically started services, the user would most often need to modify
the contents of the `services' bundle; by the way, the `network' and `local'
oneshots in `base' are also among the most customised services.
`lib/prep.rc' is the service definition preprocessor, and is invoked like:
# cp -rL /etc/slew/main /etc/slew/main.prep
# /etc/slew/lib/prep.rc /etc/slew/lib/prep /etc/slew/main.prep
What `prep.rc' does is simply running, on the $2 directory, all executables with
the `.rc' extension in the $1 directory in lexical order, and each script is a
single preprocessing pass; if it finishes successfully, the $2 directory would
contain service definitions suitable for use with `s6-rc-compile'.
The preprocessing passes follow certain rules: all files used solely by these
passes shall be either in the `.prep' subdirectory of $1 (for global settings)
or in the `prep' subdirectory of a service directory in $1 (for per-service
settings). It is also recommended that every pass chdir()s to its $1, and use
plain text files containing text like `../base/s6log.' (relative to $1) instead
of symbolic links when referencing external files and directories; to understand
the rationale for this, cf. `lib/prep/10-multi.rc'. For the detailed usage of
individual passes, use (cf. `lib/prepdoc.rc' for the documentation format):
# /etc/slew/lib/prepdoc.rc /etc/slew/lib/prep
Along with the preprocessor, also shipped with slew are "libraries" located
in `lib' that abstract common patterns, which would otherwise result in
boilerplates in the service definitions: eg. see `lib/srv.rc' (in combination
with eg. `base/getty.') for how to do instanced supervision, and `lib/rate.rc'
(in combination with eg. `base/sshhole.') for how to play cybernetics with
services that can sometimes repeatedly exit due to transient failures.
Package-specific files and some slew conventions
The `pkg' directory contains package-specific files, eg. `pkg/openvpn' for files
related to openvpn; the structure of these package directories resemble the
`/etc/slew', except for their `misc' subdirectories, which contain ancillary
files (like patches and non-slew configuration files) and are not to be
installed into `/etc/slew'. The service definitions under the root of this
repository are the core services provided by slew, and assume the presence of
busybox (along with necessary applets) on the system; services named `firewall'
and `sshd' are set as optional dependencies (cf. `lib/prep/80-odep.rc')
respectively for `network' and `services', and can be respectively linked to
`nftables' and `openssh' when they are installed.
Although s6-rc bundles can be used to implement polymorphism (eg. a `keymap'
bundle that contains either `keymap.kbd' or `keymap.busybox'), slew prefers to
use links (eg. `main/keymap' linked to `base/keymap.busybox'), because service
directories in `base' are linked into `main' anyway. However, a bundle might
still be necessary when it provides a polymorphic interface that happens
to not require anything to be run (eg. `drivers.udev' in comparison with
`drivers.mdev'). Do note that while polymorphic interfaces are not necessarily
implementation-agnostic, for example `devices.mdev' cannot be used with
`drivers.udev'; fortunately, these cases are rare, and sane distributions would
not encourage users to mix incompatible services. By the way, for an example
on how to implement polymorphic "methods", cf. the `lib/fn' directory, the
`udhcpc.' service and the `pkg/wpa_supplicant/misc/wpa_cli.rc' script.
Most scripts shipped with slew are quite strict regarding system or human
errors, eg. most executable `.rc' scripts use the `/bin/rc -e' shebang line;
however, in order to provide a reasonable level of fault tolerance, some scripts
are intentionally more permissive. The current rules are that:
* `./run' scripts for longruns should, in principle, be strict.
* Services critical for the system and hard dependencies of other services
(roughly equivalent to the `need'ed services in OpenRC) should be strict.
* If some failures in certain parts of the supposedly strict scripts can be
nonfatal, these failures shall be handled explicitly, cf. `origin' and `fsck'.
* Inessential services or soft dependencies of other services (roughly
equivalent to the `after'ed services in OpenRC) can be more permissive.
Mistakes to avoid
In general, there are few ways one can mess up a system based on s6/s6-rc;
nevertheless, sometimes the symptoms are quite obscure and it is not easy to
find the culprit at once. Here are some common mistakes that might have obscure
symptoms; avoid them, and your system will probably be well.
* s6 brings major command line interface breakage [5] (you are strongly
recommended to read the page whenever the major version number of s6/s6-rc
changes), and users with older s6 should run the following commands to adapt
slew to the legacy interface:
$ sed -i 's/-t0/-st0/' init/rc.boot
$ sed -i 's/-x /-X /' init/rc.fin
$ cd run/service/.s6-svscan;
sed -i 's@b /run@ /run@' SIGQUIT SIGTERM SIGUSR1 SIGUSR2
[5] <>.
* Due to its implementation [6], s6 before version did not handle
backward time jump well, and required the clock to be not too far in the
future when it was running. Therefore, slew initialises the system clock in
`rc.boot' instead of the beginning of stage 2. Of course, it is also
essential for the user to set $clock correctly in `slew.rc'.
[6] <>.
* As was noted in [7], a live s6-rc service database should not be tampered
with, and it is wrong to delete the live database, replace it with a new one,
and then run `s6-rc-update /etc/slew/db/compiled'. For an alternative, cf.
`lib/build.rc'; but note that the scripts depend on some internal details of
s6-rc service databases, which are undocumented as of now.
[7] <>.
* Due to some implementation details [8] of `redirfd -wnb', it must be invoked
before `background' in `rc.boot', or race conditions might occur. (Users who
use `rc.boot' as is will not encounter this issue, but distributions might
customise the scripts in slew, so this note is still necessary.)
[8] <>.
The following are not strictly related to s6/s6-rc, but might still be useful:
* Implementing syslogd with `s6-ipcserver' and `ucspilogd' (cf.
`base/syslog.ucspi') is an extremely elegant idea, but it depends on syslog()
using `SOCK_STREAM' sockets, which is not the case with musl [9]. For this
reason, alternative services (eg. `base/syslog.busybox') are also shipped with
[9] <>.
* The rules for automatic addition of `^' for string concatenation in rc(1) can
be hard to grasp; so when unsure, write `^'s explicitly. In v1.7.4 and
earlier versions of Rakitzis rc(1), `=' in strings must be quoted; this
limitation is gone [10] in newer versions, but the new versions are not yet
widely adopted.
[10] <>.
* Certain daemons (eg. openssh) use setproctitle() or similar tricks to change
the title of their processes, and this seems to be broken with an empty
enviroment. Therefore, all longruns in slew are run with $PATH preserved.