Aleksejs Ivanovs

A year and a half with NixOS

Aleksejs 23.03.2019

It's been a year and a half since I switched to NixOS as primary operating system on desktop and servers. I think I gathered enough experience and can share my impressions about this OS.

I will try to not dive into technical details about how this OS and packaging manager works under the hood. I'm also not trying to be technically precize. My point is to share some overall information for people who want to try this OS for work.



Background


Previously I've tried several Linux distributives - RedHat, CentOS, Debian, *buntu, Arch. All of them contained some flaws - some unique for distributive and some are common. One of the common flaws is absense of rollback feature. Some of distributives are bad in solving dependencies - they can easily overwrite some lib and break software that uses it. These distros are also bad in reproducing configuration - I don't remember how many times I've spent reinstalling ubuntu and making the same configuration all over again. I also started to study functional languages and became a fan of declarative part of functional programming. That's why, when I heard about a Linux distributive that is declarative, atomic, supports rollbacks and solves tons of problems I've decided to give it a try.


Intro


NixOS is a Linux based operating system build around nix package manager. Nix package manager is the declarative package manager that utilizes so called nix language. Nix language is simple, turing complete, functional language which is built specifically for Nix package manager. Nix package manager uses a declarative approach for OS configuration and the installation of packages.

If you are familiar with functional languages then you might know what is lambda or anonymous (or arrow) function. It is a function with no name that has two parts - arguments and function body that usually returns a value. All nix configuration files are in fact functions that return configurations. The format of lambda function in nix is {args}: {body}.

The usual way to install NixOS is to create a .nix file with a function written in nix that describes computer configuration, packages and services to install etc. A function in the .nix file changes options of operating system. You can find a list of options here.

Let's take a look on example:

{ config, pkgs, ... }:
{
  
  #imports go here
  imports = [
    ./hardware-configuration.nix
  ];

  boot.loader.systemd-boot.enable = true; # (for UEFI systems only)

  services = {
    sshd.enable = true;
    printing = {
      enable = true;
      drivers  = [
        pkgs.gutenprint
        pkgs.gutenprintBin
      ];
    };
    avahi = {
      enable = true;
      nssmdns = true;
      allowPointToPoint = true;
    };
  };

  environment.systemPackages = with pkgs; [
    bash
    wget
  ];
}

The first line contains parameters that are passed by NixOS building tools when build process is initiated. As you can see in the next lines, you can split configuration file into several files and import them. Next, there's an option boot.loader.systemd-boot.enable, which is set to true. It tells NixOS to enable a systemd-boot EFI boot manager. All child options can be nested in one parent option - see services option. At the end, an option environment.systemPackages tells NixOS to install packages on system level - these packages will be available to all users and will be automatically updated when you update system.

Usually you save this file as /etc/nixos/configuration.nix and then execute command nixos-rebuild switch. The switch key tells NixOS to create a new user environment called generation and to switch to it. I will get back to generations later. The nixos-rebuild tool also supports other useful keys - test (creates temporary generation which exists until you restart or switch to other generation), build-vm (creates QEMU virtual machine file using .nix file) etc.

Now let's consider that you have NixOS installed and you want to make changes - you want to install firefox. You just add firefox in the list of packages and run nixos-rebuild switch again. In some seconds firefox will be available to use, and you'll have a new generation that contains firefox.


Generations


Generations can be considered as "versions" of your NixOS installation. Each time you rebuild a system, you create a new user environment, and the symlink to it is called generation. It contains an information about installed packages and services and configuration. When you run rebuild, a new generation is created and a GRUB entry appears. Thus, if something goes terribly wrong, you can always reboot and choose a previous generation in GRUB menu. In fact, if your system is not totally halted, you don't need to reboot - you can easily switch to a previous generation using nixos-rebuild switch --rollback.

Usually generations don't occupy a lot of disk space - they are not virtual machines or containers, they just contain a collection of symlinks. But if you need to clean up old generations then you can use several utilities, one of which is nix-collect-garbage.


nix-env


nix-env is a utility that allows you to make changes in generations. For example you can install a package the way you used to do it with other package managers - nix-env --install firefox. This command won't save changes in your .nix file, so if you want to have a system that can be reproduced on other machine it's better to update .nix file and use nixos-rebuild utility.

nix-env can also be used to switch between generations. nix-env --rollback will switch to previous generation. To switch to a specific generation you can use nix-env --switch-generation 42 or nix-env -G 42. You can see a list of generations with nix-env --list-generations, delete generations with nix-env --delete-generations 42.


Nix Store


Nix store is a directory (usually /nix/store) that contains informations about packages. Information about every package is stored in it's subdirectory whose name is built using a hash of a package file that contains info about package version, inputs etc. Thus, information about different versions of say firefox will be stored in different subdirectories of nix store. That means that you can have all possible versions of any software on your PC and they won't conflict with each other. It solves a problem when two different packages depend on different versions of the same lib. Some other distributives also offer solutions to this problem but they are usually not so general and don't guarantee solutions in all cases.


Atomicity


One of the most important features of NixOS is atomicity. When you rebuild your system it won't create a new generation and won't switch to it until rebuild process is finished without errors. The rebuild process is whole, atomic. This is guaranteed by the way how nix package manager installs packages. Packages stored in nix store are symlinked to "profiles" - entries that serve as a layer between user and generations. User can be linked to several profiles and several users can use one profile. Profile contains symlinks to installed packages. Specific generation merges symlinks from profiles linked to this generation. This approach gives multiple benefits. One of them is that you never need to reinstall your OS - just change .nix file and rebuild. You also get a huge freedom in different configurations. And also you can use different generations as different versions of NixOS.


Nix-shell


nix-shell is a very powerful utility. One of ways to use it is to create a virtual shell on top of your installation with some new packages temporarily installed. For example, if you don't have an acpi package you can run nix-shell -p acpi. You will enter a shell that will have acpi installed. After doing something with acpi you just type exit and you will get back to your usual shell where acpi is not available (technically it will be stored in nix store but symlink will be destroyed). It is very useful for one-time runs or for tests.


Binary cache


NixOS builds all open source packages from source but thanks to the fact that every package is described by it's package file (that contains version, repo address, inputs etc) there's no need to rebuild the same package for the same architecture. That's why nix uses binary cache - if some package was already built with specific inputs for specific architecture it will simply download a binary from cache. This saves a lot of time when rebuilding your system.


Nixops


Nixops is a useful (though, very fresh and misses a lot of features) tool that allows you to use the declarative approach of nix to configure and deploy remote machines, virtual machines etc. Aside from giving you all features of nix, it also gives you an option to share a server state to other people. Nixops guarantees reproducable builds and the option to rollback. Atomicity gives you next to zero downtime while rebuilding.


Downsides


NixOS community is not very big. While NixOS solves a lot of problems by design, package files should still be managed manually. While Debian or similar ecosystems have huge community and a long history of problem solving, NixOS only gains it's popularity and needs a lot of hard work to prepare as many packages as possible. Also, NixOS is not friendly for people who used to use GUI - there are no user friendly installers.


My experience


My first installation went very well. It took some time to play around with network configuration (I needed NixOS to be installed via WiFi). I've tried different desktop environments and they all are working quite good. While experimenting with some very nightly video drivers I've managed to break my system but could easily roll back to previous configuration. I haven't reinstalled a system since I've installed it - reinstalls don't have any meaning in NixOS.

The problems that I faced during a period of using NixOS are mostly with problematic packages, unsolved dependencies, drivers etc. For example, I still cannot figure out how to install drivers for a wifi scanner on NixOS (to be honest, WiFi scanning is a pain on many distros, not only NixOS). I also couldn't figure out how to use Arduino IDE with esp32 drivers - there are so many dependencies that are not solved and rely on specific distro. It's quite common that some package is missing, for example, I needed a MicroPython utility called ampy and it wasn't there. Most of the problems I had I could solve with the help of very friendly people from #nixos channel on freenode IRC server. If you decide to try and install NixOS and face some problems do not hesitate to ask them for help. On your first install I would recommend a tutorial available in nixos homepage, especially if you use UEFI. Also make sure to read Nix Pills - a good and detailed intro into nix. Also you can always search for .nix files on github for some ideas.