After I heard a great talk (at FrOSCon) given by @fpletz on NixOS, which is a Linux distribution built on top of the purely functional Nix package manager, … and I am on holiday this week … I decided to give it a try.

So I backed up my homedir and started replacing my Ubuntu installation, without much of a clue on NixOS … just being experienced with other more or less custom Linux installations (like Linux From Scratch back in the days, various Gentoo boxes, etc.)

Here’s my report and a collection of first experiences with my own very fresh installation, underlining findings which seem important to me. This is the first part (and I intend to add at least two more: one on package customisation and one on development environments)…

Requirements & Constraints

  • my laptop: Thinkpad X220, 8 GB RAM, 120 GB SSD
  • NixOS replacing Ubuntu (preserving nothing)
  • fully encrypted root filesystem & swap space (LUKS)
  • i3 improved tiling window manager among screen locking et al

Starting out

First read NixOS’ manual, at least the first major chapter (Installation) and the Wiki page on Encrypted Root on NixOS.

On NixOS’ Download Page there are effectively two choices, a Graphical live CD and a minimal installation. The Thinkpad X220 doesn’t have a CD-ROM drive, and the only USB stick I could find was just a few megabytes too small to fit the graphical live cd … so I went with the minimal installation …

First steps are fairly common, using fdisk to create a new partition table, add a small /boot partition and another big one taking the rest of the space (for LUKS). Then configure LUKS like

$ cryptsetup luksFormat /dev/sda2
$ cryptsetup luksOpen /dev/sda2 crypted

… and create a LVM volume group from it + three logical volumes (swap, root filesystem and /home)

$ pvcreate /dev/mapper/crypted
$ vgcreate cryptedpool /dev/mapper/crypted
$ lvcreate -n swap cryptedpool -L 8GB
$ lvcreate -n root cryptedpool -L 50GB
$ lvcreate -n home cryptedpool -L 20GB

… last not least format those partitions …

$ mkfs.ext2 /dev/sda1
$ mkfs.ext4 /dev/cryptedpool/root
$ mkfs.ext4 /dev/cryptedpool/home
$ mkswap /dev/cryptedpool/swap

… and finally mount them …

$ mount /dev/cryptedpool/root /mnt
$ mkdir /mnt/{boot,home}
$ mount /dev/sda1 /mnt/boot
$ mount /dev/cryptedpool/home /mnt/home
$ swapon /dev/cryptedpool/swap

Initial Configuration

So we’re now ready to install, … and with other distributions we would now just launch the installer application. Not so with NixOS however, it expects you to create a configuration file first … to simplify this it provides a small generator tool:

$ nixos-generate-config --root /mnt

… which generates two files in /mnt/etc/nixos:

  • hardware-configuration.nix which mainly lists the required mounts
  • configuration.nix, after all the config file you’re expected to edit (primarily)

The installation image comes with nano pre-installed, so let’s use it to modify the hardware-configuration.nix file to amend some filesystem options:

  • configure root filesystem to not store access times and enable discard
  • configure /home to support discard as well

… so let’s add option blocks (and leave the rest untouched):

  fileSystems."/" =
    { device = "/dev/disk/by-uuid/9d347599-a960-4076-8aa3-614bb9524322";
      fsType = "ext4";
      options = [ "noatime" "nodiratime" "discard" ];
    };

  fileSystems."/home" =
    { device = "/dev/disk/by-uuid/d4057681-2533-41b0-9175-18f134d7401f";
      fsType = "ext4";
      options = [ "discard" ];
    };

I have enabled (online) discard on the encrypted filesystems as well as on the LUKS device (see below), also known as TRIM support. TRIM tells the SSD hardware which parts of the filesystem are unused and hence benefits wear leveling. However using discard in combination with encrypted filesystems makes some information (which blocks are unused) leak through the full disk encryption … an attacker might e.g. guess the filesystem type from the pattern of unused blocks. For me this doesn’t matter much but YMMV ;-)

So let’s continue and edit (read: add more stuff to) configuration.nix. First some general system configuration:

  # Define on which hard drive you want to install Grub.
  boot.loader.grub.device = "/dev/sda";

  # Tell initrd to unlock LUKS on /dev/sda2
  boot.initrd.luks.devices = [
    { name = "crypted"; device = "/dev/sda2"; preLVM = true; allowDiscards = true; }
  ];

  networking.hostName = "faulobst"; # Define your hostname.

  # create a self-resolving hostname entry in /etc/hosts
  networking.extraHosts = "127.0.1.1 faulobst";

  # Let NetworkManager handle Network (mainly Wifi)
  networking.networkmanager.enable = true;

  # Select internationalisation properties.
  i18n = {
    consoleFont = "Lat2-Terminus16";
    defaultLocale = "en_US.UTF-8";

    # keyboard layout for your Linux console (i.e. off X11), dvp is for "Programmer Dvorak",
    # if unsure pick "us" or "de" :)
    consoleKeyMap = "dvp";
  };

  # Set your time zone.
  time.timeZone = "Europe/Berlin";

  # Enable the X11 windowing system.
  services.xserver.enable = true;
  services.xserver.layout = "us";
  services.xserver.xkbVariant = "dvp";  # again, pick whichever layout you'd like to have
  services.xserver.xkbOptions = "lv3:ralt_switch";

  # Use i3 window manager
  services.xserver.windowManager.i3.enable = true;

Packages

Of course our Linux installation should have some software installed. Unlike other distributions, where you typically install software every now and then and thus gradually mutate system state, NixOS allows a more declarative approach: you just list all system packages you’d like to have. Once you don’t want to have a package anymore you simply remove it from the list and Nix will arrange that it’s not available any longer.

Long story short, you have this central list of system packages (which you can also modify any time later) you’d like to have installed and NixOS will ensure they are installed:

… here is my current selection:

  environment.systemPackages = with pkgs; [
    bc
    bwm_ng
    coreutils
    curl
    file
    gitAndTools.gitFull
    gnupg
    htop
    libxml2 # xmllint
    libxslt
    lsof
    mosh
    psmisc # pstree, killall et al
    pwgen
    quilt
    tmux
    tree
    unzip
    utillinux
    vim
    w3m
    wget
    which
    zip

    chromium
    firefox
    gimp
    i3 i3lock i3status dmenu
    inkscape
    keepassx2
    libreoffice
    networkmanagerapplet networkmanager_openvpn
    xdg_utils
    xfontsel

    # gtk icons & themes
    gtk gnome.gnomeicontheme hicolor_icon_theme shared_mime_info

    dunst libnotify
    xautolock
    xss-lock

    xfce.exo
    xfce.gtk_xfce_engine
    xfce.gvfs
    xfce.terminal
    xfce.thunar
    xfce.thunar_volman
    xfce.xfce4icontheme
    xfce.xfce4settings
    xfce.xfconf
  ];

… and we need to configure our Xsession startup …

  • I went with xfsettingsd, the settings daemon from Xfce (inspiration from here)
  • NetworkManager applet, sitting in the task tray
  • xautolock to lock the screen (using i3lock) after 1 minute (including a notification 10 seconds before actually locking the screen)
  • xss-lock to lock the screen on suspend (including keyboard hotkey)
  services.xserver.displayManager.sessionCommands = ''
    # Set GTK_PATH so that GTK+ can find the Xfce theme engine.
    export GTK_PATH=${pkgs.xfce.gtk_xfce_engine}/lib/gtk-2.0

    # Set GTK_DATA_PREFIX so that GTK+ can find the Xfce themes.
    export GTK_DATA_PREFIX=${config.system.path}

    # Set GIO_EXTRA_MODULES so that gvfs works.
    export GIO_EXTRA_MODULES=${pkgs.xfce.gvfs}/lib/gio/modules

    # Launch xfce settings daemon.
    ${pkgs.xfce.xfce4settings}/bin/xfsettingsd &

    # Network Manager Applet
    ${pkgs.networkmanagerapplet}/bin/nm-applet &

    # Screen Locking (time-based & on suspend)
    ${pkgs.xautolock}/bin/xautolock -detectsleep -time 1 \
                -locker "${pkgs.i3lock}/bin/i3lock -c 000070" \
                -notify 10 -notifier "${pkgs.libnotify}/bin/notify-send -u critical -t 10000 -- 'Screen will be locked in 10 seconds'" &
    ${pkgs.xss-lock}/bin/xss-lock -- ${pkgs.i3lock}/bin/i3lock -c 000070 &

User Configuration

… and our system needs a user account of course :)

NixOS allows for “mutable users”, i.e. you are allowed to create, modify and delete user accounts at runtime (including changig the user’s password). Contrary you can disable mutable users and controlling user accounts from configuration.nix. As NixOS is about system purity I went with the latter approach, so some more statements for the beloved configuration.nix file:

  users.mutableUsers = false;

  users.extraUsers.stesie = {
    isNormalUser = true;
    home = "/home/stesie";
    description = "Stefan Siegl";
    extraGroups = [ "wheel" "networkmanager" ];
    hashedPassword = "$6$VInXo5W.....$dVaVu.....cmmm09Q26r/";
  };

… and finally we’re ready to go:

$ nixos-install

… if everything went well, just reboot and say hello to your new system. If you’ve mis-typed something fear not, simply fix it and re-run nixos-install.

After you’ve finished installation and rebooted into your new system you can always come back and further modify configuration.nix file, just run nixos-rebuild switch to apply the changes.