UP | HOME

Installing Guix (Full LUKS + LVM)

Table of Contents

Here are some notes about installing Guix SD onto a new system from an existing configuration. We necessarily need to discuss disk preparation and partitioning. Additionally, we discuss from the perspective of existing configurations. I planned to write this back in 2022, after initially switching from NixOS to Guix. I hesitated at the time since such a post would not have any additional value to people otherwise installing Guix. However, going through the process again, I feel there are some comments to capture that may be beneficial to others venturing down this path. I will save the discussion between NixOS and Guix for another time. For now, let's settle on "scheme is more better."

After having a corrupted GRUB kill my previous Guix System, and a false sense of urgency, I have been running, begrudgingly, Fedora Workstation for the last 10 months. However, with summer here, I am eager to return to using Guix as my primary OS.

Fedora has been fine. It seems to work for the most part. But there just seems to be this constant encroaching entropy causing the system to become less and less stable. Using Guix as a package manager seemed to work fine in the beginning, but over time, it has started showing some degradation that is simply unworkable. If I must scorch earth reinstall, I might as well install using my preferred OS and existing system configuration. However, in my attempts to switch back to Guix, I have uncovered some sharp edges, because of course.

Disk preparation steps

First step to any fully encrypted setup is to throw a bunch of random bits onto the disk, the zeroth step is to check it with badblocks. Next, randomize the data on the disk to protect against easy statistical analysis. Afterwards, we partition the drive into two primary physical partitions. Then, we create the LUKS container and the LVM volumes. Finally, we format all the different volumes and mount them in preparation for system init.

Badblocks

Since I still needed a working machine in the meantime, I purchased a second drive. This way, if the installation fails for any reason— it did— I can switch back to a working "machine". But to be sure the new drive is in good working order, I tested it with badblocks:

badblocks -wsv -t 0xEF ${device}
badblocks -wsv -t 0XFE ${device}

This does two read-write, read DESTRUCTIVE, passes on the new device, checking for any errors.

Now for some, this is "enough" random scrubbing of the disk in preparation for an encrypted volume. However, I disagree. If the entire disk is a giant pattern of "0xFE" values, your actual data shines like a bright hot star for statistical analysis of the data.

Random "zeroing"

The fastest (not to be confused with "best") way I have seen to randomly write garbage to a drive is to use a plain dm-crypt device with a random passphrase and write zeros to the resulting container.

cryptsetup open --type plain --key-file=/dev/urandom ${device} wipe-me
dd if=/dev/zero of=/dev/mapper/wipe-me bs=4096 status=progress
cryptsetup close

Also, critical for the throughput of the above command, setting the "blocksize" via bs=4096 dramatically improves the write speed.

Partitioning

Since the system uses a single device and we have a "fully encrypted" setup, we only need two partitions: first for the EFI System Partition (ESP) and second for the LUKS container.

I created this using the interactive prompts within gptdisk. Creating the first partition to be 128M at the default starting sector (2048), and the second taking the rest of the sectors. I denoted their partition types using the codes EF00, and 8308, respectively. Finally, I renamed them to something more meaningful to me: "boot" and "guix", respectively.

Creating LUKS container

With a fully encrypted system, including /boot, there are some important limitations to keep in mind. As of this writing, GRUB does not support LUKS2 containers nor does it support the Argon2 key derivation function. Both of which are the default for recent versions of cryptsetup.

This is tricky because GRUB does claims to support LUKS2, however, I was unable to get this to work.

cryptsetup --type luks1 \
           --cipher aes-xts-plain64 \
           --key-size 512 \
           --hash sha512 \
           --iter-time 7680 \
           --use-random \
           --pbkdf=pbkdf2 \
           --verify-passphrase \
           luksFormat \
           ${device_part}

There are a few important notes here: first, the container type, as mentioned, should be LUKS1 type container; second, the PBKDF needs to be PBKDF2, otherwise GRUB does not know how to unlock it; third, make sure you replace ${device_part} with the appropriate partition. While the initial unlocking is quite slow because GRUB does not have full access to the machine, lowering the iteration count is not advisable for a "secure" machine.

Ensure it all works by opening the container:

cryptsetup open ${device_part} cryptroot

A new device should be available under the /dev/mapper tree.

"Correctly" naming the cryptroot

I do not think it may be important, but it is perhaps worth mentioning: the name of the device should match the name used in your configuration. Use blkid to get the UUID if needed.

cryptroot open ${device_part} \
          luks-$(blkid | grep ${device_part} | awk '{print $2}' | sed 's/UUID=//' | sed 's/\"//g')

Creating the LVM containers

Now, we can create the LVM containers for our partitions.

pvcreate /dev/mapper/cryptroot
vgcreate vg0 /dev/mapper/cryptroot
lvcreate -L 1G vg0 -n root
lvcreate -L 100G vg0 -n guix
lvcreate -L 100G vg0 -n nix
lvcreate -L 32G vg0 -n var
lvcreate -L 32G vg0 -n opt
lvcreate -L 32G vg0 -n tmp
lvcreate -L 64G vg0 -n swap
lvcreate -L 1T vg0 -n home

Since everything lives under /gnu/, the root partition really doesn't need to be even this large (I'm currently only using 18M on the root partition). However, Guix currently warns about this during the initialization phase. We can safely ignore this warning since we know the store writes go to a different partition. As I recall, this was not a warning before, requiring the root partition to be quite large for the initial installation.

Formatting

Now that we have all the partitions and logical volumes created, we need to create actual "filesystems" for each. I have been using a combination of ext4 and xfs for a while, and it seems to work well. While you may be able to get away creating the ESP as an ext2 partition, it is probably easier and more widely supported to format it using Fat32.

Since version 5.15, xfs partitions default to including bigtime, avoiding the 2038 problem. Check back in 2486…

mkfs.vfat -F 32 -n boot ${ESP}
mkfs.ext4 -L root /dev/mapper/vg0-root
mkfs.ext4 -L var /dev/mapper/vg0-var
mkfs.ext4 -L opt /dev/mapper/vg0-opt
mkfs.ext4 -L tmp /dev/mapper/vg0-tmp
mkfs.xfs -L guix /dev/mapper/vg0-guix
mkfs.xfs -L nix /dev/mapper/vg0-nix
mkfs.xfs -L home /dev/mapper/vg0-home

Do not forget to format the swap volume:

mkswap /dev/mapper/vg0-swap

Mounting

The penultimate step before turning over to the initialization step is to mount each of the partitions:

mount /dev/mapper/vg0-root /mnt
mkdir -p /mnt/{boot/efi,gnu,nix,opt,var,tmp,home}
mount ${ESP} /mnt/boot/efi/
mount /dev/mapper/vg0-guix /mnt/gnu
mount /dev/mapper/vg0-nix /mnt/nix
mount /dev/mapper/vg0-var /mnt/var
mount /dev/mapper/vg0-opt /mnt/opt
mount /dev/mapper/vg0-tmp /mnt/tmp
mount /dev/mapper/vg0-home /mnt/home

You can optionally enable the swap partition for the current live installation:

swapon /dev/mapper/vg0-swap

Nix Store

If you choose to run nix as an addition package manager, make sure to create the "store" directory, lest your first boot waits forever for such a directory to appear…

mkdir -p /mnt/nix/store

Cloning and modifying UUIDs

Each configuration is different, but before initializing the system, I need to change the UUIDs of the initial LUKS volumes within my system configuration. Some people have clever ways of scripting this out. I accept this tiny amount of pain now which grants me the peace of mind I do not need to worry about it some how changing and becoming unbootable later. I simply use blkid to get the UUID of the ${device_part} and update the system config accordingly:

modified   systems/axo.scm
@@ -59,8 +59,8 @@
 
     (mapped-devices
      (list (mapped-device
-            (source (uuid "f1e8d842-1c63-4311-803d-938f31d48d49"))
-            (target "luks-f1e8d842-1c63-4311-803d-938f31d48d49")
+            (source (uuid "d1bcf4fd-8fe8-41b6-88dc-c83851b1f071"))
+            (target "luks-d1bcf4fd-8fe8-41b6-88dc-c83851b1f071")
             (type luks-device-mapping))
            (mapped-device
             (source "vg0")
@@ -117,7 +118,7 @@
                     (needed-for-boot? #t)
                     (dependencies mapped-devices)))
             (efi (file-system
-                   (device (uuid "5A5D-20AF" 'fat))
+                   (device (uuid "0601-7942" 'fat))
                    (mount-point "/boot/efi")
                    (type "vfat")
                    (dependencies mapped-devices))))

System initialization

Before starting the initialization process, start the copy-on-write service to ensure any changes made to the running Guix system (e.g., live installation media) are copied to the new system's store.

herd start cow-store /mnt

Now, we can move to initializing the system.

It's critical for this step that your system has network connectivity. Test by pinging CloudFlare: ping 1.1.1.1.

From the root of the https://git.sr.ht/~kennyballou/dotfiles.git repository, run the following command:

guix time-machine -C ./config/guix/channels.scm -- system init \
     -L ./ \
     systems/${machine}.scm \
     /mnt

Assuming everything works, we should be able to reboot into the new system.

After initialization steps

After successfully booting into the freshly initialized system, we need to set the passwords of the root and default user.

Switch over to a different tty and "login" as root, and set the passwords using passwd.

Parting thoughts

Because installing my typical configuration is a little heavy, I am considering creating a smaller configuration just for the initial installation to quickly get into the new system. From there, re-configuring to the final system is a lot easier (and safer) than the installation media.