A Dev's Journey - Part 01 - get NixOS; prepare file system

Wednesday, August 9, 2023

Preface

First I need to bootstrap my setup. I use my existing ubuntu installation to download and write the NixOS installation image to a thumb drive. This will be my actual entry point to all further preparations and installation.

Installation Medium

NixOS ISO

Over in the download section at nixos.org two options are presented. A Graphical ISO image and a Minimal ISO image.

The graphical images come with either Gnome or Plasma desktop and will provide a graphical installation wizard. This live image allows to try out NixOS without installing it. If connected to the internet one can still select another desktop environment during the installation process. Unfortunately, not hyprland. But is still a good starting point, as it generates a decent default configuration to build upon.

But I want to learn how to prepare and set up everything from the ground up. So I go for the minimal image, and therefore a console based installation experience.

Note this URL is valid as of the day I’m writing this. I suggest you check out the most recent and recommended link on nixos.org.

> curl --create-dirs --output-dir /tmp/nixos_image -O https://releases.nixos.org/nixos/23.05/nixos-23.05.2385.48e82fe1b1c/nixos-minimal-23.05.2385.48e82fe1b1c-x86_64-linux.iso
> curl --create-dirs --output-dir /tmp/nixos_image -O https://releases.nixos.org/nixos/23.05/nixos-23.05.2385.48e82fe1b1c/nixos-minimal-23.05.2385.48e82fe1b1c-x86_64-linux.iso.sha25
> (cd /tmp/nixos_image && sha256sum --check nixos-minimal-23.05.2385.48e82fe1b1c-x86_64-linux.iso.sha256)

The final sha256sum check reports that the downloaded image is OK.

  • Acquire NixOS ISO image. Check.

Thumb drive

Time to plug and locate the thumb drive.

Here are some tools that help to identify a plugged thumb drive:

  • lsblk -e7
  • fdisk -l

The easiest way to identify a removable drive, is to call one of the commands above, before plugging it in. Then call the command again with the device plugged in and watch for changes.

I use fdisk and check the Disk model field for the name of my thumb drive. My build in drive is from Samsung and ~480GB in size. My thumb drive in an Intenso Premium Line and ~ 32GB in size.

> sudo fdisk -l
Disk /dev/sda: 476,94 GiB, 512110190592 bytes, 1000215216 sectors
Disk model: SAMSUNG MZ7LN512
Disk /dev/sdb: 30,27 GiB, 32497729536 bytes, 63472128 sectors
Disk model: Premium Line

One quick look at the output and one can tell that my thumb drive has been assigned to block device /dev/sdb.

Last thing left is to write the ISO to the drive. For this I will use the good old dd command like this:

> sudo dd if=/tmp/nixos_image/nixos-minimal-23.05.2385.48e82fe1b1c-x86_64-linux.iso \
          of=/dev/sdb bs=32M status=progress conv=sync

The installation medium is complete and can be unplugged. In theory, I should be able to proceed with just the thumb drive, and without the help of any other devices. But I leave the lid of this laptop open, just in case I need to search the internet (which I probably will). Time to switch to the target device (in my case: another laptop).

File System and Encryption

Recap

Here is a quick recap of my plan for the hard drive:

+-----------+----------+----------+
|           |          |          |
|  [/boot]  |  [/   ]  |          |
|  vfat     |  ext4    |  swap    |
|  1GB      |  ~967GB  |  32GB    |
|           |          |          |
|           |---------------------|
|           |      L U K S 2      |
+-----------+----------+----------+

What is going on here? All data but /boot are encrypted by LUKS2. Even the swap, as it may contain open and loaded files while hibernating.

There is one misconception about my plan. As shown above, I’ve thought, that one LUKS2 partition drive may contain several sub partitions. Of course this is not the case and would require LVM or btrfs to do so. But still, I stick to my idea to use three partitions of whom / and swap are secured with LUKS2. So I fixed the outline to look like this:

+-----------+----------+----------+
|           |          |          |
|  [/boot]  |  [/   ]  |          |
|  vfat     |  ext4    |  swap    |
|  1GB      |  ~967GB  |  32GB    |
|           |          |          |
|           |---------------------|
|           |  LUKS2   |  LUSK2   |
+-----------+----------+----------+

Note: My current hard disk is smaller than the final one. In this example I’m using a 256 GB drive.

Boot from USB

Before powering my laptop, I plug the thumb drive with the NixOS image into a free USB slot. Every laptop or PC has some kind of mechanic to interrupt the normal star procedure, and allow to boot from a different device. In my case it is F12 which brings up a boot selection menu from where I can choose the prepared thump drive.

Next, the bootloader menu shows up with the default NixOS installer preselected. I confirm the selection and boot into the live version of NixOS.

First thing on my agenda, after hitting the command line prompt of the terminal, is to select my go to keyboard layout: bone.

> sudo loadkeys de bone

I was surprised how easy it was to get bone up and running, with all layer behaving as expected.

Partition Table

The current go to standard for partition tables is the GUID Partition Table (GPT). It will contain the three partitions I need. A rather small EFI partition for /boot, a swap partition matching the RAM size (for hibernation, or suspend to RAM), and a large one serving as the system root /.

For this purpose I will use fdisk. As before, I determined the path to the desired hard drive /dev/sda.

sudo fdisk /dev/sda

Most of the fancy values, like start and end sectors, are calculated by fdisk. To keep it simple I will focus on my custom entries here. For all skipped values, assume the suggestion accepted.

HEADS UP:

  • G -> 1024 * 1024 * 1024
  • GB -> 1000 * 1000 * 1000
  • create a new empty GPT -> g
  • create first new partition -> n
    • last sector = first sector + 1 GiB -> +1G
    • change the partition type to EFI System -> t -> 1
  • create second new partition -> n
    • last sector = remaining space -32GiB -> -32G
  • create third new partition -> n
    • take all the remaining 32GiB
    • change the partition type to Linux swap -> t -> 3 -> swap
  • write partition table to disk -> w

I find it really convenient , that fdisk allows relative sector position statements. For example +1G will add 1 GiB to the start sector position, while -32G will subtract 32 GiB from the last sector of the remaining space. This saves the hassle of calculating the positions on my own. But just for the sake of completeness:

# Sector size is 512 Byte
sector_size = 512
# 1 GiB is 1073741824 Bytes
gib = 1024*1024*1024
# 1 GiB is 2097152 sectors in size
sectors_in_gib = int(gib / sectors_size)
# Start sector is at 2048
start_sector = 2048
# The start sector already belongs into the range as it is our first sector.
# Because we don't start at 2049 but at 2048 we can subtract 1.
end_sector = int(start_sector + sectors_in_gib) - 1
2099199

Writing +1G for the end sector of the first partition would be the same as entering 2099199.

The following commands may also come in handy during the process:

  • print the current state of the table draft -> p
  • quit without applying any changes -> q
  • show help/manual -> m

The table looks something like this now:

Device        Start       End   Sectors   Size Type
/dev/sda1      2048   2099199   2097152     1G EFI System
/dev/sda2   2099200 421279835 419188736 199.9G Linux filesystem
/dev/sda3 421278936 488396799  67108864    32G Linux swap

Formatting and Encryption

Now that the partition table is set up, let’s commence with the establishment of the file system.

boot / sda1

A short glimpse into the UEFI specs and one can tell, that we should go with FAT32 for the /boot partition. I like to keep things simple, so let’s finish this one quickly.

sudo mkfs.fat -F32 -n "EFI BOOT" /dev/sda1

root / sda2

Finally we’ve come to the exiting part of this chapter. The order in which encryption is applied and file system is set up is as follows. First the partition is encrypted using LUKS2 with the help of the cryptsetup tool. Then the LUKS2 layer is unlocked and the mkfs.ext4 command is used to emplace the file system in there.

During cryptsetup luksFormat, the user is prompted to confirm the irrevocable overwrite. It should be OK as I do not expect to have any accessible data at this point anyway. One important parameter I want to mention. -y will verify the passphrase by asking for it twice. IMHO this should be the default behavior, but who am I to judge. I assume the remaining parameters are self explanatory.

sudo cryptsetup luksFormat -y --type luks2 /dev/sda2

LUKS2 encrypts on block device level. Which means, in order to facilitate file writing a file system is required. Therefore access to the underlying block device is necessary. cryptsetup luksOpen allows access to a LUKS2 secured block device by a given label. This will map the block device to /dev/mapper/root.

sudo cryptsetup luksOpen /dev/sda2 root

Afterwards the file system can be created as usual, barring the extraordinary path.

sudo mkfs.ext4 -L "root" /dev/mapper/root

swap / sda3

Let’s walk through the future boot process and assume wakeup from hibernation, or suspend-to-RAM. The bootloader at /boot is not a problem, as this partition is not encrypted. Now we need to unlock the root file system /, so we type in a password. Then, we still need to unlock the swap partition. Remembering and entering a second password is more than inconvenient.

Fortunately we just unlocked an encrypted file system, which in turn may contain a secret to unlock further partitions. LUKS2 provides a variety of different key types. One of which is a keyfile made of arbitrary binary data.

Let’s mount the previously created file system and place a keyfile there.

sudo mount /dev/mapper/root /mnt
sudo dd bs=2048 count=1 if=/dev/urandom of=/mnt/keyfile iflag=fullblock

The following steps are similar to those in root / sda2, except the keyfile is used instead of a passphrase.

sudo cryptsetup luksFormat --type luks2 --key-file /mnt/keyfile /dev/sda3
sudo cryptsetup luksOpen --keyfile /mnt/keyfile /dev/sda3 swap
sudo mkswap -L "Linux swap" /dev/mapper/swap

The bootloader can be configured to automatically unlock the swap partition using the keyfile, once the root file system gets unlocked.

result

In the end, I got what I was planing for. Encrypted root file system and swap. It already took me a considerable amount of time and effort to write this chapter. After a short peek into the YubiKey topic, I decided to defer it to a future chapter.

This is it for now. Here is a breakdown of my hard drive:

lsblk -o name,size,type,fstype,label
NAME        SIZE TYPE  FSTYPE      LABEL
sda       232.9G disk
+- sda1       1G part  vfat        EFI BOOT
+- sda2   199.9G part  crypto_LUKS
|+-- root 199.9G crypt ext4        root
+- sda3      32G part  crypto_LUKS
+-- swap     32G crypt swap        Linux swap

For further investigation of the LUKS2 devices the cryptsetup luksDump command will come in handy.

See you next time!