Setting up a media center PC with NixOS
There are right now at least 3 Raspberry Pis and 2 regular PCs running in my home, that I'm not using as a desktop, but to provide some kind of service, including my media center PC. All of them are my pets, I set them up by hand and then promptly forget how I did it. My first setup for the media center PC was using Arch Linux (I'm using Arch Linux, btw) and Kodi, but I wasn't happy with this.
The overall problem with these computers is, as I said, I set them up and then forget how I did it. What I would really like is to have all of the necessary configuration in one place, so that I don't need to write extra documentation and so that it's reproducible. Supposedly, this is what NixOS enables you to do, so I set out to try it, fixing some other annoyances along the way.
Actually, what first prompted me to redo my setup was Kodi. It looks good on paper but I had lots of problems with it. For example, it seems that launching other programs is not something Kodi can do natively. Instead, you need to install an addon for the program in question. I ended up forking an addon twice, just so that I can launch Steam and a browser. Maybe I missed something, but that's also what Claude suggested to do.
Then, I wanted to use it to play old games. Kodi does implement the frontend for Libretro, so given that you install the correct emulator core, you should be able to play Gameboy and Nintendo 64 games. Unfortunately, it doesn't support emulators that need OpenGL support which means none of the N64 emulators will work.
Accessing the network share where I have my media was also not as straight forward as I hoped. Again, I needed to install an addon to be able to use SFTP.
In the end, I realized, what I really wanted was mostly a launcher for other programs. So I went and looked for alternatives and it turned out, there aren't that many. I spent a little time trying to run KDE/Plasma Bigscreen but wasn't successful. Then I found Flex Launcher but I didn't like the style and at that point already had the urge to write my own launcher. Which is exactly what I did, since I wanted to try Slint again anyway.
Once the yak was shaved, I went on and started to migrate my Arch based setup to NixOS. The installation was surprisingly simple, because I was already used to do the partitioning manually.
Actually configuring the system is quite different from other distributions though, and I had often difficulties understanding what configuration options would do, or how to do something I already knew how to do on Arch.
For example, a lot of services can be enabled by setting the corresponding services.<name>.enable = true
option, but it's not clear to me what that does.
It clearly isn't just enabling the systemd unit, since for example pipewire didn't automatically start after I enabled it.
Which brings me to another point. A lot of systemd services exist as system service, or user service.
The system service can usually be enabled in the configuration.nix
file, but I haven't figured out how to do that yet for user services, except when defining a completely new user service.
Similar, you usually install programs by adding them to the environment.systemPackages
list, but sometimes you can also just enable them like programs.steam.enable
.
For Nix in general, there often seems to be more than one way of doing something, and as a newcomer it's unclear which one is the best (e.g. flake or no flake).
My biggest struggle was automatically starting a Wayland compositor and then my launcher. It was very easy to start cage but remembered that I had problems making it work well when the video projector wasn't turned on. Then I discovered that Weston has a kiosk shell, which does exactly what cage is doing, just with an more feature complete compositor. I tried to configure a display manager like sddm first and use it to auto launch Weston, but failed to make it work. Instead, I wrote a systemd user service based on the documentation to launch it like so:
systemd.user.sockets.weston = {
listenStreams = ["%t/wayland-0"];
};
systemd.user.services.weston = {
enable = true;
unitConfig.ConditionUser = "htpc";
requires = [ "weston.socket" ];
after = [ "weston.socket" ];
before = [ "graphical-session.target" ];
serviceConfig = {
Type = "notify";
TimeoutSec = "60";
WatchdogSec = "20";
ExecStart = "${pkgs.weston}/bin/weston --modules=systemd-notify.so --drm-device=card1";
Environment = "PATH=/run/current-system/sw/bin";
};
wantedBy = ["default.target"];
};
As I already mentioned, my challenge was that I have a video projector connected, but not always turned on.
So by default, when the PC boots, not display is connected.
In that case, Weston (and most other compositors) will usually exit immediately.
Even if the compositor starts, it might not automatically reconfigure the outputs when the projector is turned on (like cage) so you need an extra script to do that.
With Weston however, I figured out that I can force it to use a specific output at a specific setting, always.
This is my configuration to achieve this (together with the --drm-device-card1
argument):
[core]
shell=kiosk
xwayland=true
[output]
name=HDMI-A-3
mode=173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync
force-on=true
[autolaunch]
path=/home/htpc/launcher.sh
watch=true
I generated the mode line using the cvt
tool as suggested in this documentation
This project isn't finished yet, but I already can use the PC as a speaker for Spotify, and from my launcher start the Jellyfin client, Steam in big picture mode and browser instances in kiosk mode to browse images on my self hosted Immich instance.