Services
UraniumOS supervises services with runit. The kernel starts the initramfs, the initramfs starts busybox init as process 1, and init reads /etc/inittab:
::sysinit:/etc/rc
::respawn:/rod/profile/bin/runsvdir -P /etc/service
::ctrlaltdel:/rod/init/bin/reboot
::shutdown:/etc/rc.shutdown
init runs /etc/rc once, keeps runsvdir running on /etc/service, runs reboot on Ctrl-Alt-Del and runs /etc/rc.shutdown at shutdown. runsvdir, sv and svlogd come from the runit package in /rod/profile/bin. The busybox applets are in /rod/init/bin, which is not on the PATH of a login shell, see system. Jobs that run at fixed times belong in cron.
A process list of a freshly booted system, shortened:
1 0 root init
158 1 root /rod/profile/bin/sh /etc/rc
160 1 root /rod/profile/bin/runsvdir -P /etc/service
164 160 root runsv dhcp
165 160 root runsv getty-tty1
166 160 root runsv getty-ttyS0
167 160 root runsv autoframe
168 160 root runsv ntpd
169 160 root runsv crond
170 160 root runsv trial
182 164 root udhcpc -f -i eth0 -s /etc/udhcpc.script
184 169 root crond -f -c /etc/crontabs
Process 158 is the /etc/rc shell.
What /etc/rc does
/etc/rc is a shell script (bash from the profile) that runs with PATH=/rod/profile/bin:/rod/init/bin. It keeps going when a command fails. It:
- mounts
/proc,/sysand/devif the kernel has not, then/dev/pts,/dev/shm(nosuid,nodev,noexec) and/tmp(nosuid,nodev, mode 1777), and sets the kernel log level to 3; - mounts a tmpfs on
/run. If the initramfs mounted the btrfs top level at/run/scram/top, it is moved onto the new/run; - remounts
/read-write and runsmount -afor/etc/fstab(/homeand/boot); - sets the hostname from
/etc/hostname; - loads the console keymap named in
/etc/keymap, see keyboard; - applies
/etc/sysctl.confand loads the firewall/etc/nftables.conf, see blastdoor; - creates a zram swap device of half the memory. It asks for
zstdcompression, but the 0.1.0 image ends up with lzo-rle; - brings up
lowith 127.0.0.1; - runs
/etc/rc.localif it exists. The shell sources the file as root before any service starts;
Shutdown
/etc/rc.shutdown runs sync and umount -a -r, which remounts what it cannot unmount read-only. reboot, poweroff and halt are busybox applets in /rod/init/bin. Call them as /rod/init/bin/reboot or add that directory to your PATH.
Service directories
A service is a directory in /etc/sv. It is enabled when a link to it exists in /etc/service. runsvdir checks /etc/service every five seconds, starts a runsv supervisor for each new link and stops the supervisor of each removed link. The directory can hold:
run- Required, executable. It has to
execa process that stays in the foreground. When the process exits,runsvstartsrunagain after one second. log/run- Optional. The standard output of
rungoes through a pipe to this program. The log scripts that ship create/var/log/NAMEandexec svlogd -tt /var/log/NAME. finish- Optional. Runs when
runexits. check- Optional.
sv checkruns it, and exit status 0 means the service works. down- An empty file. The supervisor starts but the service does not.
sandbox- Read by
hazmat -s, see below. supervise/- Made by
runsv, mode 0700. It holdscontrol,lock,ok,pid,statandstatus.
A run script gets the environment that runsvdir inherited from init. SVDIR is only set by /etc/profile, so sv check dhcp inside a script fails and the script has to name the service by path.
The standard error of run is not part of the log pipe. Without exec 2>&1 it goes to the console. The scripts of dhcp, ntpd, crond and sshd use exec 2>&1, and autoframe and trial do not.
Control with sv
In a login shell SVDIR=/etc/service, so sv takes a service name or a path. Anywhere else pass /etc/service/NAME.
# sv status /etc/service/*
run: /etc/service/autoframe: (pid 174) 4s; run: log: (pid 173) 4s
run: /etc/service/crond: (pid 184) 4s; run: log: (pid 183) 4s
run: /etc/service/dhcp: (pid 182) 4s; run: log: (pid 181) 4s
run: /etc/service/getty-tty1: (pid 179) 4s
run: /etc/service/getty-ttyS0: (pid 180) 4s
run: /etc/service/ntpd: (pid 186) 4s; run: log: (pid 171) 4s
run: /etc/service/trial: (pid 172) 4s
sv up NAME,sv down NAME- Start the service and keep it running, or stop it and keep it stopped. A
downstate does not survive a reboot. sv once NAME- Start the service once and do not restart it when it exits.
sv restart NAME- Stop and start. It reports the service as running when it is up again.
sv check NAME- Reports that the service is running and exits with status 0 when it is up.
sv status NAME- One line, see the table.
sv exit NAME,sv term NAME,sv hup NAME,sv kill NAME- Stop the supervisor, or send the signal to the service. See sv(8) of runit.
sv alarm NAME- Sends SIGALRM. On a
logdirectory (sv a /etc/service/dhcp/log) it makessvlogdrotate the log.
Status lines:
run: ntpd: (pid 219) 1s; run: log: (pid 171) 6s | Running, with its log process. |
down: ntpd: 1s, normally up; run: log: (pid 171) 5s | Stopped with sv down. |
run: crond: (pid 222) 3s, want down | Started with sv once. |
down: web: 5s | Has a down file and has not been started. |
down: bad1: 1s, normally up, want up | The run script keeps exiting and is being restarted. |
fail: nosuch: unable to change to service directory: file does not exist | No such name in SVDIR. |
Enable and disable
# ln -s /etc/sv/sshd /etc/service/sshd
# rm /etc/service/sshd
A new link starts the service within five seconds and at every later boot. Removing the link stops the supervisor and the service within about seven seconds. A link can also be made from a chroot, so a service can be enabled before the first boot.
Try a service before it starts at boot
# touch /etc/sv/web/down
# ln -s /etc/sv/web /etc/service/web
# sv once /etc/service/web
# sv status /etc/service/web
run: /etc/service/web: (pid 241) 1s, normally down, want down
Remove the down file and run sv up when it works. geiger skips services that have a down file.
Logs
svlogd -tt writes /var/log/NAME/current with UTC timestamps, whatever the time zone is:
# cat /var/log/dhcp/current
2026-11-02_16:53:09.65038 udhcpc: started, v1.37.0
2026-11-02_16:53:09.66001 udhcpc: broadcasting discover
2026-11-02_16:53:09.68126 udhcpc: broadcasting select for 10.0.2.15, server 10.0.2.2
2026-11-02_16:53:09.70119 udhcpc: lease of 10.0.2.15 obtained from 10.0.2.2, lease time 86400
When current reaches the size limit, or on SIGALRM, svlogd renames it to @TAI64N.s and starts a new file. The limits are the runit defaults, 1,000,000 bytes and 10 files.
A log directory added to a service that is already supervised is not used, and no /var/log/NAME/current appears. Create the log before the first start, or remove the link, wait for the supervisor to exit and link it again.
The logs are on the root subvolume, so every frame has them.
The services of the system
The stage links the seven services in the first block. setup --ssh links sshd, and uranium-desktop creates the last two.
getty-tty1 | getty -l /rod/init/bin/login 38400 tty1. The login prompt of the first virtual console. No log. |
getty-ttyS0 | getty -l /rod/init/bin/login -L 115200 ttyS0 vt100. The serial console. No log. |
dhcp | Waits for a network device with a device entry in /sys/class/net, brings the first one up and runs udhcpc -f on it. See network. Log: /var/log/dhcp. |
ntpd | ntpd -n -p pool.ntp.org -p time.cloudflare.com. See time. Log: /var/log/ntpd. |
crond | crond -f -c /etc/crontabs. See cron. Log: /var/log/crond. |
autoframe | Sleeps 3600 seconds, runs scram auto 48 and exits, and runsv starts it again. The first frame comes an hour after boot. scram auto takes a frame labelled auto and deletes the older automatic frames beyond the newest 48. Log: /var/log/autoframe, which stays empty. |
trial | Runs scram verify, sleeps 3600 seconds and repeats. scram verify returns at once unless an update is on trial. On trial it waits 300 seconds, runs geiger up to three times 30 seconds apart, confirms the update on success and reboots on failure. See scram. No log. |
sshd | Creates /var/empty, creates the ed25519 host key if it is missing and runs sshd -D -e. See ssh. Log: /var/log/sshd. |
udevd, seatd | Added and linked by uranium-desktop USER for the desktop profile. |
Write a service
This service serves /srv/www with the busybox web server. It is called by full path because busybox is not on the PATH of the scripts.
# mkdir -p /etc/sv/web/log /srv/www
# cat > /etc/sv/web/run <<'EOF'
#!/rod/profile/bin/sh
exec 2>&1
exec /rod/init/bin/httpd -f -p 8080 -h /srv/www
EOF
# cat > /etc/sv/web/log/run <<'EOF'
#!/rod/profile/bin/sh
mkdir -p /var/log/web
exec svlogd -tt /var/log/web
EOF
# chmod +x /etc/sv/web/run /etc/sv/web/log/run
# ln -s /etc/sv/web /etc/service/web
Open the port for other machines with blastdoor allow 8080. A run script that is not executable fails once a second, with an error on the console. A script that runs a program that does not exist fails the same way.
Start order
All services start together. To wait for another one, test it in run and exit while it is down. runsv starts the script again a second later:
sv check /etc/service/dhcp > /dev/null || exit 1
The path form is needed because SVDIR is not set in a script.
Sandbox a service
hazmat -s COMMAND in a run script reads the file sandbox in the current directory. runsv starts run in the service directory, and HAZMAT_FILE changes the name. Without the file the command runs without a sandbox. Each line is one of:
ro PATH- Bind the path read-only.
rw PATH- Bind the path writable. It is created if missing.
net,nonet- Share the host network, or not. The default is no network.
Blank lines and lines starting with # are ignored, and any other word stops hazmat with unknown line. hazmat then runs bwrap --unshare-all, with --share-net for net, and binds /nix, /rod/profile, /bin, /usr, /lib, /lib64, /etc/ssl and /etc/resolv.conf read-only, with a fresh /proc, /dev and /tmp. /rod/init is not bound, so use programs from the profile. The service keeps it's user id. See hazmat.