Diving Into NixOS (Part 3): Lightweight Startup With xinit
Brief case study
In this post, we continue on directly from part 2. I would recommend reading the previous part to gain an understanding of the Nix ecosystem if you are not already familiar with it. In the spirit of continuing the train of thought, instead of plainly listing some of the configuration options and explaining them without any context, I thought it would be more interesting to examine the steps I took to configure xinit.
Configurable services
As mentioned, NixOS has a wealthy host of configuration options
that can be set in the configuration.nix
file. The set of options that are of
interest to us are the services. As far as I'm concerned, each of these is
more-or-less mapped to the setup of one-or-more systemd
services.
Some examples include CUPS, for printing; or GitLab's CI
runner, for dedicated runners; or most importantly in our case,
a service for managing X11.
X server options
When installing NixOS, the default configuration.nix
will probably have
already filled-out some sane settings for the xserver. These defaults allow
users to log in to the system after a fresh install via a display manager before
it throws you into the desktop environment.
{
# ...
# Enable the X11 windowing system.
services.xserver.enable = true;
services.xserver.layout = "us";
services.xserver.xkbOptions = "eurosign:e";
# Enable touchpad support.
services.xserver.libinput.enable = true;
# Enable the KDE Desktop Environment.
services.xserver.displayManager.sddm.enable = true;
services.xserver.desktopManager.plasma5.enable = true;
# ...
}
Getting Nix to play nicely with xinit
For many, this may just as well be exactly what they want/need. If you're still reading, however, chances are you would like to avoid using a bloated desktop environment altogether and keep things light. This can be achieved using a combination of standard shell login and xinit to boot into a window manager.
- Log in to tty using username and password.
- Run
startx
.
For the most part, configuring xinit is quite simple - the Arch Wiki is our ever helpful resource in times like this.
# .xserverrc
#!/usr/bin/env sh
exec /run/current-system/sw/bin/Xorg -nolisten tcp -nolisten local "$@" "vt""$XDG_VTNR"
# .xinitrc
#!/usr/bin/env sh
exec bspwm
Note a couple of things:
- Remember that
allmost programs, libraries, etc. are symlinked under respective directories under/run/current-system/sw/
, so if you need to specify the full path to a binary, that would be the first place to look. - In the example, I start bspwm as my window manager (wm), but you could use any wm you choose as long as they are built for X.
- Naturally the
xorg.xinit
,bspwm
, andsxhkd
(bspwm requirement) packages will need to be added to theenvironment.systemPackages
list in the Nix configuration in order to make them available to all users.
Now because NixOS typically uses systemd to start X, unlike other Linux
distributions, all the system configuration files for Xorg (modules that define
drivers for graphics, input, etc.) are not available in a central directory.
This means that trying to just run startx
after preparing our .xserverrc
and
.xinitrc
will not work.
My first intuition was that I should symlink additional directories of the
filesystem hierarchy that included the appropriate Xorg modules. This can be
done in a single line via the environment.pathsToLink
option.
environment.pathsToLink = [ "/etc" ];
Unfortunately, although it works, it ends up symlinking the contents of other
packages with /etc/
subdirectories. We refer to this as polluting the
filesystem which ends up exposing more files than we need and leaving the
system configuration dependent in a state that is no longer
atomic - an unsatisfactory solution.
With thanks to the friendly NixOS community, I
discovered that a similar effect could be achieved by disabling
services.xserver.autorun
and enabling services.xserver.exportConfiguration
.
Whether to symlink the X server configuration under /etc/X11/xorg.conf.
By enabling the option, our environment still polluted, but only with the Xorg modules, allowing xinit to read the configuration from the expected directory and launch correctly!
Through this experience and community feedback, I learned a few additional
things about X11. For example, startx
can receive a number of options on the
command line, such as the program to execute when X is initialized, i.e. startx /run/current-system/sw/bin/bspwm
. Another interesting point was that unless it
is necessary, it is good practice to leave the resolution parameter in the
xserver options unset as xrandr
can detect the appropriate monitor
resolution for us.
With a single, key configuration option, we were able to bend NixOS in a way that would allow us to use a hybrid approach to logging in and starting X. Ideally, this would be implemented as a dummy display manager (it looks like this is an officially supported option on the unstable NixPkgs channel - not available in 18.09 Jellyfish at time of writing - since #47773) to avoid pollution of the filesystem with extraneous symlinks.
In the final part of the series, we will take a look at how my
development workflow has greatly benefited from using nix shell
in conjunction
with a couple of other utilities.