Installing NixOs

Introduction

I've recently started diving into Nix and NixOS. Nix is a package manager for Linux/Unix systems, a quick description from its own site:

Nix's purely functional approach ensures that installing or upgrading one package cannot break other packages. This is because it won't overwrite dependencies with newer versions that might cause breakage elsewhere. It allows you to roll back to previous versions, and ensures that no package is in an inconsistent state during an upgrade.

NixOs is an operating system which takes a declarative approach to configuration and package management and uses Nix as its package manager. Following is an example of creating a user on a NixOS system:

...
users.extraUsers.alice =
  { isNormalUser = true;
    home = "/home/alice";
    extraGroups = [ "wheel" ];
  };
security.sudo.enable = true;
...

An example of a fully configured NixOS system can be found at https://github.com/wayofthepie/sky, this is the configuration I'm currently using for my desktop.

In this post I'm going to run through how I installed NixOs, with the aim of helping anyone new to NixOs through the installation process. Throughout the post I will be assuming the following:

These are not requirements, but will make the post easier to follow.

Partition

Let's start! First boot into NixOs, whether from USB or the CD installer, and find the disk you wish to install onto. I will use /dev/sda in this post, you can replace this with whatever disk you are going to use.

The system will boot with UEFI so gdisk should be used to create the partitions. This will create a GPT (GUID Partition Table) formatted disk.

With gdisk create the following partitions:

PartitionSizeTypeCodeFilesystem
/dev/sda12 MiBBios Boot Partitionef02None
/dev/sda21024 MiBEFIef00vfat
/dev/sda3119 GiBLinux LVM8e00xfs

If you have never used gdisk before, fear not! Just run gdisk /dev/sda{.bash} and answer the questions as outlined in the next section. If you know what you are doing, you can skip the next section.

Creating Partitions With gdisk

$ gdisk /dev/sda

GPT fdisk (gdisk) version 1.0.1

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help): n
Partition number (1-128, default 1): 1
First sector (34-251658206, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-251658206, default = 251658206) or {+-}size{KMGTP}: +2M
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef02
Changed type of partition to 'BIOS boot partition'

Command (? for help): n
Partition number (2-128, default 2):
First sector (34-251658206, default = 6144) or {+-}size{KMGTP}:
Last sector (6144-251658206, default = 251658206) or {+-}size{KMGTP}: +1G
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef00
Changed type of partition to 'EFI System'

Command (? for help): n
Partition number (3-128, default 3):
First sector (34-251658206, default = 2103296) or {+-}size{KMGTP}:
Last sector (2103296-251658206, default = 251658206) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 8e00
Changed type of partition to 'Linux LVM'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
The operation has completed successfully.

In the above, we begin creating our first new partition by using the command n when asked the question Command (? for help):{.bash}.

The first question we are asked is what number we would like to give, it's our first partition so 1, this will create a partition called /dev/sda1 on /dev/sda.

Next, we are asked what sector to start on and what sector to end on. The starting sector can be left empty - this means start at next unused sector, which in this case is the start of the disk. As for which sector to end on, here we answer +2M which will means move 2 Mebibytes 1 (MiB) forward from the starting sector, giving us a partition size of 2 Mebibytes.

Finally, we are asked if we want to change the type of the partition. Our first partition should have the type ef02, more on why later.

That's the first partition created, it should be clear now how the next two are created.

Notes On The Partitions

Done? Good, to get a look at the disk layout run gdisk -l /dev/sda{.bash}, this should print out the following:

$ gdisk -l /dev/sda

GPT fdisk (gdisk) version 1.0.1

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.
Disk /dev/sda: 251658240 sectors, 120.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 031652EE-C219-4EFB-84AB-9E310B14357E
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 251658206
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048            6143   2.0 MiB     EF02  BIOS boot partition
   2            6144         2103295   1024.0 MiB  EF00  EFI System
   3         2103296       251658206   119.0 GiB   8E00  Linux LVM

Grub Compatibility: /dev/sda1

This is used by grub 2 at boot-time when booting off a GPT disk.

EFI Partition: /dev/sda2

UEFI will load files stored in this partition when booting.

Main Data Partition: /dev/sda3

This is used for creating the logical volumes swap (4GB), / (20GB), /home (20GB) and /opt (20GB) in this post, it's size should reflect the sum of the sizes of all logical volumes you wish to create. Note that any free space left of the disk can be used later, and those logical volumes can be increased (or decreased) in size.

Linux Unified Key Setup (LUKS)

Now that the partitions are setup, it's time to encrypt the main partition, /dev/sda3. This is optional and there are a few ways to do it, this post uses LUKS.

The cryptsetup tool is used to create LUKS volumes. To search nix packages the command nix-env -qaP packageName{.bash} 3 can be used:

$ nix-env -qaP cryptsetup

nixos.cryptsetup  cryptsetup-1.7.0

Now that we know cryptsetup is in the package list, we can install it with nix-env{.bash}:

$ nix-env -i cryptsetup

installing 'cryptsetup-1.7.0'
building path(s) '/nix/store/86mqcs95fb3gkkb74481fbf90zh5w98l-user-environment'
created 44 symlinks in user environment

Encrypting /dev/sda3

I won't go into detail about LUKS or cryptsetup here, we will also just use the defaults for both. Simply run the following cryptsetup command and enter a passphrase:

$ cryptsetup -y -v luksFormat /dev/sda3

WARNING!
========
This will overwrite data on /dev/sda3 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase:
Verify passphrase:
Command successful.

/dev/sda3 is now encrypted! 4 The options passed to cryptsetup have the following meaning:

Great, we now have an encrypted disk, but how is it used? Let's jump back to cryptsetup, it has a command cryptsetup luksOpen LUKS_DEVICE NAME{.bash} which, when it verifies the passphrase (in our case), will open the LUKS device and create a mapping to it (on the path /dev/mapper/NAME)from the given name.

$ cryptsetup luksOpen /dev/sda3 enc-data

Enter passphrase for /dev/sda3:

$ ls -la /dev/mapper/
total 0
drwxr-xr-x  2 root root      80 Jul  9 18:43 .
drwxr-xr-x 18 root root    3320 Jul  9 18:43 ..
lrwxrwxrwx  1 root root       7 Jul  9 18:43 enc-data -> ../dm-0

Sweet! We now have an encrypted partition and know how to access it. All we have left to do is setup logical volumes on the partition.

LVM Setup

Make sure our data partition - /dev/sda3 - is open and mapped to the path /dev/mapper/enc-data with luksOpen, as mentioned above.

There are three steps to setting up logical volumes, creating a physical volume (PV), a volume group (VG) and a logical volume (LV). If you are unfamiliar with Logical Volume Management (LVM) the Arch Linux Wiki entry is a good resource.

As a quick description, a physical disk can be divided into one or more physical volumes, a volume group is made up of one or mode physical volumes and logical volumes are created within volume groups.

Physical Volume

pvcreate{.bash} is the command used to create physical volumes. Let's use this to create a physical volume on /dev/mapper/enc-data:

$ pvcreate /dev/mapper/enc-data

Physical volume "/dev/mapper/enc-data" successfully created

pvdisplay{.bash} is used to display information on physical volumes on the system.

$ pvdisplay

"/dev/mapper/enc-data" is a new physical volume of "119.00 GiB"
--- NEW Physical volume ---
PV Name               /dev/mapper/enc-data
VG Name
PV Size               119.00 GiB
Allocatable           NO
PE Size               0
Total PE              0
Free PE               0
Allocated PE          0
PV UUID               H3bhmD-KR9n-XaHb-03L6-TpN2-pYB5-DtUG7x

Volume Group

vgcreate{.bash} is used to create a volume group. Lets create a volume group called vg.

$ vgcreate vg /dev/mapper/enc-data

Volume group "vg" successfully created

vgdisplay{.bash} displays info on the systems volume groups.

$ vgdisplay

--- Volume group ---
VG Name               vg
System ID
Format                lvm2
Metadata Areas        1
Metadata Sequence No  1
VG Access             read/write
VG Status             resizable
MAX LV                0
Cur LV                0
Open LV               0
Max PV                0
Cur PV                1
Act PV                1
VG Size               118.99 GiB
PE Size               4.00 MiB
Total PE              30462
Alloc PE / Size       0 / 0
Free  PE / Size       30462 / 118.99 GiB
VG UUID               O9EIaS-kX9Y-EXjo-I3zU-mPXY-9SVB-br2fEy

Logical Volume

lvcreate{.bash} is used to create a logical volume. In this case, we want to create four logical volumes from our volume group vg.

lvcreate -n swap --size 8G vg
lvcreate -n root --size 20G vg
lvcreate -n home --size 20G vg
lvcreate -n opt --size 20G vg

Running the above four lvcreate commands will give us our logical volumes swap, root, home and opt. Use lvdisplay{.bash} to get more info on them:

$ lvdisplay

--- Logical volume ---
LV Path                /dev/vg/swap
LV Name                swap
VG Name                vg
LV UUID                F5DbgU-ymix-24Bh-JMTy-q2kL-ayvN-UpNUFB
LV Write Access        read/write
LV Creation host, time nixos, 2016-07-09 19:11:46 +0000
LV Status              available
# open                 0
LV Size                8.00 GiB
Current LE             2048
Segments               1
Allocation             inherit
Read ahead sectors     auto
- currently set to     256
Block device           254:1

--- Logical volume ---
LV Path                /dev/vg/root
LV Name                root
VG Name                vg
LV UUID                FNLEbI-z05h-J7br-OMst-Ui0t-yq3r-BMNVjA
LV Write Access        read/write
LV Creation host, time nixos, 2016-07-09 19:11:46 +0000
LV Status              available
# open                 0
LV Size                20.00 GiB
Current LE             5120
Segments               1
Allocation             inherit
Read ahead sectors     auto
- currently set to     256
Block device           254:2

--- Logical volume ---
LV Path                /dev/vg/home
LV Name                home
VG Name                vg
LV UUID                zBLqKD-S6n7-BcTh-1oAF-TLvV-26Oc-dQ3wQR
LV Write Access        read/write
LV Creation host, time nixos, 2016-07-09 19:11:46 +0000
LV Status              available
# open                 0
LV Size                20.00 GiB
Current LE             5120
Segments               1
Allocation             inherit
Read ahead sectors     auto
- currently set to     256
Block device           254:3

--- Logical volume ---
LV Path                /dev/vg/opt
LV Name                opt
VG Name                vg
LV UUID                hr3GRw-d0P4-hDIw-HRIo-hqKK-Lql9-gD58a7
LV Write Access        read/write
LV Creation host, time nixos, 2016-07-09 19:11:47 +0000
LV Status              available
# open                 0
LV Size                20.00 GiB
Current LE             5120
Segments               1
Allocation             inherit
Read ahead sectors     auto
- currently set to     256
Block device           254:4

Ok! That's our disk setup ~90% complete.

#Final Disk Setup All we have left to do now is format the boot partition and our logical volumes and we ca start preparing NixOs for install. The formatting can be done with a short script:

mkfs.vfat -n BOOT /dev/sda2
mkswap -L swap /dev/vg/swap
for lv in root home opt; do
  mkfs.xfs -L $lv /dev/vg/$lv
done

This formats /dev/sda2 with vfat and labels it BOOT, sets up a swap area on our LV /dev/vg/swap and finally formats our LV's /dev/vg/root, /dev/vg/home and /dev/vg/opt with xfs.

Awesome! Disk setup is now 100% complete!

Install NixOS

Now that our disk is ready we can prepare to install NixOs, this is pretty short, and sweet.

First, we need to create the directories /mnt/boot, /mnt/home and /mnt/opt. Then, we need to mount our LV's - that we created in the previous section - and our boot partition:

mkdir /mnt/{boot,home,opt}
mount /dev/vg/root /mnt
mount /dev/vg/home /mnt/home
mount /dev/vg/opt /mnt/opt
mount /dev/sda2 /mnt/boot

Before we install NixOs, we have to run nixos-generate-config --root /mnt/{.bash} 6:

$ nixos-generate-config --root /mnt/

writing /mnt/etc/nixos/hardware-configuration.nix...
writing /mnt/etc/nixos/configuration.nix...

We need to add some extra information to /mnt/etc/nixos/configuration.nix to tell NixOs what our boot device is, and the fact that it is also a LUKS device:

boot.loader.grub.device = "/dev/sda";

boot.initrd.luks.devices = [{
  name = "root";
  device = "/dev/sda3";
  preLVM = true;
}];

This can be added anywhere in the file, however there should already be a section for boot.loader.grub.device which is commented out. If so, uncomment it and add the config for LUKS (boot.initrd.luks.devices) right after it.

Now we can install! Just run:

nixos-install

This can take some time, you should see information on what packages are being downloaded and installed while the installation is running. You will be prompted to enter the root password during the installationi, once you do that the installation has completed and you can boot up your new NixOs install! 7

Conclusion

The aim of this post was just to get NixOs installed, using the steps I used on my own system. I plan on doing some more posts on Nix and NixOs once I get some time to dive deeper into them.

1

1 MiB = 220 bytes = 1024 kibibytes = 1048576 bytes. 2: See Arch Linux - Grub GPT 3: Short for nix-env --query --available --attr-path packageName{.bash}. This searches all available packages for the packageName, and will also print out its attribute path (see Install By Attribute Path). 4: Running cryptsetup -y -v luksFormat /dev/sda3{.bash} will use the default LUKS settings, which can be found by running cryptsetup --help{.bash}. In my case (v1.7.0 of cryptsetup) they are LUKS1: aes-xts-plain64, Key: 256 bits, LUKS header hashing: sha256, RNG: /dev/urandom. 5: See Arch Linux - Luks KeyMangement for more information. 6: For more info on nixos-generate-config{.bash} see Section 10 of the Nix Install Manual. 7: When the system boots you will be asked for a password before the login prompt, this is the password you used to create the LUKS volume.