# Declarative Configuration with NixOS

NixOS is a GNU/Linux operating system based around the Nix package manager. Nix was developed as a research project but is now fully functional open source project with a vibrant community.

The advantage of Nix is that the software configuration can be completely defined by a single file containing a single “Nix expression” in its own domain specific language and always evaluates with the same result. This means that you can be sure that the system will always end up in the same state if it is supplied the same configuration file.

This post is centered around my experience with NixOS and Nix on it. Though, it should be noted that it is possible to install Nix on top of other distributions/OSs such as Arch Linux, Mac OS X etc as well.

# The NixOS Pitch

## 1. Declarative configuration

In NixOS, configuration is defined by declaring what is needed. Textbook example would be declaring GNOME or KDE as desktop environment.

services.xserver.desktopManager.gnome3.enable = true;
services.xserver.desktopManager.plasma5.enable = true;

These two lines will install functional GNOME and KDE Plasma Desktop Environment (DE). Visit list of available desktop environments to check if your favourite DE is supported. Read on to learn how to use these lines.

## 2. Instant & Painless rollbacks

On every configuration change and system rebuild, Nix stores the complete state of the system as described in the updated configuration as a new “generation”. If you start with GNOME enabled and then replace it with KDE and rebuild, it will cause Nix to generate a new generation with KDE enabled numbered 1 higher than the last generation. All of these generations are accessible via the boot menu in NixOS.

Switching generations or rollbacks are done by selecting one of the previous generations during the boot process. So, if your most recent change or system update broke your computer, you can simply reboot, select previous generation and get on with your work. You could also try correcting your changes and report troubling updates.

## 3. DevOps heaven

Building a new generation will never cause a server to crash as all the builds are done in an isolated environment and they never affect the current running system. In fact, updates in NixOS are atomic in nature because updating on NixOS simply means updating symbolic links which is an atomic operation in Linux. In addition to that, components (barring the Kernel and the Init system) can be updated even during runtime without any issues.

With declarative configuration and atomic updates, there is never a reason to worry about the state of a system. One can be assured that the computer will always end up in a known state. So, with NixOS, there is no need to tend to every server computer like a child. No need to manually setup server and/or run frail shell scripts or non portable housekeeping programs to bring them up and keep them running.

# Why’d I come to NixOS?

1. I’ve heard good things about NixOS from a lot of sources.
2. Recent Arch Linux update broke my installation which gave me a reason to try NixOS.
3. NixOS looked too good to be true. So, I had to see it for myself. I mean: reproducible single file configuration and instant rollbacks. Who wouldn’t want that?

# Quickly setup NixOS

NixOS is a very young distribution and they don’t have a GUI installer as of yet. So, you’ll have to be comfortable with the terminal to install it. Also, you’ll need a fast internet connection because the installer downloads lot of data during the install. This section is an overview of the install process as I do not intent to regurgitate what is already written in the official install guide.

You’ll need at least one partition where NixOS will be installed. With UEFI (which is enabled on all modern computers) you’ll need a boot partition to install the bootloader and need to disable Secure Boot. Note that you can probably reuse a boot partition already provided by Windows or other distributions. You also need a Linux swap partition if you want suspend-to-disk/hibernate enabled.

## 2. Boot the installer

Download the latest ISO files for NixOS and then ‘dd’ it to a flash drive.

$dd if=iso_image.iso of=/dev/flashDriveNode bs=1M # substitute correct values Then boot the flash drive on your computer. ## 3. Prepare for installation Mount the partition where you want to install NixOS at /mnt, then mount the UEFI boot partition at /mnt/boot and finally turn on swap if you have a swap partition. # mount /dev/rootFSNode /mnt # substitute correct values # mkdir -p /mnt/boot # mount /dev/UEFINode /mnt/boot # substitute correct values # swapon /dev/swapNode ## 4. Generate initial configuration Let Nix detect your hardware and generate a starter configuration file. # nixos-generate-config --root /mnt Then you can edit the generated file at /mnt/etc/nixos/configuration.nix with your favourite editor to specify the bootloader you want to use. For UEFI: # ... some lines imports = [ ./hardware-configuration.nix ]; boot.loader.systemd-boot.enable = true; # you need these line boot.loader.efi.canTouchEfiVariables = true; # ... more lines for MBR: # ... some lines imports = [ ./hardware-configuration.nix ]; boot.loader.grup.device = "/dev/bootDiskNode"; # substitute correct values # ... more lines ## 5. Install NixOS Make sure that your internet is working and then run this command: # nixos-install This should set you up with a working NixOS installation and you should be able to reboot into NixOS after this. # Stuff your install with awesomeness Now that you have a working installation, you can take a look at a basic NixOS configuration to see how the pieces fit together and how you can add more flair to your installation. # Help is available in the configuration.nix(5) man page # and in the NixOS manual (accessible by running ‘nixos-help’). { config, pkgs, ... }: { imports = [ # Include the results of the hardware scan. ./hardware-configuration.nix ]; boot.tmpOnTmpfs = true; # mount /tmp as tmpfs # Use the systemd-boot EFI boot loader. boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; # modify NVRAM networking.hostName = "localhost"; networking.networkmanager.enable = true; time.timeZone = "Asia/Kolkata"; # List packages installed in system profile. To search, run: #$ nix search wget
environment.systemPackages = with pkgs; [
gcc # compiler
git # distributed version control system
# you can add more packages here
];

# Enable the X11 windowing system.
services.xserver.enable = true;
services.xserver.layout = "us";
services.xserver.xkbOptions = "eurosign:e";

services.xserver.libinput.enable = true;

# Enable the KDE Desktop Environment.
services.xserver.desktopManager.plasma5.enable = true;
services.xserver.displayManager.sddm.enable = true; # GUI Login screen

users = {
mutableUsers = false; # enable declarative user management
extraUsers.someuser = {
description = "Some User";
extraGroups = [ "audio" "networkmanager" "wheel" ];
isNormalUser = true;
uid = 1000;
};
};
system.stateVersion = "18.03"; # Compatible NixOS version
}

You can also view my computer’s configuration in shreyanshk/nixos-config repository for more examples. :-D

# Working with NixOS

## 1. Virtual environments on steroids

Many projects need multiple components in different languages, file formats, libraries etc. Generally, for such projects one may have to make multiple virtual environments for all the different components. For example, if an application is using Python and friends for number crunching and Elixir for the website then two virtual environments need to be taken care of as two different languages are involved. It can certainly be improved as shown by Nix.

Nix improves on this by providing a system-wide standard way to work with virtualenv(s) with a single file. Consider the configuration file below:

with import <nixpkgs> {};

stdenv.mkDerivation rec {
name = "somename";
env = buildEnv { name = name; paths = buildInputs; };
buildInputs = [
cudatoolkit
elixir_1_5
python36
python36Packages.numpy
python36Packages.pytorch
];
}

A shell in this virtualenv can be launched by simply running:

$ls shell.nix$ nix-shell
[nix-shell:~/dir]$In fact, if provided with full configuration file it is possible to quickly launch a Virtual Machine containing the required configuration.$ NIXOS_CONFIG=./config.nix nixos-rebuild build-vm
... lots of lines
Done. Virtual machine can be started by running <some long path here>
$./result/bin/run-<hostname>-vm ## 2. Imperative installations on NixOS Sometimes, you simply need to install rarely used applications quickly. It is useful to be able to skip all the hoops and just get on with your task at hand. To do this one can use the nix-env utility. For example to install Wireshark, you could run this command:$ nix-channel --list
nixuns https://nixos.org/channels/nixos-unstable
$nix-env -iA nixuns.wireshark installing 'wireshark-qt-2.6.3' That should get you up and running with Wireshark in a single command. # Advantages of NixOS ## 1. Version controlled system configuration Since the configuration can be defined in a single text file or in a directory containing multiple files, the system state can now be committed to a version controlled repository just like application code. ## 2. No dependency conflicts If you ever had to simultaneously work with different applications which depend on conflicting versions of libraries or programming languages, you’ll see that it’s very tricky to get them working correctly because environment variables and hard coded paths. In NixOS, dependencies are not a problem. I don’t mean “almost”. It’s really never. Everything is installed in it’s own isolated environment and the environment is marked read-only after the install. During invocation, NixOS makes sure that an application’s dependencies are passed to it. For example, flask (an awesome micro web framework) executable is really a bash script which sets up the environment and then call the actual script. #! /nix/store/<path to bash>/bash -e export PATH='/nix/store/<some long paths>'${PATH:+':'}$PATH export PYTHONNOUSERSITE='true' exec -a "$0" "/nix/store/<actual path to flask>"  "${extraFlagsArray[@]}" "$@"

Heck, you can install Flask without installing Python. You can work with applications depending on Ruby 2.3, 2.4 and 2.5 simultaneously.

## 3. Great out-of-the-box experience

I was pleasantly surprised to find out the attention to details given in NixOS. Few of the things that I would like to point out are:

• Hardware works together nicely and most hardware keys work without any extra steps. The keyboard lights turn off with display backlight. Display brightness changes are smooth and gradual in NixOS. Going standby pauses media playback.
• Available shells are already configured with sane defaults. This is possibly the first time I don’t mind using the provided shell configuration for extended time.
• Enabling most software such as Plymouth and SDDM is a breeze and a boolean variable away.
• Amazing laundry list of packages available in the repository to choose from.

## 4. Binary based? Source based? Both!

Nix packages are maintained in the nixpkgs-channels git repository. This repository contains Nix expressions for building all supported packages and is cached locally by Nix. The Nix community also also created their own Hydra build system which continuously builds packages on updates to the git repository and caches the results. So, if anything is used from here, it’s binary package is downloaded from the cache and directly used like in ArchLinux.

On the other hand, Nix also gives you the flexibility to declaratively define your custom packages or override instructions for predefined packages right in your configuration file. In these cases, the packages are build locally like in Gentoo.

For example, you could override build instructions of the Linux kernel package to you likings and put it inside the system configuration file. In this case, only the Linux kernel will be build locally and as specified by you but everything else will be downloaded from the cache.

Fact: If the cache goes down due to any reason, it will not affect you in anyway as Nix will then simply build the required packages locally.

## 1. Awful documentation

[NixOS community: I’d love to be proven wrong. Please prove me wrong.]

NixOS needs much work in the documentation department. Coming from Arch Linux, my expectations were high and it was only after reading NixOS documentation I realized the value of proper documentation.

Most of the official documentation is not up to date with current best practices in the community. Other sources of documentation in form of blog posts/mailing lists responses/email threads/forums are also useless because Nix is rapidly moving and hence, they become obsolete fast.

I also feel like the official documentation expects that the reader is already deeply knowledgeable in the Nix philosophy and doesn’t explain the details of what’s suggested. For example, the wiki on Python shows a snippet of code but doesn’t explain how/where to use that.

with pkgs;
let
my-python-packages = python-packages: with python-packages; [
pandas
requests
# other python packages you want
];
python-with-my-packages = python3.withPackages my-python-packages;
in ...

From the above piece of code, it’s not immediately clear where these lines should go as it assumes the user will know how to work with the ‘let’ block. That can be improved if the wiki at least pointed the user to an example on how to use this block or the ‘let’ block man page but they don’t. Unfortunately, that’s the case with most of their documentation.

Even the package listing is hard to navigate. For example, the latest version of Firefox is listed under three different names: firefox, firefoxWrapper & firefox-wrapper. I ultimately settled on just the plain firefox and the difference is still not clear to me but I didn’t look much into it because “firefox” worked well for me.

In my experience, it is better to just search and look up at other people’s Nix configurations on GitHub or directly ask on the IRC channel #nixos on Freenode for pointers.

[Arch Linux community: Guys, thank you for your amazing documentation.]

# The competition: GuixSD

GNU took the Nix ideas and philosophy and decided to run with it. The result was called GuixSD. GuixSD is based around the Guix package manager and uses GNU Guile to define configuration files.

GNU Guile is far more approachable than Nix for declaring the configuration because the syntax is much cleaner as it is based on the Scheme programming language and the code is mostly self documenting. For example, this is GNU Hello in Guix:

(package
(name "hello")
(version "2.10")
(source (origin
(method url-fetch)
(uri (string-append "mirror://gnu/hello/hello-" version
".tar.gz"))
(sha256 (base32 "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i"))))
(build-system gnu-build-system)
(synopsis "Hello, GNU world: An example GNU package")
(description
"GNU Hello prints the message \"Hello, world!\" and then exits.")
(home-page "https://www.gnu.org/software/hello/")