Image credit: analognowhere.com

An Archer delving into the Void

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.