If you know me, you know I’m quite fond of Arch Linux. Using it over the last four and a half years hasn’t been without some rough spots, but it still ended up being the best time I’ve ever had using Linux.
But y'know, it’s kinda boring when everything Just Works™. On top of that, I was also quite curious to play with more unconventional underlying systems. Every distro I’ve used so far, be it Arch, Solus, or openSUSE, has been more or less the same underneath, making extensive use of the ubiquitous and rather controversial systemd stack.
So with that in mind, I looked around and stumbled into the Void. Void Linux, that is. Void Linux is a bit of an odd one, but overall looked like exactly what I wanted: a DIY sort of distro that had the guts to have different internals than most distros.
So without further ado, let’s don our Brotherhood robes and delve into the Void in ethernal service to Sithis. (What, you thought you could escape The Elder Scrolls just because this is a Linux article? YOU FOOLS!!! >:3 )
Installation
The initial installation was actually quite painless. Void ships with a really nice guided installer that can handle most of the installation steps: set a language, select your mirrors, partition and format the disk, and create a user account. Everything looked like smooth sailing, but little did I know, those were not calm waters that I was headed towards.
Immediately after booting into the installed system, there was still plenty to
be done; but I’ll skip over the boring stuff. Yeah, I had to install a bunch of
packages like mesa
, vim
and whatnot, but you’re not here for that, you’re
here for the stuff that made me question if I should even keep going with Void
or crawl back to Arch.
Networking? More like not working
Didn’t mention it earlier, but the installer is supposed to also be able to set up a WiFi connection. Maybe this works for others and I just have a fucky setup, but it failed connecting for me. Not a huge deal, since I could also install packages from the local image and update later, but when I made it into the installed system things got… weird.
By default, Void uses wpa_supplicant
to connect to WiFi networks, and
wpa_supplicant
just so happens to store network configurations in
/etc/wpa_supplicant.conf
. So I went in there to manually add my WiFi network,
and… it was there? Huh??
Turns out the installer added my network just fine, but for whatever reason it just failed to connect, and in fact checking the logs there were a lot of failed attempts to connect to my WiFi.
Initially I didn’t know what the culprit could be since Arch connected just
fine to this same WiFi. I considered the possibility that maybe
wpa_supplicant
just didn’t know how to handle the encryption since my WiFi
uses WPA3, but quickly dropped that since the official
site clearly claims it supports WPA3.
So, what could the issue be? Well, I’ll cut to the chase, wpa_supplicant
does
infact have issues with WPA3. I switched my WiFi network to allow WPA2 and it
instantly connected to it, no problem. Switched the network back to WPA3 only,
and it had issues connecting again. Maybe it was some misconfiguration stuff,
but all documentation I’ve been able to find led me to believe that all you
have to do is tell it whether the network uses WEP or WPA and wpa_supplicant
then automagically figures out which WPA you mean. Clearly, tho, that didn’t
work in this case.
Service Management
The first thing I found that was very different from other distros I’ve used is
how Void does service management. On more conventional distros, systemd manages
services, with systemctl
as the accompanying tool for the user to to enable
or disable services, check their status, etc.
As established earlier, however, Void doesn’t use systemd, so there’s no
systemctl
here, either. “So what does it use?” I pretend to hear you asking.
Well, it uses runit
to manage services, and the accompanying command line
tool is sv
. sv
covers most of what you’d use systemctl
for, but not all
of it, namely enabling and disabling services is done quite differently, as is
defining a service.
Defining Services
Most of you might not end up ever needing to define services, but I did.
In additions to common services like pipewire and mpd, I also want to run a few other binaries as services, such as an mpd-mpris bridge program that I yanked off GitHub at some point in time (I believe this to be the repo but I’m not sure, looked it up as I’m writing this and that repo seems about right).
Now I know the linked repo has a systemd service file, but I didn’t know about that months back when I first decided to define this as a service, so here’s my systemd service definition for it:
[Unit]
Description=MPRIS2 bridge for MPD
Requires=mpd.service
After=mpd.service
[Service]
Type=simple
ExecStart=/home/reid/.bin/mpd-mpris
[Install]
WantedBy=default.target
I could explain the whole thing, but it’s pretty self-explanatory. Only thing I feel needs to be pointed out here is that systemd doesn’t manage services, but rather manages units which may be services. So the above defines a unit which is a service. Simple, right?
So if that’s how systemd defines a service, what would this exact same service look like when defined for runit?
Well, here’s how:
.
├── run
└── supervise
├── control
├── lock
├── ok
├── pid
├── stat
└── status
Huh???
Okay so, services are no longer singer files in runit. Instead they’re a whole directory containing a few files inside. While this is a very different way to lay out the service information, it’s actually simpler-ish than the systemd definition.
The main file I’m interested in here is run
. It is equivalent to ExecStart
in the systemd unit declaration. Kind of. Its contents look something like the
following.
#!/bin/sh
exec 2>&1 # Redirects stderr to stdout
exec ~/.bin/mpd-mpris # runs the binary
This might look like a load of gibberish, but it’s a shell script that runs the same binary as the systemd unit above, along with redirecting its stderr to stdout (probably for logging).
Now you might be wondering, why run a script that just runs a binary instead of
linking run
directly to the binary? Well, I actually tried that, initially,
and for whatever reason it didn’t work.
At first I didn’t know what to do since I couldn’t find much info on what run
should look like (in big part because search engines are pretty broken these
days). But then, in a stroke of genius, I finally thought of calling upon the
wisdom of my predecessors.
This whole time there were a bunch of services defined in /etc/sv/
just
waiting for me to read them. And so I did, and I didn’t understand much of what
each was doing, but did see the loosest of patterns.
Following the shebang, every single script started by redirecting stderr to
stdout, and at the very end of the script there was the final exec line with
the main binary of the service. So I wrote exactly that in my run
, and it
worked!
Now if you want to read more in-depth about runit services, be my guest, the Void documentation has more information about it here, but for me this was all the information I needed.
Tho there’s one more thing I should add: Void doesn’t actually do user-level services by default!
Managing Services
First of all, take everything you know about service management on systemd and throw it out the window. runit’s service management is quite minimal but just as clever.
System Services
In /etc/runit/runsvdir/
, there are multiple directories that Void calls
“runsvdirs”, but I’ll call them service groups. A service group is simply
a directory of services to be started, and by default Void comes with 2 groups:
default
which is where your regular services should go, and single
which is
more of a minimal rescue service group, kind of like a Safe Mode for Linux. But
of course, there’s nothing stopping you from creating as many service groups as
you want.
Another thing to point out: there’s no such thing as a disabled service as far
as runit is concerned. If you want to disable a service, you remove it form the
service group. However, there is a solution: namely, placing all services in
/etc/sv/
by default then creating symlinks to them from the service groups.
For your own user services, you may either place them in /etc/sv/
or create
a new directory for user services, say, /etc/usv/
.
User Services
By default, runit only manages system-level services. If you want to run
user-level services, you’ll need an extra tool like turnstile. Turnstile does
a few other things, and doesn’t technically actually user services either, but
instead it runs a runit
instance under your user that does.
So, I set up turnstile as described
here
(which I did for session management initially), and bada bing bada boom,
there’s now a runsvdir in ~/.config/system/
.
The service definitions are identical to user services, but do keep one thing
in mind: if you want to take a system service and run it under your user,
linking the entire service dir will not work since the user runit won’t have
write access to the virtual files present in each service file. Instead, either
copy the service dir wholesale or only link the run
file
So with that done, I added the service described above, created a pipewire service that worked pretty much the same, and also made a copy of the system mpd service to run under my user.
So of course, it was time now for another headache, yay! Well, it wasn’t that bad, actually.
As it turns out, dbus actually runs not only a unique system bus, but also
a number of user buses. On Arch, this was just handled for me so I never had to
think about it, but here on Void that’s not the case. As it also turns out,
MPRIS interfaces run on the user bus and will not fall back to a system bus.
Thankfully Void described
here
how to run a user dbus and even had a nice run
file for the service, how
nice! So with a user bus now running, MPRIS worked just fine!
When power goes unhandled…
This is the part that honestly just broke me. Everything so far had been a lot more work than what I’d put in an average Arch install, but for the most part it was just a matter of getting used to things being different, and by the end of it I ended up appreciating Void’s ways more.
However, when it comes to power management, I just had to throw in the towel. Initially everything seemed to be working but then I closed the laptop’s lid and figured out that oh wait, it’s not going to sleep! That’s kind of a big problem on a laptop.
I started looking around and it looks like acpid
should be able to handle
those, but the only documentation I’ve been able to find is this
manpage and maybe I was just impatient by
this point but I just didn’t find what I was looking for there. The usual
DuckDuckGo-fu didn’t help, either. Seemingly everyone who has ever used Void
only ever used it on a desktop and never had to worry about “advanced” power
management like going to sleep when the lid is closed.
A sad end…
Some of you might now send me solutions, others might start screaming “skill issue!”, but one thing is clear: at this point I just gave up, grabbed my Arch USB, and bailed. Maybe I’ll return to Void another time, the experience wasn’t entirely bad and I quite liked some aspects of it, but in the end it was a lot more than I could deal with at the moment.
If ever I return to Void, be assured there’ll be a new post about it, but for now just know that I just couldn’t be fucked with acpid.