Installing NixOS impermanence with LUKS

go back

What is impermanence?

This. Basically because Nix is immutable and declarative it can rebuild your system from scratch as long as it has the /nix directory. So you can wipe your root filesystem and Nix will automatically regenerate everything. It's useful to prevent bloat from accumulating on your computer since it's all wiped away when you restart.

Setup details

I'm using a LUKS encrypted partition with LVM. I have two LVM logical volumes: An ext4 persistent filesystem for data that should not be deleted, and an ext4 root filesystem (mounted at /) that gets wiped on restart.

Partition disks

Let's do it, and by it,.. haha well. lets justr say partitioning disks. We want a boot partition and the rest of the space to be a LUKS partition. I've got this installed on a VM using Oracle VBox which only supports legacy EFI so that's what I'll be using. The following is approximately the steps with fdisk:

    fdisk /dev/sdX
    > o (MBR for legacy EFI)
    > n (Create a 500MB boot partition named /dev/sdX1)

    > n (Create the root partition named /dev/sdX2, just keep pressing enter)

    > w (Write the changes)
            

mkfs

Now we need to put a FAT filesystem on the boot partition

    mkfs.fat -F32 -L NIXOS_BOOT /dev/sdX1
            

and add LUKS and LVM on the rest of the drive.
(it's good security to randomize the data on disk before encrypting, the Arch wiki has a useful tutorial on that here)

    # Setup luks encrypt at /dev/mapper/cryptroot
    cryptsetup --label=NIXOS_LUKS luksFormat /dev/sdX2
    cryptsetup luksOpen /dev/sdX2 cryptroot

    # Setup an LVM volume group named root_vg
    pvcreate /dev/mapper/cryptroot
    vgcreate root_vg /dev/mapper/cryptroot
            

Then we'll create two ext4 filesystems, one for the ephemeral root that is wiped on reboot.

    lvcreate -L <size> -n root root_vg
    mkfs.ext4 -L NIXOS_ROOT /dev/mapper/root_vg-root
            

And one for the persistent storage that shouldn't be deleted.

    lvcreate -l +100%FREE -n persist root_vg
    mkfs.ext4 -L NIXOS_PERSIST /dev/mapper/root_vg-persist
            

Note how we are giving them filesystem labels. This will be useful later when managing our Nix configurations.

Mounting the filesystems

Next we need to mount the filesystems.

    mkdir -p /mnt/{boot,persist}

    mount /dev/disk/by-label/NIXOS_ROOT /mnt
    mount /dev/disk/by-label/NIXOS_BOOT /mnt/boot
    mount /dev/disk/by-label/NIXOS_PERSIST /mnt/persist
            

But remember, Nix still needs the /nix directory to regenerate properly, so we have to make it persistent. We can use bind mounts for this.

    mkdir -p /mnt/{persist,}/{nix,etc/nixos}
    
    mount --bind /mnt/persist/nix /mnt/nix
    mount --bind /mnt/persist/etc/nixos /mnt/etc/nixos
            

We mount /persist/nix to /nix so anything installed into /nix will be persistent. For convenience we also mounted /etc/nixos. Although not strictly neccessary it is nice that our configurations are persistent too.

Configurations

Now we can generate our configuration.nix and hardware-configuration.nix.

    nixos-generate-config --root /mnt
            

There are still a few essential changes to be made to the configurations before we can run nixos-install. Most can be found here but we'll also need to make a few more edits to support LUKS and to wipe our ephemeral storage on reboot.

First lets add the LUKS partition as a luks device. Make the following changes in hardware-configuration.nix:

    boot.initrd.kernelModules = [ ... "cryptd" ]; # <-- add cryptd as a kernel module
    boot.initrd.luks.devices.cryptroot.device = "/dev/disk/by-label/NIXOS_LUKS"; # <-- add luks device
            

Then we need to mark the /persist directory as neededForBoot or else we won't be able to boot.

    fileSystems."/persist" =
        { device = "/dev/disk/by-label/NIXOS_PERSIST";
          fsType = "ext4";
          neededForBoot = true; # <-- add neededForBoot
        };
            

And finally we need to wipe the root filesystem on restart by adding the following configuration:

    boot.initrd.systemd = {
        enable = true;
        extraBin."mkfs.ext4" = "${pkgs.e2fsprogs}/bin/mkfs.ext4";
        services.wipe-file-systems = {
            description = "Wipe the root filesystem";
            unitConfig.DefaultDependencies = false;
            serviceConfig.Type = "oneshot";
            requiredBy = [ "initrd.target" ];
            before = [ "local-fs-pre.target" ];
            after = [ "initrd-root-device.target" "systemd-hibernate-resume.service" ];
            script = ''
                mkfs.ext4 -F -L NIXOS_ROOT /dev/disk/by-label/NIXOS_ROOT
            '';
        };
    };
            

This adds a systemd service that wipes the filesystem by reformating the ext4 filesystem on root. mkfs.ext4 is not present in the initial ramdisk so it has to be added using extraBin.

Install

Run nixos install --root /mnt.

and that's my setup! I've been thinking of using disko which is a tool to handle the partitioning automatically. Although, I'm not sure if it's worth the hassle.