14 KiB
title |
---|
Runit on Arch Linux |
Last update: 26 August 2014
The easy method, for the impatient
I like how runit manages things, especially the restarting of dead daemons. I was growing tired of sysvinit when systemd started making inroads, and when Arch moved to systemd, I figured it was a good time to make the switch.
I know a lot of people feel very passionately about systemd; I just like runit better. If you want to rage about systemd, there are many online venues available for you to do so.
What's the advantage?
Runit allows€”no, forces—you to write your own startup scripts. You can write them in any language you want, but Bourne shell is pretty convenient. I think this is the biggest selling point for me. I like doing things with specialized programs, and I know Bourne shell pretty well, so it's easy for me to figure out what runit is doing, and extend it.
If a daemon dies, runit restarts it in 2 seconds. I find that convenient, but some prefer for things with problems to be restarted manually.
Runit wants daemons to run in the foreground. Having written many daemons, I like this philosophy a lot. I never understood why the "fork twice" hack needed to be duplicated in every daemon ever; Runit takes care of that for you.
Runit encourages things to log to stdout (or stderr),
instead of syslog or custom logging code.
Writing to stderr is also very convenient from the standpoint of the daemon's author.
It's a natural way to provide information to the user,
and all that's needed for "debugging mode" is to launch the daemon at the command line
instead of from runit.
In fact, the "log" package in Go (language) works without any modifications
in this way.
stdout from a runit service is sent to stdin on a log service,
which runit also keeps track of.
That log service can be anything you want:
svlogd
does a pretty good job timestamping lines,
and it also rotates logs automatically without needing to stop and start the daemon.
People aren't using sysvinit anymore (alas), but this essay by Denys Vlasenko is still a pretty good overview of other ways runit is neat.
Have I convinced you?
Try out my runit-init AUR which does everything described here, and also contains updates for things like mdev (instead of udev) patches to X to start without udev, and cryptographic filesystem mounting.
The Gory Details
Everything below here was written in February 2013. Things have changed since then; in particular, I have an AUR which uses busybox runit to boot your system.
I'm leaving the rest for people who want discussion about how to boot their system. This will all still work, it's just that the AUR works better :)
Peculiarities of my setup
I like the runit that comes with busybox,
so I'm using that.
If you prefer Gerrit Pape's runit,
the procedure will be similar,
but there are subtle differences you will need to watch out for.
In particural,
Gerrit's runsvdir
does not have the -s
option:
that functionality is provided by runit-init
.
I put my runit service directory in /service
.
Everything else seems to be making top-level directories these days,
and, hell, it's my computer.
You can put yours wherever you want,
just change /service
in my examples to your directory.
Ctrl-alt-del does an immediate reboot.
Once I'm more comfortable with this setup,
I may change that by writing to the approprate file in /proc
,
but I actually like this behavior for now.
Warning
If you screw this up,
you might not be able to boot your computer up anymore.
If you're using Arch,
presumably you're already comfortable administering your computer.
But messing with init
can break things in exciting new ways.
Arch's initrd has a nice break=postmount
kernel commandline option
to open a shell after mounting root,
which I used to recover things several times.
You might want to play with that and understand what the initrd is,
before you are put into a position where you have to use it and can't start a web browser.
WARNING
This document is now pretty old. It's unlikely it will work at all on a modern Arch installation. My AUR is usually only a few days behind the latest change in Arch's packages. I've left this here because it might help people trying similar things with different distributions. But if you're using Arch, I strongly recommend you start with the AUR.
Let's go
The version of busybox
packaged for Arch comes with many "applets" compiled in:
enough for us to set it all up.
Because it's statically linked,
we don't even need to worry about libc updates.
To prevent a bad busybox
update from bringing down the entire works,
let's make a copy.
cp /bin/busybox /usr/local/sbin/busybox.static
ln -s busybox.static /usr/local/sbin/runsv
ln -s busybox.static /usr/local/sbin/runsvdir
ln -s busybox.static /usr/local/sbin/sv
We also need to create a new /sbin/init
to replace systemd
(or sysvinit) and launch runsvdir
.
Arch actually has a pretty nice init setup,
almost as though they had this use case in mind when they were designing it.
The early userspace init sets up /, pivots root, and runs /sbin/init.
At that point, we can take over, run /etc/rc.sysinit
,
and hand off to runsvdir
.
Putting the system initialization stuff into a shell script
was a nice move on the part of the arch folks,
and makes this almost trivial.
The other thing init
needs to handle is being called by programs like reboot
and poweroff
,
which want to signal init
by changing runlevel.
So if our new init
is not PID 1,
we'll emulate telinit
from sysvinit,
by checking what runlevel is being requested and sending the appropriate
signal to PID 1.
Be sure to move the old init
to soming like init.sysv
,
then create a new init
similar to this
(don't forget to chmod +x
):
#! /bin/sh
PATH=/usr/bin; export PATH
if [ $$ -ne 1 ]; then
case $1 in
6)
exec kill -15 1
;;
0)
exec kill -12 1
;;
esac
echo "LOL: runit doesn't have run levels" 1>&2
exit 1
fi
echo
echo 'Arch Linux'
echo 'http://www.archlinux.org/'
echo '-----------------------------'
echo
echo ":: Mounting initial filesystems"
mountpoint -q /proc || mount -t proc proc /proc -o nosuid,noexec,nodev
mountpoint -q /sys || mount -t sysfs sys /sys -o nosuid,noexec,nodev
mountpoint -q /run || mount -t tmpfs run /run -o mode=0755,nosuid,nodev
mountpoint -q /dev || mount -t devtmpfs dev /dev -o mode=0755,nosuid
mkdir -p -m0755 /run/runit /run/lock /run/lock/lvm /run/lvm /run/user /dev/pts /dev/shm
mountpoint -q /dev/pts || mount -n -t devpts devpts /dev/pts -o mode=0620,gid=5,nosuid,noexec
mountpoint -q /dev/shm || mount -n -t tmpfs shm /dev/shm -o mode=1777,nosuid,nodev
mount -o remount,ro /
echo ":: Setting up Unicode"
for i in /dev/tty[0-9]*;do
unicode_start <$i
done &
echo ":: Setting system clock"
hwclock --utc --hctosys
echo ":: Enabling devices"
touch /dev/mdev.seq
/usr/bin/mdev -s &
echo ":: Loading drivers"
for i in $(seq 2); do
find /sys -name modalias -type f -exec cat {} + | sort -u | xargs modprobe -b -a
done 2>/dev/null
echo ":: Bringing up network"
ip link set up dev lo
cat /etc/hostname >/proc/sys/kernel/hostname
echo ":: Setting up cryptographic devices"
grep "^[^#]" /etc/crypttab | while read name device password options; do
case $options in
*swap*)
cryptsetup --key-file /dev/urandom open --type plain $device $name
mkswap /dev/mapper/$name
;;
*)
cryptsetup luksOpen $device $name < /dev/console
;;
esac
done
echo ":: Checking filesystems"
[ -f /forcefsck ] || grep -q forcefsck /proc/cmdline && FORCEFSCK=-f
if ! [ -f /fastboot ] && ! grep -q fastboot /proc/cmdline; then
fsck -A -T -C -a -t noopts=_netdev $FORCEFSCK
if [ $? -gt 1 ]; then
sulogin
fi
fi
echo ":: Mounting filesystems"
mount -o remount,rw /
mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev
echo ":: Enabling swap"
swapon -a
echo ":: Tidying up"
install -m0664 -o root -g utmp /dev/null /run/utmp &
rm -f /etc/nologin /forcefsck /forcequotacheck /fastboot &
if grep -q 'break=init' /proc/cmdline; then
echo 'Breaking before init, type "exit" to continue booting'
/bin/sh
fi
if [ -x /etc/rc.local ]; then
echo ":: Sourcing /etc/rc.local"
. /etc/rc.local
fi
echo ":: Passing control to runit"
echo
exec runsvdir -P -s runit-signal /service
This does a couple things:
- Mounts /proc, /sys, /dev, and some other directories.
- Turns on Unicode for 9 TTYs
- Sets the system clock from the hardware clock
- Runs an initial mdev to populate /dev
- Loads modules for things in /sys
- Bring up the loopback interface
- Initialize your cryptfs, if you have any in /etc/crypttab
- fsck then mount everything in /etc/fstab
- Run whatever's in /etc/rc.local
- Start runsvdir
You may also want to install the dash
package,
and link /bin/sh
to that,
if you worry (as I do) about libc breaking things
(dash
is statically linked).
runit-signal
Busybox's version of runsvdir
has a -s
option.
When runsvdir gets a signal,
it runs whatever was provided to -s
with the signal number as the first argument.
Here's my /usr/local/sbin/runit-signal
(make sure you chmod +x
):
#! /bin/sh
##
## Signal handler for runit
##
if [ $PPID != 1 ]; then
echo "This program should only be invoked by PID 1."
# The reason is that killall5 won't kill anything in the same
# process group. That means it won't kill your invoking shell,
# getty, or svrun. That in turn prevents filesystems from
# unmounting, or even being remounted ro, since svrun (at least) has
# a FIFO open for writes. And if we reboot without unmounting
# filesystems, that's bad.
echo "Feel free to read $0 to learn why :)"
exit 1
fi
waitall () {
for i in $(seq 50); do
# If all processes are in group 0, we're done
awk '($5){exit 1;}' /proc/[0-9]*/stat && return 0
usleep 200000
done
return 1
}
cleanup () {
echo "Stopping services..."
sv stop /service/*
echo "Asking processes to exit..."
killall5 -1
killall5 -15
if waitall; then
echo "Forcing processes to exit..."
killall5 -9
waitall
fi
echo "Unmounting file systems..."
umount -a -r
# Sometimes when we reach here we still haven't been able to umount
# everything. Not much more we can do about that, other than flush
# write buffers and hope for the best.
sync
}
case $1 in
1) # SIGHUP
;;
15) # SIGTERM: reboot
cleanup
echo "Rebooting..."
busybox reboot -f
;;
10) # SIGUSR1: halt
cleanup
echo "Halting..."
busybox halt -f
;;
12) # SIGUSR2: power
cleanup
echo "Shutting down..."
busybox poweroff -f
;;
*) # Everything else
;;
esac
Create a getty
Before we reboot, we need to make sure to create a way to log in.
The following in /service/tty2/run
will start a getty on the second virtual console
(don't forget to chmod +x
):
#! /bin/sh
pwd=$(pwd)
TTY=${pwd##*/}
exec agetty $TTY
You can make more than one getty by copying this to /service/tty3/run
and so on.
Reboot!
Double check you did it all right:
- You understand how to use the initrd shell you get passing the
break=postmount
boot argument /sbin/init
should be an executable shell script- At least a getty service will start from the
service
directory you set up
That's not a big checklist. Ready to go? Tell the currently-running init to reboot:
/sbin/init.sysv 6
Cross your fingers!
If it doesn't work
You can always move /sbin/init.sysv
back to /sbin/init
and reboot
into your old setup.
Nothing in this page will destroy the old bootup process
(other than renaming init
, of course).
If it does work
Congratulations, you're now using runit.
You now need to write startup scripts for things you like to run,
like dhcpcd
, ntpd
, maybe xdm
.
You're an Arch Linux sysadmin,
you should know what you need,
and I can't help you past here.
Hotplug events won't work, though. For that, you need to either run udev or some other hotplug listener.
Setting up mdev
as a hotplug listener
The mdev
utility of busybox can replace most of what udev
does.
You just need to have the kernel run mdev
as the hotplug userspace thingy.
Recent precompiled kernels have removed support for /proc/sys/kernel/hotplug
,
so it's necessary to run a userspace program to get netlink events.
Send me an email asking for hurtplurg.c
if you'd like the one I wrote.
You'll need to configure mdev
to set up file permissions that work for you.
Since X11 wants udevd
for something or other,
you'll also need to tell it to use whatever the old method is.
I don't quite understand what they do,
and they've surely changed since I wrote mine.
Have fun with man pages.
Getting rid of systemd
At this point you are not running anything in systemd
.
But you still need it installed,
because a lot of things depend on libraries it's taken over.
Don't panic about this.
It's going to be okay.
You have lots of things installed that you don't strictly need.
I chose Arch over Gentoo because I like precompiled packages,
even though that means they bring in a couple things I don't need.
systemd
is one of those things.
Have fun!