Switching To Void Linux On My HTPC
In 2008 I built myself a HTPC. It started out running Arch Linux but switched to Ubuntu when Arch decided to force systemd. Ubuntu’s Upstart didn’t live up to Arch’s original RC system, but it fit the bill of not being systemd. I have never liked Ubuntu so it was most certainly a stop-gap solution. My replacement for Ubuntu is Void Linux.
I discovered Void a few months ago when Debian decided to force systemd as well. Once that happened I did some digging on Distrowatch for distributions that didn’t include systemd (aside: technicall I did a search for distros with a specific init system, but that doesn’t seem possible at the time of this writing). After researching a few on the list it was clear to me that Void Linux would be my new distribution of choice. The release model is very much like Arch’s, makes a point of not using OpenSSL by using LibreSSL instead, and uses Runit for the init system.
Regarding LibreSSL over OpenSSL: look back at the early posts of opensslrampage.org. It’s very illuminating.
Runit is rather amazing in its simplicity. The flexibility of systemvinit is still present, but there’s pretty much no reason to have more than 5 lines in a Runit init script; still, there are crazy people out there. The short of it is Runit doesn’t fork processes. It simply starts a process and waits for it to exit. If the process does exit, Runit restarts. So a complete init script can be:
#!/bin/sh
mkdir -p /run/samba
exec smbd -F -S
That simple script is all that is necessary to start Samba. Compare that to a traditional sysv init script or a systemd script and you’ll see why this is so great.
So, getting back to my HTPC. Reinstalling the base OS with Void was very easy. And installing everything I needed to run my interface (Kodi) was even easier:
$ xbps-install kodi xorg x11vnc
Now, at this point there’s always some trickery needed to get the system to boot straight to Kodi. This time was no exception. Initially I thought I’d be able to get by with a guide on the Void wiki. But that didn’t pan out: the guide assumes the user will only ever be used for logging in straight to X11. I need to SSH to the system as that user on a regular basis, so that assumption wouldn’t work.
When I originally built my HTPC back in 2008 there was a display manager that supported automatic logins without much hassle (I can’t recall which one). But that got replaced with SLiM. SLiM supported automatic logins, but only on the first login. If whatever program you were running, Kodi in this case, crashed then you’d be staring at login screen. Who wants to get out a keyboard and mouse to use an entertainment system? Not me. I searched for a solution and found none, so I wrote my own tool for the job. If you’ve read this site for a while you may have seen it listed as “mythlogin”, as I originally used MythTV. Since the guide’s method of automatic login wouldn’t work for me I once again turned to my tool. This time I’ve renamed it autox; this tool will be in the official Void respository likely by the time you read this.
I originally wrote autox to be used on a sysvinit system with an inittab. When I switched to Ubuntu it turned out using autox was almost just as easy. But under Runit? It wasn’t so easy:
- autox doesn’t truly log a user in to the system. It merely sets up his regular environment with all of his PAM granted permissions, e.g. real-time clock access.
- simply using agetty as the guide does results in the process being launched outside of Runit’s supervisor proccess. That’s no good since we want Runit to manage the process.
Digging in to how Void sets up ttys I learned about a tool I hadn’t heard of
before – setsid. Combining setsid
with agetty
did the trick.
The resulting Runit script for my HTPC:
#!/bin/sh
sv start wpa_supplicant
exec 2>&1
exec setsid -w agetty -a htpc -n -l /usr/bin/autox -o htpc tty7 38400 linux
Wait. What is line number three? That’s how you define a dependent service under Runit. Instead of some convoluted descriptor file like Upstart and systemd want you just start the required service. In this case I need network access and my HTPC is only connected via 802.11n currently. So I need to authenticate to my access point prior to launching Kodi since Kodi uses the Internet.
There was one other problem, though. I use x11vnc to make X11 accessible from
my other computers. This is handy when I need to do something with Kodi that
would be a chore with just an IR remote. I had been using my .xinitrc
file
to launch x11vnc. I was using exec
to fork it off into its own process in
the background. Well, do that under this new configuration resulted in x11vnc
running outside of Runit’s supervisor process. Again, not good. Solution?
Runit:
#!/bin/sh
sv start kodi
exec x11vnc -many -q -avahi -ncache 10 -passwd super_secret
Again, since x11vnc is dependent on X11 being already up and running I just
invoke the kodi
service before hand. Simple.
Finally, there is one other piece of my HTPC puzzle. I use nzbget for some things. And I let it run on my HTPC as the “htpc” user. Under the previous init systems it wasn’t worth the hassle to define it as a system service. So I wrapped it in a screen script and launched it manually every time I had to reboot my HTPC (which isn’t often). But there’s a pretty cool feature of Runit – user services. No more manually starting nzbget!:
#!/bin/sh
exec 2>&1
exec /bin/nzbget --server
With that run script and /home/htpc/{sv,service}
I can let Runit take care
of starting and stopping it. All while not having to jump through a bunch of
hoops to start it as a specific user. This is something I’d love to use at work,
but I’m stuck with RedHat and I’m not going to put another init system on top
of an existing one (maybe).
Anyway, the point of this post was mainly to highlight Runit and Void Linux. They are a great combination for an appliance system like an HTPC. Such a system doesn’t need a lot of resources, but it is better to give the actual application the majority of the resources. With Void and Runit your application gets almost all of the system resources. I’ll end this post with the stats on my HTPC’s currently used resources:
% free -h [s:127 l:385]
total used free shared buff/cache available
Mem: 7.7G 475M 2.8G 57M 4.4G 7.1G
Swap: 0B 0B 0B
% ps_mem [s:1 l:392]
Private + Shared = RAM used Program
92.0 KiB + 23.5 KiB = 115.5 KiB nanoklogd
100.0 KiB + 26.0 KiB = 126.0 KiB socklog
124.0 KiB + 38.0 KiB = 162.0 KiB uuidd
132.0 KiB + 71.5 KiB = 203.5 KiB kodi
180.0 KiB + 38.5 KiB = 218.5 KiB acpid
176.0 KiB + 73.0 KiB = 249.0 KiB runsvdir (2)
192.0 KiB + 132.0 KiB = 324.0 KiB sh (2)
216.0 KiB + 169.0 KiB = 385.0 KiB autox (2)
200.0 KiB + 236.0 KiB = 436.0 KiB xinit
448.0 KiB + 166.0 KiB = 614.0 KiB svlogd (5)
448.0 KiB + 219.0 KiB = 667.0 KiB agetty (4)
704.0 KiB + 4.0 KiB = 708.0 KiB runit
740.0 KiB + 272.0 KiB = 1.0 MiB login (2)
932.0 KiB + 132.5 KiB = 1.0 MiB sudo
1.0 MiB + 90.5 KiB = 1.1 MiB udevd
1.4 MiB + 506.5 KiB = 1.9 MiB runsv (19)
1.6 MiB + 395.0 KiB = 1.9 MiB wpa_supplicant
2.3 MiB + 109.5 KiB = 2.4 MiB most
2.6 MiB + 461.5 KiB = 3.1 MiB mandoc
2.9 MiB + 437.5 KiB = 3.3 MiB nmbd
1.2 MiB + 2.7 MiB = 3.9 MiB sshd (5)
4.2 MiB + 4.3 MiB = 8.5 MiB smbd (2)
7.6 MiB + 1.3 MiB = 8.9 MiB mosh-server (2)
12.2 MiB + 562.5 KiB = 12.8 MiB x11vnc
11.1 MiB + 2.4 MiB = 13.6 MiB zsh (6)
14.9 MiB + 811.0 KiB = 15.7 MiB nzbget
29.2 MiB + 1.8 MiB = 31.0 MiB Xorg
411.8 MiB + 5.2 MiB = 417.0 MiB kodi.bin
---------------------------------
531.1 MiB
=================================
% pstree [s:0 l:386]
runit─┬─2*[mosh-server───zsh]
└─runsvdir─┬─runsv─┬─socklog
│ └─svlogd
├─4*[runsv───agetty]
├─runsv───sshd─┬─sshd───sshd───zsh
│ └─sshd───sshd───zsh───pstree
├─runsv───uuidd
├─runsv───login───zsh
├─runsv───smbd───smbd
├─runsv───nanoklogd
├─runsv─┬─svlogd
│ └─wpa_supplicant
├─runsv─┬─mythlogin───autox───sh───xinit─┬─Xorg───{Xorg}
│ │ └─sh───kodi───kodi.bin─┬─{AESink}
│ │ ├─{ActiveAE}
│ │ ├─{AirPlayServer}
│ │ ├─{EventServer}
│ │ ├─{FDEventMonitor}
│ │ ├─23*[{LanguageInvoker}]
│ │ ├─{PeripBusUSBUdev}
│ │ ├─{TCPServer}
│ │ ├─17*[{kodi.bin}]
│ │ └─2*[{libmicrohttpd}]
│ └─svlogd
├─runsv───login───zsh───man───most
├─runsv───nmbd
├─runsv───udevd
├─runsv───acpid
├─runsv─┬─svlogd
│ └─x11vnc
└─runsv───runsvdir───runsv─┬─nzbget───6*[{nzbget}]
└─svlogd