NixOS

A functional GNU/Linux distro

Robert Helgesson <robert@rycee.net>

About me

  • Backend programmer

  • Nix user and contributor since ~2014

Agenda

  • The Nix package manager

  • The Nix expression language

  • The NixOS GNU/Linux distribution

  • Demo

Nix

A purely functional package manager

What is a package?

  • Set of files in a file hierarchy

  • Built using a build recipe, e.g.

    • source code

    • other packages it depends on

    • build instructions

    • etc.

Immutability

Once built a package never changes

Determinism

Nix treats building a package as a function

build : recipe → package

and building a recipe always gives the same result

Allows identifying a package by hashing its recipe!

Nix store

Built Nix packages are kept in the Nix store

  • Key is the hashed recipe

  • Value is the built recipe

Nix store example

/nix/store/mkk5m2kflfrc5w5pl50f88rhdj9vwsdf-cowsay-3.03
├── bin
│   ├── cowsay
│   └── cowthink -> cowsay
└── share
    ├── cows
    │   ├── beavis.zen.cow
    ┆   ┆
    │   └── www.cow
    └── man
        └── man1
            ├── cowsay.1.gz
            └── cowthink.1.gz -> cowsay.1.gz

Nix store example

$ /nix/store/mkk5…-cowsay-3.03/bin/cowsay 'Hello, BornHack!'
 __________________
< Hello, BornHack! >
 ------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Nix store example

$ find /nix/store -maxdepth 1 -name '*-cowsay-3.03'
/nix/store/igk92axpn6gdj232j802sdsc1fm8nj25-cowsay-3.03
/nix/store/mkk5m2kflfrc5w5pl50f88rhdj9vwsdf-cowsay-3.03
/nix/store/cd4i6g0h3ns4xqg1ac4mnhzigcnzy7cm-cowsay-3.03
/nix/store/mhfaj4miflqz1abn60nnkrpaxcg310i9-cowsay-3.03

User operations

Non-privileged users can install packages accessible in only their $PATH

[simon:~]$ nix-env -f '<nixpkgs>' -iA cowsay
…
[simon:~]$ which cowsay
/home/simon/.nix-profile/bin/cowsay
[simon:~]$ readlink $(which cowsay)
/nix/store/mkk5…wsdf-cowsay-3.03/bin/cowsay
[jane:~]$ which cowsay
which: no cowsay in (…:/home/jane/.nix-profile/bin:…)

User operations

Install

$ nix-env -f '<nixpkgs>' -iA cowsay

Uninstall

$ nix-env -e cowsay

Nix expression language

  • Used, for example, to describe Nix packages

  • A pure, lazy, functional language

Basics

1 + 2
# ⟹ 3

if true then 1.5 else -1.5
# ⟹ 1.5

let b = false; in b || !b
# ⟹ true

Strings

"My system: ${builtins.currentSystem}"
# ⟹ "My system: x86_64-linux"

''
  1 + 5
    = ${toString (1 + 5)}
''
# ⟹ "1 + 5\n  = 6\n"

Lists & attribute sets

[ 1 2 ] ++ [ "three" ]
# ⟹ [ 1 2 "three" ]

{ key1 = "v1"; key2 = 2; }
# ⟹ { key1 = "v1"; key2 = 2; }

{ key1 = "v1"; key2 = 2; }.key2
# ⟹ 2

Functions

add = x: y: x + y
add 5 6
# ⟹ 11

mul = { x, y }: x * y
mul { x = 2; y = -9; }
# ⟹ -18

Package description

{ stdenv, fetchurl, pkgconfig, libav, libxslt }:

stdenv.mkDerivation rec {
  name = "unpaper-6.1";

  src = fetchurl {
    url = "https://www.flameeyes.eu/files/${name}.tar.xz";
    sha256 = "0c5rbkxbmy9k8vxjh4cv0bgnqd3wqc99yzw215vkyjslvbsq8z13";
  };

  nativeBuildInputs = [ pkgconfig ];
  buildInputs = [ libav libxslt ];

  meta = {
    homepage = "https://www.flameeyes.eu/projects/unpaper";
    description = "Post-processing tool for scanned sheets of paper";
    license = stdenv.lib.licenses.gpl2;
    maintainers = [ stdenv.lib.maintainers.rycee ];
  };
}

NixOS

A GNU/Linux distribution built on Nix

A distro in 4 easy steps

Step 1

Describe system using the Nix expression language

{
  fileSystems = {
    "/"     = { device = "/dev/sda2"; fsType = "btrfs"; };
    "/boot" = { device = "/dev/sda1"; fsType = "ext2"; };
  };

  networking.extraHosts = ''
    192.36.125.18 ping.sunet.se ping
  '';

  # …
}

Step 2

Create build recipe that builds the system files

$ cat /nix/store/i5si…20jx-sysconf/etc/fstab
/dev/sda2 /     btrfs defaults 0 0
/dev/sda1 /boot ext2  defaults 0 2

$ cat /nix/store/i5si…20jx-sysconf/etc/hosts
127.0.0.1     localhost
192.36.125.18 ping.sunet.se ping

Step 3

Create a boot loader entry that populates /etc when loading the OS

/etc/fstab ⟶ /nix/store/i5si…20jx-sysconf/etc/fstab
/etc/hosts ⟶ /nix/store/i5si…20jx-sysconf/etc/hosts
           ⋮

Step 4

Reboot and hope it works

If it didn’t, tell GRUB to boot the old configuration

🤯

Demo!

System-wide packages

environment.systemPackages = [
  pkgs.cowsay
  pkgs.tree
]

after nixos-rebuild switch:

$ which cowsay
/run/current-system/sw/bin/cowsay
$ which tree
/run/current-system/sw/bin/tree

Dynamic packages

environment.systemPackages = [
  (pkgs.emacsWithPackages (epkgs: [ epkgs.nyan-mode ]))
]

Configure programs

programs.bash.shellAliases = {
  hello = "cowsay Hello, Bornhack!";
}

after nixos-rebuild switch:

$ type hello
hello is aliased to `cowsay Hello, Bornhack!'

Configure services

services.redis = {
  enable = true;
  databases = 8;
  extraConfig = "maxclients 500";
}
$ redis-cli
127.0.0.1:6379> CONFIG GET databases
1) "databases"
2) "8"
127.0.0.1:6379> CONFIG GET maxclients
1) "maxclients"
2) "500"

Documentation

man pages

$ man configuration.nix

HTML

$ nixos-help

Oopsie

fileSystems = {
  "/" = lib.mkForce { device = "/no/such/device"; };
}

Ad-hoc “packages”

A project directory can contain a file default.nix with build instructions. Inside the directory run

  • nix-build to build the project

  • nix-shell to open a shell with all build dependencies

Nix shebang

#! /usr/bin/env nix-shell
#! nix-shell -i bash -p inotify-tools

while
    nix-build
    inotifywait -e close_write \
                custom-css.html \
                default.nix \
                presentation.mdwn
do : ; done

Wider ecosystem

Summary

Benefits

  • Declarative configuration language

  • Reproducible

  • Atomicity

  • Fearless upgrades thanks to rollback

Drawbacks

  • Steep learning curve

  • Tricky to package software that assumes a “standard system”

  • Relatively small user base

Thanks!

Extra slides

Proceed with caution

Nix expression language

Path literals

./presentation.mdwn
# ⟹ /home/rycee/presentation.mdwn

/home/rycee/presentation.mdwn
# ⟹ /home/rycee/presentation.mdwn

"${/home/rycee/presentation.mdwn}"
# ⟹ "/nix/store/8d4s4…78881-presentation.mdwn"

Laziness

let x = { a = x; }; in x
# ⟹ { a = { ... }; }

let x = { a = x; }; in x.a
# ⟹ { a = { ... }; }

let x = { a = x; }; in x.a.a
# ⟹ { a = { ... }; }

let x = { a = x; }; in x.a.a.a
# ⟹ { a = { ... }; }

Home Manager

“NixOS for $HOME

Install packages

{ config, pkgs, ... }:

{
  home.packages = [
    pkgs.tree
    pkgs.cowsay
  ];
}

… and

$ cowsay It works!
 ___________
< It works! >
 -----------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Program modules

programs.git = {
  enable = true;
  userName = "John Doe";
  userEmail = "john.doe@example.org";
};

… and

$ cat .config/git/config
[user]
email=john.doe@example.org
name=John Doe

User services

services.random-background = {
  enable = true;
  imageDirectory = "%h/backgrounds";
  interval = "1h";
};

E-mail accounts

programs.msmtp.enable = true;
programs.mbsync.enable = true;
accounts.email.accounts.test-account = {
  primary = true;
  address = "john.doe@example.org";
  userName = "john.doe";
  realName = "John Doe";
  passwordCommand = "getpass john.doe@example.org";
  imap.host = "imap.example.org";
  smtp.host = "smtp.example.org";
  msmtp.enable = true;
  mbsync.enable = true;
};