Skip to content

s6 init

Runink River boots with s6, s6-rc and s6-linux-init from skarnet.org. There is no systemd anywhere in the image: no .service units and no systemctl. This is invariant 1 of the project and is not up for negotiation in a normal pull request (FAQ: why not systemd?).

The pieces

ProgramJob
s6-linux-initPID 1: sets up the system, starts the supervision tree, and handles shutdown
s6-svscan, s6-supervisesupervise every long-running service, restarting it if it dies; the scan directory is /run/service
s6-rcbrings services up and down in dependency order, from a database compiled ahead of time (/etc/s6/rc/compiled)
s6 (s6-frontend)manages the enabled set and compiles the database (s6 set enable, s6 set commit)

The image’s s6 packaging (the service stores /etc/s6/sv/ and /etc/s6/adminsv/, the -s6 service packages for NetworkManager, OpenSSH, SDDM, BlueZ and CUPS) comes from Artix Linux today, as part of the transitional base. In Runink River’s own-base scaffold, s6, skalibs and execline already build bit-for-bit reproducibly from skarnet’s sources (docs/OWN-BASE.md, section 5.5).

How the system comes up

  1. GRUB loads linux-runink and the initramfs from the EFI partition; the initramfs imports the ZFS pool, asks for its passphrase and mounts the boot environment.
  2. s6-linux-init becomes PID 1 and starts the supervision tree.
  3. s6-rc brings up the default bundle, in dependency order. Runink River’s own oneshots come first where it matters: runink-fw loads the firewall before NetworkManager and sshd start, zfs-mount mounts /home before the filesystems are declared mounted and before SDDM starts, and river-perms re-asserts file modes once they are mounted. udev, dbus, elogind, NetworkManager, Bluetooth, printing and the display manager follow.
  4. /etc/s6/rc.local runs last, in init context: sysctl --system and zram swap sized to the machine.

The full dependency table is on Architecture.

A service is a directory

Services are directories, not unit files: a type file (oneshot or longrun), an up or run script, and a dependencies.d/ directory with one empty file per dependency. The firewall’s oneshot is three lines of execline:

#!/bin/execlineb -P
foreground { /usr/local/bin/runink-fw apply }
exit 0

It always exits 0, because a failed firewall load must not keep the network and SSH down: the machine has to stay reachable for repair. runink-fw logs what went wrong instead.

Everyday commands

s6-rc -a list                                      # services that are up
sudo s6-rc -d change sddm-srv                      # stop a service (here: the login screen)
sudo s6-rc -u change sddm-srv                      # start it again
s6-svstat /run/service/NetworkManager-srv          # state of a supervised longrun
s6-rc-db -c /etc/s6/rc/compiled contents default   # what starts at boot

More, including how to add your own service: Services (s6).

The desktop on s6

KDE Plasma runs on s6 through elogind (the session and seat API that KWin, SDDM and PowerDevil use) and dbus, on the non-systemd startplasma path. PipeWire, PipeWire-Pulse, WirePlumber and the session bus run as user services. Two small pieces fill gaps that systemd would otherwise cover:

  • the plymouth-quit oneshot ends the boot splash before SDDM starts, because SDDM has no Plymouth integration of its own and nothing else would stop it;
  • the installer writes /etc/tmpfiles.d/runink-runtimedir.conf, which declares the first user’s runtime directory, /run/user/1000.

Anything the desktop does not need, and that must survive logouts, starts from rc.local in init context, so elogind never reaps it.

Software that expects systemd

Packages that ship only a systemd unit need an s6 service definition instead. Tools that require a running systemd (systemctl, systemd --user, journalctl) do not work. Before you report a bug in a package, check whether it assumes systemd.

More: the boot sequence in docs/ARCHITECTURE.md.