Add paper about runit under arch.
This commit is contained in:
parent
1d914d777a
commit
23ee16724c
|
@ -0,0 +1,295 @@
|
||||||
|
Title: Runit on Arch
|
||||||
|
|
||||||
|
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's time to make the switch.
|
||||||
|
I know a lot of people feel very passionately about systemd.
|
||||||
|
I just prefer to use runit.
|
||||||
|
My wife's computer uses systemd and I have no issues with that.
|
||||||
|
If you want to continue your holy war about systemd,
|
||||||
|
there are many web forums available for you to do so.
|
||||||
|
|
||||||
|
What's the advantage?
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Runit lets--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 encourages things to log to stdout (or stderr),
|
||||||
|
and keeps a log file for each service.
|
||||||
|
It also runs one log program per service.
|
||||||
|
That log program can be anything you want.
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
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 `runit`.
|
||||||
|
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/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/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
|
||||||
|
|
||||||
|
# Run arch's sysinit
|
||||||
|
/etc/rc.sysinit
|
||||||
|
|
||||||
|
if grep -q 'break=init' /proc/cmdline; then
|
||||||
|
echo 'Breaking before init, type "exit" to continue booting'
|
||||||
|
/bin/sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
# XXX: Who creates this?
|
||||||
|
rm /run/nologin
|
||||||
|
|
||||||
|
# Hand off to runit
|
||||||
|
exec runsvdir -P -s runit-signal /service
|
||||||
|
|
||||||
|
|
||||||
|
This will still run `udev` and `bootlogd` from `/etc/rc.sysinit`.
|
||||||
|
I tried to set up `mdev` from busybox as a `udev` replacement,
|
||||||
|
but Xorg wants `udev`,
|
||||||
|
and I was having other problems getting drivers loaded,
|
||||||
|
so I'm just trusting the the Arch devs here.
|
||||||
|
If you can figure out another way,
|
||||||
|
please email me about it, I'd love to know.
|
||||||
|
|
||||||
|
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..."
|
||||||
|
reboot -f
|
||||||
|
;;
|
||||||
|
10) # SIGUSR1: halt
|
||||||
|
cleanup
|
||||||
|
echo "Halting..."
|
||||||
|
halt -f
|
||||||
|
;;
|
||||||
|
12) # SIGUSR2: power
|
||||||
|
cleanup
|
||||||
|
echo "Shutting down..."
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Have fun!
|
Loading…
Reference in New Issue