2018-02-16
I recently installed NixOS on one of my laptops. I setup XMonad and Taffybar, using stack
to compile them. This is somewhat unconventional on NixOS, so I wanted to write a blog post detailing how I did it.
These steps are aimed for people using NixOS, but they should work for any Linux distro with slight modification.
XMonad on NixOS the normal way
The normal way to use XMonad on NixOS is to enable it in /etc/nixos/configuration.nix
:
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
/etc/nixos/hardware-configuration.nix
];
...
services.xserver.enable = true;
services.xserver.layout = "us";
services.xserver.windowManager.xmonad.enable = true;
services.xserver.windowManager.xmonad.enableContribAndExtras = true;
}
The downside of this is that you must configure which GHC version and XMonad version you want to use in the configuration.nix
file1. This isn't a huge problem, but it makes the solution non-portable to other Linux distros. The method I explain below should be portable to other distros as-is.
XMonad on NixOS the stack
way
Before building XMonad, stack
needs to be installed on the system. In general, you should install the latest version of stack
.2
One easy way to do this is as follows:
- Use your package manager to install
stack
. - Have
stack
upgrade itself to the latest version. - Uninstall the older version with your package manager.
On NixOS
The previous three steps looks like the following when done on NixOS:
Install
stack
withnix-env
:You can check that
stack
is actually installed with the following command:Have stack upgrade itself to the latest version.
stack
will download the latest version of itself and install it to~/.local/bin/
.Make sure
~/.local/bin/
is on yourPATH
. This can be done in your current bash session like the following:The above statement should also be added to your
~/.bashrc
file so thatstack
will be on yourPATH
for every bash session.You can check to make sure the latest version of
stack
is being used:Use
nix-env
to uninstall thestack
executable installed in step 1.
If you ever want to upgrade to a newer version of stack
, you can just run stack upgrade
again.
NixOS-specific stack
settings
There are a few NixOS-specific settings needed for stack
. These should be added to the ~/.stack/config.yaml
file. This is not needed for other distros:
# This file contains default non-project-specific settings for 'stack', used
# in all projects. For more information about stack's configuration, see
# http://docs.haskellstack.org/en/stable/yaml_configuration/
# This enables stack cooperation with nix.
nix:
enable: true
# `pure` set to `true` means that environment variables won't get forwarded
# when running `stack build` and `stack exec`. This is generally not what
# we want when doing day-to-day development.
pure: false
# These are packages passed to the nix environment when running `stack
# build` or `stack exec`. Note that `stack` will NOT be able to see a
# library that has not been passed in here.
packages:
- cairo
- gcc
- gnome2.pango
- gtk2-x11
- libxml2
- pkgconfig
- upower
- x11
- xorg.libX11
- xorg.libXext
- xorg.libXinerama
- xorg.libXrandr
- xorg.libXrender
You must specify the system packages that should be available when running stack build
or stack exec
. The packages specified above are needed for XMonad and Taffybar.
Changes to /etc/nixos/configuration.nix
Before setting up XMonad, some options must be enabled in /etc/nixos/configuration.nix
.
Here is a /etc/nixos/configuration.nix
file with the relevant options:
{ config, pkgs, ... }:
{
...
# Packages to install.
environment.systemPackages = with pkgs; [
...
# I use dmenu from XMonad.
dmenu
...
];
# Enable the X11 windowing system.
services.xserver.enable = true;
# Set the keyboard layout to "us".
services.xserver.layout = "us";
# Enable touchpad support.
services.xserver.libinput.enable = true;
# Enable UPower, which is used by taffybar.
services.upower.enable = true;
systemd.services.upower.enable = true;
...
}
After modifying this file, you should run sudo nixos-rebuild switch
to make sure the changes take effect.
On other distros, this corresponds to the following steps:
Compiling XMonad and Taffybar
Now that we have the latest version of stack
and all required system packages, it is time to compile and install XMonad and Taffybar.
xmonad.hs
XMonad is configured with a file ~/.xmonad/xmonad.hs
. This is just a normal Haskell file. Writing the xmonad.hs
is out of scope for this article, but you should be able to find many examples by googling. My xmonad.hs
is here.
When XMonad is launched, it calls GHC to compile this file and produce a new XMonad binary with all the options you have specified. It then re-execs this new binary.
This works well when GHC and XMonad are installed system-wide. However, when you are using stack
, this obviously isn't going to work. XMonad won't be able to find GHC
.
Recent versions4 of XMonad provide a way around this: a build.sh
file.
build.sh
Instead of trying to call GHC directly, recent versions of XMonad will instead execute a ~/.xmonad/build.sh
file, if it exists. You are free to write any sort of build steps you want in this build.sh
file.
We will be using build.sh
to do the following things:
- Install the
gtk2hs-buildtools
Haskell package. This is used by Taffybar. - Install the
taffybar
Haskell package. - Install the
xmonad
andxmonad-contrib
Haskell packages. - Build the
~/.xmonad/xmonad.hs
file.
My build.sh
file can be found here.5
In the beginning of the build.sh
file, I source a file ~/.xmonad/xmonad_build_vars.sh
. This file looks like the following:
#!/usr/bin/env bash
#
# Set config values used for building and running xmonad and taffybar.
# The directory in $HOME with all the XMonad stuff.
export XMONAD_DIR="${HOME}/.xmonad"
# A directory to use to install the taffybar and xmonad binaries.
export XMONAD_LOCAL_BIN_PATH="${XMONAD_DIR}/local-bin"
# The lts resolver we will use for building with stack.
export XMONAD_STACK_RESOLVER="lts-9.14"
These settings will come in handy later, when actually running XMonad and Taffybar.
Now ~/.xmonad/build.sh
can be run.
This will produce a ~/.xmonad/xmonad-x86_64-linux
executable. This executable is XMonad compiled with your settings from xmonad.hs
. There will also be xmonad
and taffybar
executables in ~/.xmonad/local-bin/
.
Running XMonad and Taffybar
There are many different ways of running XMonad. Two popular ways are to use either startx
or xdm
.
startx
is run from the command line. When run, it executes the file ~/.xinitrc
. xdm
is a graphical login manager. If you enter the correct username and password, it executes the ~/.xsession
file for you.
In general, the contents of these files should be the same. The first part of the script will fork off some background processes. The second part of the script will exec
your window manager (which is XMonad in this case).
~/.xsession
NixOS does not currently have support for startx
, so the remainder6 of this article will be explaining how to use xdm and ~/.xsession
.
If you added the services.xserver.enable = true
setting to your /etc/nixos/configuration.nix
file, then xdm should start up when you boot your computer.
The following is an example ~/.xsession
file. Make sure to make it executable:
#!/bin/sh
# Make sure to add ~/.local/bin to the PATH because that is where `stack` is.
# We use `stack` to run `taffybar`.
export PATH="${HOME}/.local/bin:${PATH}"
# Source xmonad settings for xmonad and taffybar. This reads in the environment
# variables XMONAD_LOCAL_BIN_PATH and XMONAD_STACK_RESOLVER.
# XXX: This must be run using `.` and not `source` on systems where `sh` is
# not `bash`, because xinit always runs this file with `sh` (even if a
# different hashbang is specified). Some versions of `sh` do know the
# `source` alias for `.`.
. "${HOME}/.xmonad/xmonad_build_vars.sh"
# Start taffybar.
PATH="${XMONAD_LOCAL_BIN_PATH}:$PATH" stack exec \
--local-bin-path="${XMONAD_LOCAL_BIN_PATH}" \
--resolver="${XMONAD_STACK_RESOLVER}" \
-- taffybar &
# Start xmonad.
${XMONAD_LOCAL_BIN_PATH}/xmonad &
windowmanagerpid=$!
wait $windowmanagerpid
Some things to keep in mind:
- I've seen systems where
/bin/sh
will always be used to run~/.xsession
and~/.xinitrc
. Some versions ofsh
do not understand bash'ssource
alias for.
. Make sure you don't use any bash-isms in this file. - Taffybar is similar to XMonad in that it recompiles a
~/.config/taffybar/taffybar.hs
file when it is executed. This means that Taffybar must be run withstack exec
, so it gets access to all the correct Haskell libaries. You can see mytaffybar.hs
file here.
You can see my ~/.xsession, which contains some examples of setting environment variables and running background processes.
xdm
When you type your username and password into xdm, it will run your ~/.xsession
file. This should run XMonad. If you are using my xmonad.hs
, you should see Taffybar at the top of your screen. You should also be able to use dmenu
to run any other program with the key Super + p.
Conclusion
This setup is a little complicated, but it works well across distros, whether you are using startx
or xdm
.
Footnotes
This could be done using the
xmonad.haskellPackages
configuration option.↩︎Older versions of
stack
will not work with newer versions of lts.↩︎If you are unsure of which packages are needed, consult your distro's documentation.
If you can't find anything in your distro's documentation, one easy trick is to install XMonad (or another window manager) through your system's package manager. This should pull in all X11-related dependencies. Then you can just uninstall the XMonad package installed through the package manager.↩︎
This functionality was available starting with
xmonad-0.13
.↩︎My
build.sh
file is somewhat complicated. Sincebuild.sh
is run every time XMonad is run, I wantbuild.sh
to be able to run as quickly as possible. In order to run only the minimal number of commands necessary, I am using aMakefile
-like timestamp-based build process. For instance, if thebuild.sh
file is older than thetaffybar
binary, then I don't try to reinstall Taffybar. However, if thebuild.sh
file is newer than thetaffybar
binary, then I runstack install taffybar
to reinstall Taffybar.In practice, this is very flexible and works reasonably well. However, if I was starting from scratch, I might elect to use a proper build system, like
make
orshake
.Another possibility would be to create a Haskell package for your XMonad configuration, complete with a
stack.yaml
file. Runningstack build
in this directory would create the XMonad executable for your config.↩︎If you want to use the
startx
approach on a different distro, you will need to install a package that provides thestartx
binary. This package is generally called something likexinit
orx11-xinit
.After installing this package, create the
~/.xinitrc
file and runstartx
. XMonad should start up.↩︎