How to Install NixOS with Full Disk Encryption + Secure Boot + Unlock LUKS via TPM2

Bonus feature: Intro to Home Manager

By Lani Akita

|

Updated:

Background: a wireframe of a scenic mountain view, with water pooling into a pond at the base. Foreground: Directly in the center, sits the NixOS logo, a plus sign, a lock icon, a plus sign, and a motherboard icon, all imposed upon a translucent, teal rectangle.

Background: a wireframe of a scenic mountain view, with water pooling into a pond at the base. Foreground: Directly in the center, sits the NixOS logo, a plus sign, a lock icon, a plus sign, and a motherboard icon, all imposed upon a translucent, teal rectangle.

Yes to love, yes to life, yes to staying in more! - Liz Lemon

Happy New Year! Yep, it's that time of year again. A time of new beginnings, cleaned slates, and Winter weather (in the Northern Hemisphere). So, bundle up near the warmth of your PCs exhaust fans, and bask in the glow of artificial light from it's connected display. Because, dear reader, it's time to start anew! And what better way to achieve that, than with a clean install of NixOS?

Sure, perhaps you vowed this would be the year you'd stop distrohopping1, saving your SSD from near certain doom. But if that's the case, there's a good chance you might just find NixOS to be that final hop you've been looking for ... Well, only one way to find out, right?

[INFO]: Changelog

Jan, 9th: Added notes on updating nix flakes, what a flake.lock file is, how to override nix flake inputs, and other fun, clarifying notes. See: #7bb10d9, #5dfc95e, #e533186.

[INFO]: On future NixOS versions

I wrote most of this article before NixOS 24.11 (Vicuña) was made stable. At the time I went through the install process for this write-up, NixOS 24.05 (Uakari) was loaded onto my USB. Currently, NixOS 25.05 (Warbler) is the latest Unstable release as of the time of publishing (1/4/2024).

With that said, I'm currently unaware of any major differences in the install process between NixOS 24.05, 24.11, and 25.05.

Additionally, when differences do appear, the gcc compiler (or other compiler) used during nixos-rebuild will likely print out a warning message to the console explaining which deprecated (or soon to be deprecated) options you've used and what changes need to be made to correct the configuration you're trying to build—well, theoretically at least.

As such, I hope this guide is able to serve you well! Hopefully into the future too.

Introduction

After taking some time to setup a fresh install of one of my favorite distros, I felt it was a good idea to post a write up about the somewhat extensive process on installing NixOS with Full Disk Encryption (FDE), enabling secure boot, and automagically unlocking the encrypted LUKS device(s) during the boot process with keys enrolled into the motherboard's TPM2 chip.

Now, I should state that I didn't come up with this guide alone. This guide is more of an amalgamation, based pretty heavily upon a lot of other guides and resource materials out there scattered across the net. As such, I encourage you to check out the reference materials2, 3, 4, 5, 6, 7, 8, 9 I used, linked in the footnotes below.

What is NixOS?

NixOS is a operating system (OS) based on Nix, a purely functional package management system. Nix (the package manager) treats packages like how values are treated in purely functional programming languages, like Haskell, and stores the resultant packages in the Nix store (/nix/store). Such packages are built from Nix Expressions, written in the purely functional Nix language.10

In NixOS specifically, the whole OS is built by the Nix package manager, from a Nix expression that declares the entire system's configuration, typically in a configuration.nix file. Because everything is declared in a purely functional manner, a new configuration cannot overwrite previous configurations. This results in reliable, atomic upgrades, (which can be atomically rolled back) and reproducible system configurations.10

[WARN]: Non-FHS Compliance

The immediate implications of the Nix store is that Nix, and by extension NixOS, are non Filesystem Hierarchy Standard (FHS) compliant. This creates many immediate benefits (listed above), but also some new challenges too, such as running precompiled binaries11.

While there's workarounds12, 11 and escape hatches13 for the latter, If this guide is how you experience NixOS for the first time, then I should warn you about it's most common pain point, before you sink some time into this project. As such, please consider if non-FHS compliance is a deal breaker for you, before wiping your SSD.

Who is the guide for?

This guide is for anyone interested in using NixOS10, in a relatively secure fashion, with modern conveniences like using an onboard TPM2 chip to decrypt LUKS devices during boot up. Additionally, the level of technical knowledge in this guide, assumes a somewhat decent familiarity with Linux systems, at least to the point where one might feel comfortable enough to perform a manual install of either Arch Linux14 or Gentoo15.

While we won't be installing Arch or Gentoo today, we are going to be making heavy use of the command line and TUI based text editors as we perform a manual install of NixOS. If that's something you're up for, then this guide is written for you.

On the other hand, if this is your first exposure to Linux or even Unix based operating systems, let alone NixOS, then I encourage you to consider skimming through this guide, instead of actually performing any of the steps contained within it. That way, you can at least discover if anything I talk about today seems interesting to you, even if it seems beyond your current comfort level of technical expertise. Because, who knows, you might just find you've been bitten by the 'Nix bug before you know it! Also, should that happen, I highly recommend trying a traditional Linux distro like fedora workstation, just to get your feet wet, before diving headfirst into something that's far less user-friendly.

Goals of this Guide

By the end of this article, my hope is that you’ll be able to:

  1. Install NixOS with Full Disk Encryption.
  2. Use a flake.nix to configure your NixOS system.
  3. Install and configure Lanzaboote to enable secure boot on your machine.
  4. Use systemd-cryptenroll to unlock your LUKS devices via your motherboard's TPM chip.
  5. Use home-manager to configure your shell and git/gh.

Part 01: From Zero to NixOS

Prerequisites

  1. A machine with a UEFI motherboard
  2. A machine with a TPM2 chip (This is only needed for the Unlock LUKS via TPM2 part of the guide, which uses tpm2-tss to enable systemd-cryptenroll to enroll your LUKS keys into the TPM chip.16)
  3. A bootable USB (4 GB minimum) with the Minimal NixOS ISO17 loaded onto it

Booting the installer

  1. Repeatedly mash F2 or F10 or DEL or whichever key let’s you into your system’s UEFI/Bios.
  2. Disable secure boot (if enabled).
  3. Disable the CSM/Legacy Support (if enabled/applicable).
    1. WARNING: If this was enabled, any legacy hardware you might have (e.g., a GTX 660 with a VBIOS lacking UEFI support) will immediately become incompatible/stop working (until you re-enable CSM/Legacy Support). If you continue, you acknowledge that you're comfortable with removing/losing any incompatible hardware from your current system.
  4. Move the NixOS USB to the highest boot priority (if applicable)
  5. Save your changes, and boot into your NixOS USB.
  6. Once you've reached GRUB, feel free to either load the installer from the USB itself, or copy it to the RAM. You can also choose "no modeset" if you're having issues (usually graphical) reaching the installer's TTY.
[CRITICAL]: Secure boot requires UEFI!

Installing NixOS in UEFI mode is critical to enabling secure boot in part 02. Most modern motherboards/hardware supports UEFI by default. Still yet, you might want to ensure that the Compatibility Support Module (CSM) is disabled if your motherboard UEFI/BIOS has that option.

Connect to the Internet

Before we proceed with the installation, it’s probably a good idea to make sure you can access the internet way before making any drastic changes (partitioning, formatting, etc.) that would affect your currently installed OS.

So, plug in an ethernet cable, or follow the steps below to setup WiFi, then try to ping a website: e.g., ping google.com. If you can see packets coming back, hit ctrl+c to stop the pinging, then proceed to partitioning.

Otherwise, stop here, and start troubleshooting. If you still can’t connect to the internet, it’s possible your network adapter is just too new, and thus isn’t supported by the Linux kernel at this time.

WiFi Setup

First, make sure your wireless network adapter is actually enabled. You can see which adapters are available with ip link

[nixos@nixos:'']$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
 link/ether 55:a5:b5:c5:d5:f5 brd ff:ff:ff:ff:ff:ff
3: wlp: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DORMANT group default qlen 1000
 link/ether f5:d5:c5:b5:af:f5 brd ff:ff:ff:ff:ff:ff

If your WiFi adapter is DOWN like mine (wlp) just run:

# ip link set <your-interface> up

If that results in an error:

RTNETLINK answers: Operation not possible due to RF-kill

Please see the note on RF-kill Unblocking below, otherwise skip to Getting Connected with wpa_supplicant.

[INFO]: RF-kill Unblocking

First, run rfkill to investigate what interface is blocked.

[nixos@nixos:'']$ rfkill
ID TYPE      DEVICE               SOFT      HARD
 0 wlan      ideapad_wlan      blocked unblocked
 1 bluetooth ideapad_bluetooth blocked unblocked
 2 bluetooth hci0              blocked unblocked
 3 wlan      phy0              blocked unblocked

In my case, my wlan interface is “soft” blocked. So, running # rfkill unblock wlan unblocks it.

[nixos@nixos:'']$ sudo rfkill unblock wlan
ID TYPE      DEVICE                 SOFT      HARD
 0 wlan      ideapad_wlan      unblocked unblocked
 1 bluetooth ideapad_bluetooth   blocked unblocked
 2 bluetooth hci0                blocked unblocked
 3 wlan      phy0              unblocked unblocked

Then running ip link set wlp up results in no error.

[nixos@nixos:'']$ sudo ip link set wlp up

[nixos@nixos:'']$ _
Getting Connected with wpa_supplicant

With the WiFi adapter actually available, we can now continue (mostly) from the official NixOS manual9 for getting connected to the internet run:

# systemctl start wpa_supplicant

Then bring up the CLI with: wpa_cli. From there, you can scan for networks:

> scan
OK
> scan_results
bssid / frequency / signal level / flags / ssid
1a:2b:34:56:70    5555  -53 [WPA2-PSK+SAE-CCMP][WPS][ESS] myhomenetwork
...

Then add, set, and enable your network:

> add_network
0
> set_network 0 ssid "myhomenetwork"
OK
> set_network 0 psk "mypassword"
OK
> set_network 0 key_mgmt WPA-PSK
OK
> enable_network 0
OK

Alternatively for an enterprise network:

> add_network
0
> set_network 0 ssid "eduroam"
OK
> set_network 0 identity "myname@example.com"
OK
> set_network 0 password "mypassword"
OK
> set_network 0 key_mgmt WPA-EAP
OK
> enable_network 0
OK

If everything went okay after the last command, you’ll something like:

...
> enable_network 0
OK
<3>CTRL-EVENT-SCAN-STARTED
<3>CTRL-EVENT-SCAN-RESULTS
<3>SME: Trying to authenticate with 1a:2b:34:56:70 (SSID='myhomenetwork' freq=5555 MHz)
<3>Associated with 1a:2b:34:56:70
<3>CTRL-EVENT-SUBNET-STATUS-UPDATE status=0
<3>WPA: Key negotiation completed with 1a:2b:34:56:70  [PTK=CCMP GTK=CCMP]
<3>CTRL-EVENT-CONNECTED - Connection to 1a:2b:34:56:70 completed [id=0 id_str=]
>

Then all you have to do is run quit to return to the shell, and we can proceed with the next step.

Partitioning

[CRITICAL]: Partitioning will result in DATA LOSS!

Please make sure to have backed up anything and everything that is important to you, before proceeding through this section!

Now that we’re absolutely positive we can connect to the internet, we’re going to use fdisk to partition the drive we want to install NixOS on. Running the ensuing fdisk commands will create both a boot partition and a LVM partition. The LVM partition will hold both a root partition and a swap partition.

We’ll use the fdisk CLI to perform the partitioning part of the process. But first, we can also use it to list our disks via # fdisk -l, so we can get the exact location of the disk we want to partition.

[nixos@nixos:'']$ sudo fdisk -l
Disk /dev/loop0: 1021.89 MiB, 1071525888 bytes, 2092824 sectors

Disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
Disk model: SAMSUNG M2VLB512
Units: sectors of 1 ** 512 = 512 butes
Sector Size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 09515620-9D73-11EF-BE8C-0800200C9A66

Device             Start        End   Sectors   Size Type
/dev/nvme0n1p1      2048     206847    204800   100M EFI System
/dev/nvme0n1p2    206848     239615     32768    16M Microsoft reserved
/dev/nvme0n1p3    239616  998639615 998400000 476.1G Microsoft basic data
/dev/nvme0n1p4 998639616 1000212479   1572864   768M Windows recovery environment

...

Once we've found our disk, we'll pass it's location as an input into fdisk to perform the actual partitioning steps:

# fdisk /dev/your-disk-to-partition

In my case the command looks like: # fdisk /dev/nvme0n1.

[CRITICAL]: Be sure your commands reference your drive to partition!

Many of the commands featured in this guide refer to /dev/nvme0n1 as an example. However, You might have your drive to partition on /dev/sda for instance. So, to avoid any unnecessary heartache, please make sure the commands you're running are on the drive you actually want to partition.

Create a Empty GPT Table

To begin, we’ll clean up any old partitions by creating a empty GPT partition table (g):

Command (m for help): g
Created a new GPT disklabel (GUID: 5213d2a2-a040-44c4-ba6a-02787812777e).

Command (m for help):

To verify this, you can print (p) out the new partition table, which should now be empty:

Command (m for help): p
Disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
Disk model: SAMSUNG M2VLB512
Units: sectors of 1 ** 512 = 512 butes
Sector Size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 5213d2a2-a040-44c4-ba6a-02787812777e

Command (m for help):
[WARN]: Empty partition table !== Secure erase

Creating an empty partition table doesn't securely erase the data on your disk. If that's something you'd like to perform, perhaps ATA Security Erase18 might be something worth looking into.

Create the EFI System Partition (ESP)

We can then create the ESP like so:

  1. n
  2. (partition number; default) enter
  3. (first sector: default) enter
  4. (last sector) +1G
  5. (remove signature if applicable) Y
  6. t (change partition type)
  7. (partition type or alias) 1 (EFI)
Command (m for help): n
First sector (2048-1000214527), default 2048):
Last sector, +sectors or +size{K,M,G,T,P} (2048-1000214527, default 1000215216): +1G

Created a new partition 1 of type 'Linux filesystem' and of size 1 GiB.

Command (m for help): t
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI'.

Command (m for help):

Create the LVM Partition

Next, we’ll add our LVM partition:

  1. n
  2. (partition number: default) enter
  3. (first sector: default) enter
  4. (last sector: default) enter
  5. (remove signature if applicable) Y
  6. t (change partition type)
  7. (partition number: (1, 2, default 2)) enter
  8. (partition type or alias:) 44 (Linux LVM)
Command (m for help): n
Partition number (1, 2, default 2):
First sector (2099200-1000214527), default 2099200):
Last sector, +sectors or +size{K,M,G,T,P} (2048-1000215216, default 1000215216):

Created a new partition 2 of type 'Linux filesystem' and of size 475.9 GiB.

Command (m for help): t
Partition number (1, 2, default 2):
Partition type or alias (type L to list all): 44
Changed type of partition 'Linux filesystem' to 'Linux LVM'.

Command (m for help):

Verifying the Partitions

If you print (p) out the partition table again, it'll probably look similar to this:

Command (m for help): p
Disk /dev/nvme0n1: 476.94 GiB, 512110190592 bytes, 1000215216 sectors
Disk model: SAMSUNG M2VLB512
Units: sectors of 1 ** 512 = 512 butes
Sector Size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 5213d2a2-a040-44c4-ba6a-02787812777e

Device           Start        End   Sectors   Size Type
/dev/nvme0n1p1    2048    2099199   2097152     1G EFI System
/dev/nvme0n1p2 2099200 1000214527 998115328 475.9G Linux LVM

Command (m for help):

if it looks right, then we can save (w) our changes and exit!

Now, running # lsblk should look something like this:

[nixos@nixos:'']$ sudo lsblk
NAME        MAJ:MIN RM   SIZE  RO TYPE MOUNTPOINTS
...
nvme0n1      259:0    0  476.9G  0 disk
|-nvme0n1p1 259:3    0      1G  0 part
|-nvme0n1p2 259:4    0  475.9G  0 part

[nixos@nixos:'']$

Encrypting with LUKS

[CRITICAL]: Running luksFormat will cause DATA LOSS!

Assuming you skipped the partitioning step, please make sure to have backed up anything and everything that is important to you, before proceeding through this section!

We’ll be encrypting the LVM partition with LUKS (version 2). To do that, we just have to run the following command:

# cryptsetup -v -y --label=NIXLUKS luksFormat --type luks2 /dev/nvme0n1p2

The -v flag ensures a verbose output, -y ensures it’ll ask us for our encryption password twice to confirm, the label flag is simply assigned to NIXLUKS but you can call it whatever you want. luksFormat actually encrypts our disk, and the --type flag set to luks2.19 This is important because it's both newer, and a necessary requirement to use systemd-cryptenroll, which enables enrolling your LUKS keys into the TPM2.16

[nixos@nixos:'']$ sudo cryptsetup -v -y --label=NIXLUKS luksFormat --type luks2 /dev/nvme0n1p2

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

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase fpr /dev/nvme0n1p2:
verify passphrase:
Key slot 0 created.
Command successful.

[nixos@nixos:'']$

Verifying the LUKS Device

To verify the partition got encrypted, you can inspect the LUKS header using:

# cryptsetup luksDump /dev/nvme0n1p2

Which will give an output similar to this:

[nixos@nixos:'']$ sudo cryptsetup luksDump /dev/nvme0n1p2
LUKS header information
Version         2
...
UUID:           09515620-9D73-11EF-BE8C-0800200C9A66
label:          NIXLUKS
...

The most important thing to note is the version number and your label. If it’s “2”, and your label looks right, then we’re all good to go.

[WARN]: Back up your LUKS header!

As a best practice, you probably should backup the LUKS header sometime later.20 You can do so with the following command:

# cryptsetup luksHeaderBackup /dev/nvme0n1p2 --header-backup-file /path/to/backup.dat

Opening the LUKS Device

Now, we’re going to open our encrypted LUKS partition and create a reference to it on /dev/mapper/cryptroot with:

# cryptsetup luksOpen /dev/nvme0n1p2 cryptroot

You can then check our mapped partition exists with: ls /dev/mapper/cryptroot

This won't return anything really (because there's nothing in cryptroot yet), unless cryptroot doesn't exist.

[nixos@nixos:'']$ ls /dev/mapper/cryptroot
/dev/mapper/cryptroot

[nixos@nixos:'']$

LVM Partitioning

Before creating the root and swap (and other possible) logical volumes (LVs), we'll first need to create a Physical Volume on cryptroot, which can then be used to create a Logical Volume Group (LVG) named lvmroot.

# pvcreate /dev/mapper/cryptroot
# vgcreate lvmroot /dev/mapper/cryptroot

With that, we can create the root LV and the swap LV. You can of course create as many LVs as you want, such as a home LV. However, for my purposes, I’m going to keep it simple.

Create the swap Logical Volume

We can create the swap LV on our root LVG (lvmroot) with the following command:

# lvcreate -L24G lvmroot -n swap
[INFO]: swap space depends on the use case

How much swap space you need is use case dependent. In some cases, it might not even be needed at all! As such, please see the linked stackoverflow askubuntu thread21 for more. For my own usecase, I’ve got 16 GB of ram on the laptop I'm using as the test subject example for this guide, and plan to use hibernate, so 16 * 1.5 = 24 GB.

Create the root Logical Volume

With the swap LV set, we can set the root LV to take up the rest of the available space:

# lvcreate -l 100%FREE lvmroot -n root

Formatting

This is a fairly standard procedure, the only thing of note here is adding labels, which makes mounting the partitions much easier.

[INFO]: A volume label's max length == 11 || 16 bytes

The volume label string for the bootable, ESP partition (FAT32) has a maximum length of only 11 bytes22. Both the ext4 and Swap partitions have a maximum volume label length of 16 bytes. 23, 24.

  1. Format the ESP partition:

    # mkfs.fat -F 32 -n NIXBOOT /dev/nvme0n1p1
    
  2. Format the Root LV:

    # mkfs.ext4 -L NIXROOT /dev/mapper/lvmroot-root
    
  3. Format the Swap LV (if you made one):

    # mkswap -L NIXSWAP /dev/mapper/lvmroot-swap
    

Mounting

Then we can mount the formatted partitions like so.

  1. Mount the Root LV:

    # mount /dev/disk/by-label/NIXROOT /mnt
    
  2. Create the ESP Directory:

    # mkdir /mnt/boot
    
  3. Mount the ESP:

    # mount -o umask=0077 /dev/disk/by-label/NIXBOOT /mnt/boot
    
  4. Turn on Swap:

    # swapon -L NIXSWAP
    

Verifying the Mounts

To verify everything is in the right place, running:

# lsblk -o name,label,size,type,mountpoints /dev/nvme0n1

should result in output similar to this:

[nixos@nixos:'']$ sudo lsblk -o name,label,size,type,mountpoints /dev/nvme0n1
NAME                LABEL     SIZE TYPE  MOUNTPOINTS
nvme0n1                     476.9G disk
|-nvme0n1p1         NIXBOOT     1G part  /mnt/boot
|-nvme0n1p2         NIXLUKS 475.9G part
   |-cryptroot              475.9G crypt
     |-lvmroot-swap NIXSWAP    24G lvm   [SWAP]
     |-lvmroot-root NIXROOT 451.9G lvm   /mnt

[nixos@nixos:'']$

Installing NixOS

We’ll first generate a minimal configuration file. Then we’ll edit it to support our LUKS encrypted partitions.

# nixos-generate-config --root /mnt

Then we can use Vim (or nano) prefixed with sudo to edit our generated config to mount our LUKS partition. If you forget to elevate your editor command, you won't be able to save your changes—don't ask me how I know that.

# vim /mnt/etc/nixos/hardware-configuration

hardware-configuration.nix

Our generated config currently looks something like this:

# Do not modify this file! It was generated by ■nixos-generate-config■
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernerlModules = ["xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ "dm-snapshot" ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{
device = "/dev/disk/by-uuid/f96d7825-6b61-452b-a6f9-456ad8ba308f";
fsType = "ext4";
};
fileSystems."/boot" =
{
device = "/dev/disk/by-uuid/6335-2377";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices =
[{ device = "/dev/disk/by-uuid/b0849f28-36bf-41c1-b58f-108f4a51442a"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declaration with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updatedMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

While it's nearly perfect, it doesn't take into account our LUKS device, and so we'll have to disregard the ominous warnings to make some minor changes if we want our machine to be useable. In short, all we have to do is:

  • Add cryptd to boot.initrd.kernelModules.
  • Define our LUKS partition as a LUKS device under boot.initrd.luks.devices."cryptroot".
  • Update our filesystem mounts to use “by-label” for ease of use / readability.

This last change is optional, but I find it helpful for compatibility reasons:

  • Set hardware.enableAllFirmware to true.
# Do not modify this file! It was generated by ■nixos-generate-config■
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{config, lib, pkgs, modulesPath, ...}:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernerlModules = ["xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ "dm-snapshot" "cryptd" ]; # add cryptd
# Define our LUKS device
boot.initrd.luks.devices."cryptroot".device = "/dev/disk/by-label/NIXLUKS";
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
# Use labels instead of UUIDs to mount our LVs
fileSystems."/" =
{
device = "/dev/disk/by-label/NIXROOT";
fsType = "ext4";
};
fileSystems."/boot" =
{
device = "/dev/disk/by-label/NIXBOOT";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices =
[{ device = "/dev/disk/by-label/NIXSWAP"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declaration with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.enableAllFirmware = true; # Helps with compatibility
hardware.cpu.intel.updatedMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

With our edits in place, we can save and exit the hardware-configuration.nix file and move on to configuration.nix.

[INFO]: Thoughts on the ominous warnings

In all likelihood, this is the only time you'll ever run nixos-generate-config, so feel free to make edits to hardware-configuration.nix. Just be aware that running nixos-generate-config again, will undo all the changes you've made to it.

configuration.nix

configuration.nix is where you'll want to configure everything else about your machine (e.g., hostname, user accounts, networking, window managers, programs, etc.). As such, we'll want to make some edits from the minimal configuration to something a lot more useable. Because, as it stands, the generated configuration.nix has most of what we need commented out:

# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# networking.hostName = "nixos"; # Define your hostname.
# Pick only one of the below networking options.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant
# networking.networkManager.enable = true; # Easiest to use and most distros use this by default.
# Set your time zone.
# time.timeZone = "America/New_York";
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Select internationalisation properties.
# i18n.defaultLocale = "en_US.UTF-8";
# console = {
# font = "Lat2-Terminus16";
# keyMap = "us";
# useXkbConfig = true; # use xkb.options in tty.
# }
# Enable the X11 windowing system.
# services.xserver.enable = true;
# Configure keymap in X11
# services.xserver.xkb.layout = "us";
# services.xserver.xkb.options = "eurosign:e,caps:escape";
# Enable CUPS to print documents.
# services.printing.enable = true;
# Enable sound.
# hardware.pulseaudio.enable = true;
# OR
# services.pipewire = {
# enable = true;
# pulse.enable = true;
# }
# Enable touchpad support (enabled default in most desktopManager).
# services.libinput.enable = true;
# Define a user account. Don't forget to set a password with ■passwd■.
# users.users.name = {
# isNormalUser = true;
# extraGroups = [ "wheel" ]; # enable ■sudo■ for the user.
# }
# List packages installed in system profile. To search, run:
# $ nix search wget
# environment.systemPackages = with pkgs; [
# vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
# wget
# ];
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# List services that you want to enable:
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in cage you
# accidentally delete configuration.nix.
# system.copySystemConfiguration = true;
# The option defines the first version of NixOS you have installed on this particular machine.
# and is used to maintain compatibillity with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your pacakges and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
#For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "24.05"; # Did you read the comment?
}

To make our changes, we can use vim or nano again to load configuration.nix into an editing buffer:

# vim /mnt/etc/nixos/configuration.nix

From there we'll, uncomment out network manager, we'll enable flakes, we'll update our time zone, add an account, and anything else we might need.

Core configuration

The following configures/enables what I believe to be the most important features/options. You'll be able to enable most of them simply by uncommenting them out (removing the # before them) and or modifying their default values.

Add a hostname
networking.hostName = "my-nixos-machine"; # Define your hostname.

Your hostname will correlate with the host system defined in your nixosConfiguration. If you plan on sharing a single flake.nix between multiple NixOS machines, it's probably for the best you change this to something unique—well, unique amongst the machine's that have differing configurations.

Enable network manager
networking.networkManager.enable = true;

Network Manager is both the easiest and (unsurprisingly) the default networking tool for most Distros. It's also what I prefer using, but you might prefer wpa_supplicant instead.

Enable flakes
nix.settings.experimental-features = ["nix-command" "flakes"];

Flakes are one of my most favorite things about Nix. They're incredibly versatile from setting up modular, multi-machine configurations from a single flake.nix25 to creating reproducible development environments26. In short, they're quite handy.

Update the time zone
time.timeZone = "America/Los_Angeles"

I believe it's also possible to update your time zone imperatively as well, if you're Desktop Environment (i.e., Gnome, Plasma, etc.) allows it. As such, setting it from configuration.nix will thus establish it as the "default" global/system time zone.

Add a user account with superuser privileges, that's also in the networkmanager group
users.users.mycoolusername = {
isNormalUser = true;
extraGroups = ["wheel" "networkmanager"];
};

superuser privileges via the "wheel" group, gives your user (in this case a user named mycoolusername) access to sudo. If you tried to use sudo without being in the wheel group or being given explicit access somewhere in the sudoers file, well it becomes an "incident", and gets reported ... somewhere27.

Adding your user to the networkmanager group, while possibly optional, helps to ensure your user has the right permissions to use/configure networkmanager.

[INFO]: Dude, Where's my password?

If you're wondering about the user password, that's something we'll configure at the very end, just before rebooting.

Enable unfree packages (optional)
nixpkgs.config.allowUnfree = true;

Allowing "unfree" packages, will give you access to closed-source/proprietary binaries/software, such as Nvidia's proprietary Linux drivers. It's really up to you whether you want to enable this feature or not.

Helpful Packages/Software
environment.systemPackages = with pkgs; [
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
wget
firefox
chromium # swap for ungoogled-chromium if you prefer
];

At minimum, you'll want to add a secondary terminal based editor like vim, and a browser like firefox or alternatively, chromium and or ungoogled-chromium.

Anything Else You Might Want

Beyond the Core configuration above, I'll leave it up to you to determine how you want to configure the rest of your machine. For example, you'll probably want to choose a Desktop Environment (DE) or alternatively, configure a Window Manager (WM). I'll leave the following suggestions to give you some ideas:

Add a Desktop Environment or, alternatively, a Window Manager

In 2024, the two most popular DEs remain to be Gnome and Plasma. You can enable Gnome like this (see: wiki.nixos.org/wiki/GNOME28):

services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;

Or KDE Plasma like this (see: wiki.nixos.org/wiki/KDE29)

services.xserver.enable = true; # optional
services.displayManager.sddm.enable = true;
services.displayManager.sddm.wayland.enable = true;
services.desktopManager.plasma6.enable = true;

Alternatively you can configure a Window Manager, such as Sway, like this (see: wiki.nixos.org/wiki/Sway30)

# A very minimal Sway configuration
environment.systemPackages = with pkgs; [
grim # screenshot functionality
slurp # screenshot functionality
wl-clipboard # wl-copy and wl-paste for copy/paste from stdin / stdout
mako # notification system developed by swaywm maintainer
];
# Enable the gnome-keyring secrets vault.
# Will be exposed through DBus to programs willing to store secrets.
services.gnome.gnome-keyring.enable = true;
# enable sway window manager
programs.sway = {
enable = true;
wrapperFeatures.gtk = true;
};
services.xserver = {
displayManager.gdm = {
enable = true;
wayland = true;
};
};
[INFO]: NixOS makes It's painless to try out a new DE/WM

The nice thing about NixOS is that you're not stuck with a DE or WM. If you want to try something else, edit your config to accomodate whichever thing you want, run # nixos-rebuild switch, and you'll have a totally new desktop experience!

Enable PipeWire (sound)
# rtkit is optional but recommended
security.rtkit.enable = true;
services.pipewire = {
enable = true; # if not already enabled
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# If you want to use JACK applications, uncomment this
#jack.enable = true;
};

PipeWire is a modern replacement for Pulse Audio, and if you're using Ardour or some other DAW, you'll want to enable JACK as well (see: wiki.nixos.org/wiki/PipeWire31). For configuring Bluetooth audio you'll want to see: wiki.nixos.org/wiki/PipeWire#Bluetooth_Configuration32.

Enable Bluetooth
hardware.bluetooth.enable = true; # enables support for Bluetooth
hardware.bluetooth.powerOnBoot = true; # powers up the default Bluetooth controller on boot

The above should enable Bluetooth, and if your DE doesn't come with a Bluetooth manager GUI, you can additionally enable the blueman applet

services.blueman.enable = true;

For more see: wiki.nixos.org/wiki/Bluetooth33.

Enable CUPS + Avahi
# Enable CUPS to print documents.
services.printing.enable = true;
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};

Enabling CUPS will allow you to print things. Enabling Avahi will allow you to find printers on your network. For more see: wiki.nixos.org/wiki/Printing34.

Use an Alternative Shell (e.g., zsh)
programs.zsh.enable = true;
users.defaultUserShell = pkgs.zsh;

If you've been hacking away at the command line on a Mac in the last decade, chances are you've become attached to using zsh instead of bash. In that case, the above will change the default shell (bash) to something that feels much more at home (zsh). For more see: wiki.nixos.org/wiki/Zsh35.

Alternatively, you can use fish36 or even nushell37.

[INFO]: Customizing the shell

As for custom prompts like starship38 and plugins like zsh-syntax-highlighting39, we'll be using Home-Manager via the flake.nix in this guide later to configure that.

Example configuration.nix
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "my-nixos-machine"; # Define your hostname.
networking.networkManager.enable = true; # Easiest to use and most distros use this by default.
# Set your time zone.
time.timeZone = "America/Los_Angeles";
# Enable Flakes
nix.settings.experimental-features = ["nix-command" "flakes"];
# Enable the Gnome Desktop Environment
services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
# Enable CUPS to print documents.
services.printing.enable = true;
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
# Enable sound.
security.rtkit.enable = true;
services.pipewire = {
enable = true; # if not already enabled
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# If you want to use JACK applications, uncomment this
#jack.enable = true;
};
# Bluetooth
hardware.bluetooth.enable = true; # enables support for Bluetooth
hardware.bluetooth.powerOnBoot = true; # powers up the default Bluetooth controller on boot
# Define a user account. Don't forget to set a password with ■passwd■.
users.users.mycoolusername = {
isNormalUser = true;
extraGroups = ["wheel" "networkmanager"];
};
# Default shell => ZSH
programs.zsh.enable = true;
users.defaultUserShell = pkgs.zsh;
# Allow Unfree packages
nixpkgs.config.allowUnfree = true;
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
vim
wget
firefox
chromium
];
system.stateVersion = "24.05"; # Did you read the comment?
}

If you're happy with the above, we can move on to the best part, the flake.nix.

flake.nix

Alright, we're nearly at the finish line. The final piece of the (installation) puzzle is to create a flake.nix at /mnt/etc/nixos, in the same place where you've created the hardware-configuration.nix and configuration.nix, to bring everything together.

The flake.nix is where you'll be able to add additional modules as inputs, such as home-manager and lanzaboote.

[INFO]: Flakes are cool.

I'll be honest, Nix flakes are far more versatile/feature-packed than I've let on. I highly recommend giving ryan4yin's NixOS & Flakes Book40 a read sometime.

We can define a minimal flake.nix like so:

{
description = "A minimal flake.nix for a NixOS machine";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = {self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
my-nixos-machine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; # Assumes a standard x86 CPU
modules = [./configuration.nix];
};
};
};
}
[INFO]: Why nixos-unstable? Isn't it unstable?

I'm using nixos-unstable in this guide instead of the stable nixos-24.11 for a few reasons. One is that home-manager:master tracks nixos-unstable. Two is that the nature of Nix makes rollbacks incredibly easy, and significantly cuts down the risk of using nixos-unstable. Finally, while the nixos-unstable channel is occasionally true to it's namesake, it's not as unstable or as bleeding-edge as something like Arch Linux. That's due to the somewhat significant amount of automated build testing any given commit into nixpkgs:master needs to go through before being released as the latest nixos-unstable build/channel update.41

And with that, we can finally proceed to the very much anticipated install command.

nixos-install

This is it, moment of truth time. To begin, we’ll change directories to our nixos directory:

cd /mnt/etc/nixos

Then we’ll update our flake. However, because the minimal NixOS install doesn't enable the nix-command nor flakes, we'll need to append these features as flags to actually perform this step:

# nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update
Updating Your System with a flake.nix

Once NixOS is actually installed (and the nix-command and flakes features are both enabled), you can update your system flake.nix simply by running # nix flake update. Doing so will update the nixpkgs input to the latest git commit for whichever channel you've configured (in our case that's nixos-unstable). However, simply updating the flake doesn't update the system itself. You still have to build an updated system configuration (and switch to it) by running # nixos-rebuild switch.

Note: If you encounter a flake.nix in userspace, you typically don't have to elevate the command.

If that presented no errors, your output should look something like this:

[nixos@nixos:/mnt/etc/nixos]$ sudo nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update
warning: creating lock file '/mnt/etc/nixos/flake.lock'

[nixos@nixos:/mnt/etc/nixos]$
[INFO]: On flake.lock, & overriding flake.nix inputs

If you're familiar with modern package managers for dev tooling (e.g., Cargo, NPM, yarn, Bun, etc), then you (conceptually) already understand what flake.lock does and why.

For everyone else, a flake.lock locks the commit hashes of your flakes' configured inputs, by writing it into the flake.lock file. In practice, this means you can alter your system configuration (i.e., configure a new option, add a new package, etc.), without having to upgrade your entire system/installed packages, even way into the future (assuming the features you're changing/adding existed in said locked input commit hash).

The above is possible because nixos-rebuild <switch|reboot> will use the commit hashes of the inputs configured in the flake.nix found in the flake.lock file. In our case, following the prior nix flake update, running nixos-rebuild switch will use the commit hashes of our input (NixOS/nixpkgs/nixos-unstable) found in the newly created flake.lock, and generate a fresh system profile from it. This also means that if you share your flake.nix and flake.lock to a different machine, it should reproduce the exact same system configuration upon nixos-rebuild switch (so long as you don't run nix flake update and alter the flake.lock).

Note: You can override/lock an input's commit hash in your flake.nix. In fact, it's sometimes necessary. For example, I once locked my nixpkgs to commit 5633bcff0c61 because a package I wanted to use in later nixos-unstable builds was a bit too unstable (at least for a week or two).

{
inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ nixpkgs.url = "github:NixOS/nixpkgs/5633bcff0c61";
};
}

hint-01: It's somewhat unsafe, but totally possible to use PRs to NixOS/nixpkgs before they're merged following the same technique/idea.

hint-02: You can find the status and commit hashes of the latest builds of all the different Nix channels at status.nixos.org. The build status and corresponding commits of nixos-unstable specifically, can be found at https://hydra.nixos.org/job/nixos/trunk-combined/tested#tabs-status.

With a clean flake update, that's the go-ahead to perform the magical install command.

# nixos-install --root /mnt --no-root-passwd --flake /mnt/etc/nixos#nixos

Be warned though, this step could take a while (even assuming no compilation errors). As such, now's probably a good time to stretch your legs and get some fresh air. You've probably been sitting here reading this guide for a while now, so, maybe relax your eyes, and go enjoy a refreshment of some kind (a cup of tea, coffee, etc.). That way, if there are any unexpected errors (likely syntax, like a forgotten semi-colon), you'll at least be better prepared to deal with it...

...Alright, if everything did go according to plan you should see output like the following:

[nixos@nixos:/mnt/etc/nixos]$ sudo nixos-install --root /mnt --no-root-passwd --flake /mnt/etc/nixos#my-nixos-machine
copying channel...
building the flake in path:/mnt/etc/nixos?lastModified=1729820879@narHash=sha256-xRGYbMOgJiuxxVKLtkxb0yi01srhw/NL652Uq/ghXXY%3D...
Installing the boot loader...
setting up /etc...
Initializing machine ID from random generator.
Created "/boot/EFI".
Created "/boot/EFI/systemd".
Created "/boot/EFI/BOOT".
Created "/boot/loader".
Created "/boot/EFI/Linux".
Copied "/nix/store/xg6f0c5pchmc2jq84s4np19jirnn90mn-systemd-256.6/lib/systemd/boot/efi/systemd-bootx64.efi" to "/boot/EFI/systemd/systemd-bootx64.efi".
Copied "/nix/store/xg6f0c5pchmc2jq84s4np19jirnn90mn-systemd-256.6/lib/systemd/boot/efi/systemd-bootx64.efi" to "/boot/EFI/BOOT/BOOTX64.EFI".
Created EFI boot entry "Linux Boot Manager".
Installation finished!

[nixos@nixos:/mnt/etc/nixos]$

Final Steps

VERY IMPORTANT: We still need to setup a password for our user account. We can do that like so:

# nixos-enter --root /mnt -c “passwd mycoolusername”

Once that's all set, it's finally time to reboot. You can do that safely3 like so:

# umount -R /mnt sudo swapoff -L NIXOS_SWAP sudo vgchange -a n lvmroot sudo cryptsetup close /dev/mapper/cryptroot reboot

Or dangerously via a simple: $ reboot

If everything went well, you’ll be greeted by LUKS asking for your passphrase.

<<< NixOS Stage 1 >>>

loading module dm-snapshot...
loading module cryptd...
loading module dm_mod...
running udev...
Starting sytemd-udevd version 256.6
Passphrase for /dev/disk/by-label/NIXLUKS: _

If it went really well, it'll accept your passphrase, and you'll arrive at your Display Manager of choice (or a TTY if that's what you configured), and you'll be able to login to your user with the password you just set.

At this point you can either continue configuring/tweaking your new NixOS machine to your liking before continuing to the next section, or you can just keep pushing through, the choice is up to you.

Part 02: Secure Boot with Lanzaboote

Lanzaboote is a wonderful project that I’ve been happily using on my NixOS machines for about a year now without a single issue. Still yet, it’s always a good idea to back up any important data before setting it up (like, your Windows BitLocker Recovery Keys, in case you're securely dual booting), and especially before updating Lanzaboote to the latest release.

In any event, we’ll be following along with the quick start guide lanzaboote/docs/QUICK_START8 for this section.

PLEASE BE ADVISED: THE QUICK START GUIDE LINKED ABOVE WILL PROVIDE YOU WITH FAR MORE ACCURATE/CURRENT INSTRUCTIONS THAN WHAT'S SHOWN HERE FOR THIS ARTICLE. THE FOLLOWING SECTIONS ABOUT LANZABOOTE SHOULD BE REGARDED AS SUGGESTIONS/COMMENTARY FOR EDUCATIONAL PURPOSES ONLY, RATHER THAN AS A TRUE INSTRUCTION MANUAL.

Confirm NixOS is installed in UEFI mode

Assuming you met the prerequisites and you successfully installed NixOS into the UEFI, the output of bootctl status should look like this:

❯ bootctl status
System:
     Firmware: UEFI 2.60 (INSYDE Corp. 22532)
Firmware Arch: x64
  Secure Boot: disabled (disabled)
 TPM2 Support: yes
 Measured UKI: no
 Boot into FW: supported

Current Boot Loader:
      Product: systemd-boot 256.6
...

If the firmware is UEFI and the current boot loader is systemd-boot we can continue.

Generate the Keys

We’ll need to use sbctl for this. You could add it to your system packages like so:

{pkgs, ...}:
{
environment.systemPackages = with; pkgs [
# sbctl needed to generate keys
sbctl
];
}

However, we want to just run it from an ephemeral nix shell via $ nix-shell -p sbctl (we're going to declare this package in a module, as we'll see).

Either way, once you have sbctl just run # sbctl create-keys like so:

sudo sbctl create-keys
[sudo] password for mycoolusername:
Created Owner UUID fcdc9ade-1757-4fd6-8376-bc21c7f4d093
Creating secure boot keys...✓
Secure boot keys created!

Configure the Lanzaboote Module (Flake)

The following is how I personally like to modularize my own NixOS flake based configuration. What we'll do is amend our minimal flake from earlier, adding lanzaboote as an input, and we'll enable it by adding it to our hosts module array. We're also going to create a directory in our /etc/nixos folder, called modules that will contain a module I'm calling lanza.nix. This is what the flake should look like:

{
description = "A minimal flake.nix for a SecureBoot-enabled NixOS machine";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
lanzaboote = {
url = "github:nix-community/lanzaboote/v0.4.1";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {self, nixpkgs, lanzaboote, ... }@inputs: {
nixosConfigurations = {
my-nixos-machine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; # Assumes a standard x86 CPU
modules = [
./configuration.nix
lanzaboote.nixosModules.lanzaboote
./modules/lanza.nix
];
};
};
};
}

Of course, the ./modules/lanza.nix doesn't exist yet, so let's create it and bring it up in our editor.

sudo mkdir /etc/nixos/modules
sudo touch /etc/nixos/modules/lanza.nix
sudo vim /etc/nixos/modules/lanza.nix

Then we can create something like this:

{pkgs, lib, ... }:
{
environment.systemPackages = with pkgs; [
# For debugging and troubleshooting Secure Boot.
sbctl
];
# Lanzaboote currently replaces the systemd-boot module.
# This setting is usually set to true in configuration.nix
# generated at installation time. So we force it to false
# for now.
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
}
[INFO]: If you previously declared pkgs.sbctl in your configuration.nix ...

Now might be a good time to remove it from your configuration.nix and redeclare it here.

Enable Lanzaboote

With the module configured, you should be cleared to run # nixos-rebuild switch, which will result in Lanzaboote being installed/enabled. You can check everything went well with the output from # sbctl verify

sudo sbctl verify
Verifying file database and EFI images in /boot...
✓ /boot/EFI/BOOT/BOOTX64.EFI is signed
✓ /boot/EFI/Linux/nixos-generation-1.efi is signed
✓ /boot/EFI/Linux/nixos-generation-2.efi is signed
✗ /boot/EFI/nixos/kernel-linux-6.11.5.efi is not signed
✓ /boot/EFI/systemd/systemd-bootx64.efi is signed

If the output looks clean, we can enable secure boot.

"It is expected that the files ending with bzImage.efi are not signed." - The QUICK START guide for Lanzaboote

Enabling Secure Boot

if you followed the beginning of this guide, secure boot is currently disabled. What we’re going to do now is turn it back on, but with setup mode enabled.

Enable Setup Mode

My Lenovo laptop has this setup mode, and if you happen to have it as well, simply turn it on and proceed to the next step.

[INFO]: Lenovo's setup mode can be quircky

On my particular Lenovo laptop, when I enable setup mode, it disables secure boot simultaneously. So, make sure to re-enable it either before you leave the UEFI/BIOS, or after enrolling the keys.

[INFO]: Manually replicating setup mode in ASUS' UEFI

If you, like me, also happen to have a machine with an ASUS motherboard, then we’ll have to take some extra steps.

  1. Enable secure boot
  2. Install factory default keys (if they’re empty)
  3. Delete every key except the Forbidden Signature Database (dbx)
    • This is typically the key at the bottom of the list
  4. Save changes and reboot

Enroll the Keys

Now that secure boot’s enabled (sorta), we can enroll the keys we generated earlier with vendor keys from Microsoft.

$ sudo sbctl enroll-keys --microsoft
[sudo] password for lani:
Enrolling keys to EFI variables...
With vendor keys from microsoft...✓
Enrolled keys to the EFI variables!

And now, just reboot! Once you’re back in, you can verify secure boot is activated (user mode).

$ bootctl status
System:
      Firmware: UEFI 2.60 (INSYDE Corp. 225332)
 Firmware Arch: x64
   Secure Boot: enabled (user)
  TPM2 Support: yes
  Measured UKI: yes
  Boot into FW: supported

Part 2.5: Unlocking LUKS with TPM2 using systemd-cryptenroll

At the very beginning of this guide, I promised I’d show you how to auto decrypt your LUKS device with TPM2. So, to do that, we’re going to amend the lanza.nix module we created earlier, to both install tpm2-tss and to create a tiny script (luksCryptenroller) to leverage a handy tool included in systemd: systemd-cryptenroll. The command our script calls on our LUKS device (NIXLUKS) looks something like this:

# systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-label/NIXLUKS

According to the man pages for systemd-cryptenroll42, the flags on the command do the following:

  • --wipe-slot=tpm2 clears out any previous keys bound to the slot.
  • --tpm2-device=auto automatically grabs the default TPM2 chip (usually found on your motherboard).
  • --tpm2-pcrs=0+7 binds the keys to Platform Configuration Registers 0 (platform-code, i.e., system firmware) and 7 (secure-boot-policy, i.e., the secure boot state; changes when enabled/disabled, or firmware certificates changes).

Then, we can implement said command into our lanza.nix module like this:

{pkgs, lib, ... }:
let
luksCryptenroller = pkgs.writeTextFile {
name = "luksCryptenroller";
destination = "/bin/luksCryptenroller";
executable = true;
# Note: You can hardcode additional LUKS devices like so:
# text = let
# ...
# luksDevice02 = "BEEGLUKS01";
# luksDevice03 = "BEEGLUKS02";
# in ''
# ...
# sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-label/${luksDevice02}
# sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-label/${luksDevice03}
# '';
text = let
luksDevice01 = "NIXLUKS";
in ''
sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0+7 /dev/disk/by-label/${luksDevice01}
'';
};
in
{
environment.systemPackages = [
luksCryptenroller
# For debugging and troubleshooting Secure Boot.
pkgs.sbctl
# Needed to use the TPM2 chip with `systemd-cryptenroll`
pkgs.tpm2-tss
];
# Lanzaboote currently replaces the systemd-boot module.
# This setting is usually set to true in configuration.nix
# generated at installation time. So we force it to false
# for now.
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/var/lib/sbctl";
};
}

I'll admit, not the prettiest (or the DRYest) bash script in the world, but hey, it gets the job done. A far more sophisticated approach would probably store the LUKS devices in an array, and use a for loop to run through it, calling systemd-cryptenroll with the current/ith device in the array. I'll leave that up to you to implement.

Enrolling the keys

This is rather straight forward just run $ luksCryptenroller (you'll immediately be prompted for your superuser password). Then just enter your passphrase for each LUKS device, in the order you declared them in.

[INFO]: Emergent properties of a reused shared LUKS passphrase

Fun fact, if all your LUKS devices share the same passphrase, systemd-cryptenroll seems to just know to re-use it, rather than prompting you for it again. As to how that's possible ... well, I have no idea. If by chance you happen to figure that out, I'd love to hear about it!

Part 03: Configuring Home Manager (Abridged)

home-manager is a very extensive utility for configuring the user (rather than the global/system) environment on a Nix/NixOS machine. It's far too much to talk about in detail here, so I'll leave you with the official Home Manager Manual43, a very handy tool: Home Manager Option Search44, and finally the nix-community/home-manager/issues45 page for when things go awry (It doesn't happen that often, but I'll admit weird bugs do happen on occasion).

As such, I'm going to very briefly walk you through configuring home-manager via further amending our flake.nix, and setting up the most important features I use it for (shell configuration, git/gh configuration, etc.).

Step 0 - fonts.nix

Shells look best with monospaced fonts, especially monospaced nerd-fonts, so before we get ahead of ourselves, let's create a fonts.nix in the systemwide space.

# touch /etc/nixos/modules/fonts.nix

And we'll bring it into an editor with:

# vim /etc/nixos/modules/fonts.nix

To create the following:

# modules/fonts.nix
{pkgs, ...}:
{
fonts = {
fontconfig = {
enable = true;
};
packages = with pkgs; [
noto-fonts
noto-fonts-emoji-blob-bin
noto-fonts-cjk-sans
nerd-fonts._0xproto # personal fav monospaced font. However, you can use whatever monospaced nerd font you'd like.
nerd-fonts.symbols-only
];
};
}

As you'll notice, this module enables a system wide fontconfig (you could also use home-manager to enable a user space one), and several common font packages, along with my favorite monospaced font (0xProto) packaged as a nerd-font.

We can then import it into configuration.nix like so:

# configuration.nix
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
# system modules
./modules/fonts.nix
];
...
}
[INFO]: On where to import modules within a flake.nix

You could import the above into the flake.nix module array as well, the snippet above just demonstrates that you can import it into your main configuration.nix file too. Inversely, you could move the lanza.nix module out of the flake.nix module array, and import it into the configuration.nix imports array instead as well.

This is possible because nested modules will ultimately wind up in the flake.nix module array anyway, so long as the parent modules (i.e., configuration.nix) containing such nested child modules (fonts.nix) are declared there.

We can then build the new configuration like so:

# nixos-rebuild switch

And with single # nixos-rebuild switch we now have all the fonts we need to continue.

Configuring Home Manager via flake.nix

This is rather straight forward (it's one of the neat things about flakes!), just like we added lanzaboote, we'll add home-manager as an input, and configure it as a module46.

{
description = "A minimal flake.nix for a SecureBoot-enabled NixOS machine, with Home Manager";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
lanzaboote = {
url = "github:nix-community/lanzaboote/v0.4.1";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {self, nixpkgs, lanzaboote, home-manager, ... }@inputs: {
nixosConfigurations = {
my-nixos-machine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; # Assumes a standard x86 CPU
specialArgs = {
inherit inputs; # this passes down the inputs
};
modules = [
./configuration.nix
lanzaboote.nixosModules.lanzaboote
./modules/lanza.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.mycoolusername = import ./home;
home-manager.extraSpecialArgs = inputs; # from the passed down input, we can pass these as args to `home.nix`
}
];
};
};
};
}

Then we'll create our home directory, along with the module's we'll need:

sudo mkdir /etc/nixos/home
❯ sudo mkdir /etc/nixos/home/modules
❯ sudo mkdir /etc/nixos/home/modules/shell
❯ sudo touch /etc/nixos/home/default.nix
❯ sudo touch /etc/nixos/home/home-configuration.nix
❯ sudo touch /etc/nixos/home/modules/shell/default.nix
❯ sudo touch /etc/nixos/home/modules/shell/zsh.nix
❯ sudo touch /etc/nixos/home/modules/shell/starship.nix
❯ sudo touch /etc/nixos/home/modules/shell/git-gh.nix

The above is pretty verbose, so let's walk through each module.

[INFO]: Primer on default.nix

You probably noticed in our flake config this line:

{
home-manager.users.mycoolusername = import ./home;
}

As well as the two default.nix modules we created in ./home and ./home/modules/shell. So, what does it do? Well, it's a module that primarily serves to imports other modules. As an added bonus, the way to import a default.nix module, is through referencing it's parent directory, such as home or home/modules/shell.

This is a feature that becomes more useful, when you have modules that shouldn't be imported by default. For example, you might decide to create modules for AMD GPUs (modules/amd.nix) and Nvidia GPUs (modules/nvidia.nix). Unless you want to install both sets of drivers across all your machines, you'll probably want to manually import those on a system by system basis via the flake.nix, rather than importing both via a default.nix in a modules/gpu directory.

home/default.nix

Continuing, let's start with home/default.nix. Using a superuser privileged editor, you'll want to assemble home/default.nix like this:

{
imports = [
./home-configuration.nix
./modules/shell
];
}

Inline with the spirit of default.nix, this simply imports a file called ./home-configuration.nix (which we'll configure next), and another default.nix which declares the default imports for the shell module.

home/home-configuration.nix

And we can assemble home-configuration.nix like so:

{
home.username = "mycoolusername";
home.homeDirectory = "/home/mycoolusername";
home.stateVersion = "24.05";
programs.home-manager.enable = true;
}

The above configures Home Manager to act on mycoolusername's home directory, and enables itself.

home/modules/shell/default.nix

Another default.nix module, it'll import modules used primarily within the terminal shell.

{
imports = [
./zsh.nix
./starship.nix
./git-gh.nix
];
}

home/modules/shell/zsh.nix

{config, pkgs, ...}: {
home.packages = with pkgs; [
pfetch-rs
];
programs.zsh = {
enable = true;
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
history = {
size = 10000;
path = "${config.xdg.dataHome}/zsh/history";
};
initExtra = '' case $(tty) in (/dev/tty[1-9]);; (*) eval pfetch;; esac '';
};
}

The above configures zsh (which we declared and enabled as the default shell from configuration.nix), with some common plugins, a decent history size, and even runs pfetch every time you open a fresh shell. The little if/case statement in initExtra ensures pfetch doesn't run in a bare tty (which, I'm sure you're very familiar with if you followed Part 01: From Zero to NixOS all the way through).

home/modules/shell/starship.nix

{
programs.starship = {
enable = true;
settings = {
add_newline = false;
nix_shell = {
symbol = " ";
};
git_status = {
format = "([\$all_status$ahead_behind\]($style) )";
modified = "󰇂 ";
ahead = " $\{count}";
conflicted = "󱚝 ";
behind = " $\{count}";
diverged = "󰹺  $\{ahead_count}  $\{behind_count}";
up_to_date = " ";
deleted = "󰮉 ";
untracked = "󱚠 ";
stashed = " ";
staged = " ";
style = "bold blue";
};
};
};
}

The above is my personal Starship Prompt configuration, and I hope it serves you well in your version control adventures (if you choose to implement this module).

home/modules/shell/git-gh.nix

{
programs.git = {
enable = true;
userName = "mycoolgithubusername";
userEmail = "mycoolgithubacctemail@xample.com";
extraConfig = {
safe.directory = "/etc/nixos";
core.editor = "nvim"; # We didn't cover this, but I trust you can setup neovim on your own, perhaps via nixvim ;3 (https://nix-community.github.io/nixvim/)
};
};
programs.gh = {
enable = true;
};
}

The Final Step: nixos-rebuild switch

With all the modules configured, it's time to run # nixos-rebuild switch, or # nixos-rebuild boot if you want to cut down on the misc Nix profile generations (only drawback is you have to reboot). If everything compiled successfully, it's now time to bask in the triumph of having accomplished a LOT today, complete with a base Home Manager configuration as icing on the cake.

[INFO]: Cleaning up old Nix generations

Eventually, you'll want to clean out old Nix generations (created after each # nixos-rebuild switch) since they can really start piling up, and monopololize your diskspace. You can do so (mildly dangerously) with # nix-collect-garbage -d to delete all old generations of profiles. Or, for a much safer garbage collection, you can run # nix-collect-garbage --delete-older-than <time_period> to remove things older than, say, 30 days, using 30d as the time period. 47

The former garbage collection command can also become très dangereaux, if your last config and current config are both broken in a way that you can no longer access the TTY, since all possible escape hatches (working generations) would be removed. This is probably one of the only ways you can truly brick a NixOS install.

In the event of such a bricked install, you can use the USB with the Minimal NixOS ISO you used earlier to attempt a repair. Once booted, just mount your drives, run luksOpen to unlock them, edit the borked config files, then follow the instructions on the NixOS Wiki page for Change root to run nixos-enter, so you can run nixos-rebuild switch.

Discussion

Welcome to the end of a very long blog post guide. If you completed the above, you have earned a very well deserved break and celebration of your accomplishments today (or however long it took to get through this guide).

What you've done is no small feat. In reaching the end of this guide, you've installed NixOS with Full Disk Encryption, enabled secure boot with lanzaboote, enabled your devices TPM2 chip to decrypt your LUKS devices during boot with systemd-cryptenroller, and finally configured a slick zsh shell with Starship Prompt, some plugins, and configured git and gh, ready to turn your /etc/nixos folder into a repo48. Pretty rad!

If you knew nothing about Nix when you first got here, well, I hope you were able to get the gist of it after reaching the end here, and picked up a few things along the way.

I also hope that in being your guide on this journey through the major circles of Nix, that you were able to overcome some of the steep learning curve Nix/NixOS is infamously known for. If however, I failed to help with that... Welp, I guess I'll just have to update this post in accordance to your feedback, won't I?

In any event, I hope you learned something, and if you were ever a chronic distrohopper1 like I was, I hope NixOS brings you the same peace of mind it brought me, serving as the final distro following a very long series of hops.

Finally, It's just me writing/editing these articles, and they're quite long. As such, it's entirely possible I've accidentally a word or semi-colon or two. Should you find any errors in this guide, I'd really appreciate it if you either left a comment below explaining what's wrong, opened an issue on this site's repo, or submitted a pull request to correct this article directly. Thank you.

PS, I'd love to hear your thoughts! Feel free to drop a comment or question below, or reach out to me on social media to let me know what you thought of this article!

Footnotes

  1. Urban Dictionary: distrohopper [Internet]. Urban Dictionary. [cited 2025 Jan 4]. Available from: https://www.urbandictionary.com/define.php?term=distrohopper 2

  2. Full Disk Encryption - NixOS Wiki [Internet]. NixOS Wiki. 2024 [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Full_Disk_Encryption

  3. Cîmpianu D. Installing NixOS with Flakes and LVM on LUKS [Internet]. Jadarma’s Blog. 2024 [cited 2025 Jan 3]. Available from: https://jadarma.github.io/blog/posts/2024/08/installing-nixos-with-flakes-and-lvm-on-luks/ 2

  4. Schulke M. Installing NixOS with Full Disk Encryption [Internet]. Gist. 2021 [cited 2025 Jan 3]. Available from: https://gist.github.com/mara-schulke/43e2632ce73d94028f50f438037c1578

  5. Kovari BB. A Modern and Secure Desktop Setup - Guides [Internet]. NixOS Discourse. 2024 [cited 2025 Jan 3]. Available from: https://discourse.nixos.org/t/a-modern-and-secure-desktop-setup/41154

  6. Vermaat M. Installation of NixOS with encrypted root [Internet]. Gist. 2016 [cited 2025 Jan 3]. Available from: https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134

  7. Hong SZ. How to Install NixOS With Full Disk Encryption (FDE) using LUKS2, Detached LUKS Header, and A Separate Boot Partition on an USB/MicroSD Card [Internet]. Shen’s Essays. 2021 [cited 2025 Jan 3]. Available from: https://shen.hong.io/installing-nixos-with-encrypted-root-partition-and-seperate-boot-partition/

  8. Stecklina J, Lahfa R, nikstur. lanzaboote/docs/QUICK_START [Internet]. GitHub. 2024 [cited 2025 Jan 3]. Available from: https://github.com/nix-community/lanzaboote/blob/master/docs/QUICK_START.md 2

  9. NixOS contributors. NixOS Manual [Internet]. Nix & NixOS | Declarative builds and deployments. [cited 2025 Jan 3]. Available from: https://nixos.org/manual/nixos/unstable/ 2

  10. NixOS contributors. How Nix Works | Nix & NixOS [Internet]. [cited 2025 Jan 3]. Available from: https://nixos.org/guides/how-nix-works/ 2 3

  11. Packaging/Binaries - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Packaging/Binaries 2

  12. Thornhill JT and J. Nix-ld: A clean solution for issues with pre-compiled executables on NixOS [Internet]. 2022 [cited 2025 Jan 3]. Available from: https://blog.thalheim.io/2022/12/31/nix-ld-a-clean-solution-for-issues-with-pre-compiled-executables-on-nixos/

  13. Steam - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Steam#FHS_environment_only

  14. Installation guide - ArchWiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.archlinux.org/title/Installation_guide

  15. Gentoo AMD64 Handbook - Gentoo wiki [Internet]. [cited 2025 Jan 4]. Available from: https://wiki.gentoo.org/wiki/Handbook:AMD64

  16. systemd-cryptenroll - ArchWiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.archlinux.org/title/Systemd-cryptenroll 2

  17. Download | Nix & NixOS [Internet]. [cited 2025 Jan 3]. Available from: https://nixos.org/download/#nixos-iso

  18. encryption - How to secure erase files and folders? - Information Security Stack Exchange [Internet]. [cited 2025 Jan 3]. Available from: https://security.stackexchange.com/questions/175968/how-to-secure-erase-files-and-folders

  19. cryptsetup(8) - Linux man page [Internet]. [cited 2025 Jan 3]. Available from: https://linux.die.net/man/8/cryptsetup

  20. Upgrading and backing up your LUKS header - Infrastructure and Application Security [Internet]. [cited 2025 Jan 4]. Available from: https://krvtz.net/posts/upgrading-and-backing-up-your-luks-header.html

  21. Szelei T. I have 16GB RAM. Do I need 32GB swap? - Ask Ubuntu [Internet]. Ask Ubuntu. 2018 [cited 2025 Jan 3]. Available from: https://askubuntu.com/questions/49109/i-have-16gb-ram-do-i-need-32gb-swap

  22. FAT - OSDev Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.osdev.org/FAT#FAT_32_2

  23. Global Structures — The Linux Kernel documentation [Internet]. [cited 2025 Jan 3]. Available from: https://docs.kernel.org/filesystems/ext4/globals.html#super-block

  24. swaplabel(8) - Linux manual page [Internet]. [cited 2025 Jan 3]. Available from: https://man7.org/linux/man-pages/man8/swaplabel.8.html

  25. Yin R. Modularize Your NixOS Configuration [Internet]. NixOS & Flakes Book. 2024 [cited 2025 Jan 3]. Available from: https://nixos-and-flakes.thiscute.world/nixos-with-flakes/modularize-the-configuration#modularize-your-nixos-configuration

  26. Perkins L. the-nix-way/dev-templates [Internet]. The Nix Way; 2024 [cited 2025 Jan 3]. Available from: https://github.com/the-nix-way/dev-templates

  27. Munroe R. Incident [Internet]. xkcd. [cited 2025 Jan 3]. Available from: https://xkcd.com/838/

  28. GNOME - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/GNOME

  29. KDE - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/KDE

  30. Sway - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Sway

  31. PipeWire - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/PipeWire#Bluetooth_Configuration

  32. PipeWire - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/PipeWire#Bluetooth_Configuration

  33. Bluetooth - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Bluetooth

  34. Printing - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Printing

  35. Zsh - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Zsh

  36. fish - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Fish

  37. Nushell - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Nushell

  38. Starship: Cross-Shell Prompt [Internet]. [cited 2025 Jan 3]. Available from: https://starship.rs/

  39. zsh-users/zsh-syntax-highlighting [Internet]. zsh-users; 2025 [cited 2025 Jan 3]. Available from: https://github.com/zsh-users/zsh-syntax-highlighting

  40. Yin R. NixOS & Flakes Book [Internet]. NixOS & Flakes Book. 2024 [cited 2025 Jan 3]. Available from: https://nixos-and-flakes.thiscute.world/

  41. Nix channels - NixOS Wiki [Internet]. [cited 2025 Jan 4]. Available from: https://nixos.wiki/wiki/Nix_channels

  42. systemd-cryptenroll [Internet]. [cited 2025 Jan 4]. Available from: https://www.freedesktop.org/software/systemd/man/latest/systemd-cryptenroll.html

  43. Home Manager Manual [Internet]. [cited 2025 Jan 3]. Available from: https://nix-community.github.io/home-manager/

  44. Issues · nix-community/home-manager [Internet]. GitHub. [cited 2025 Jan 3]. Available from: https://github.com/nix-community/home-manager

  45. Nix Flakes: NixOS module - Home Manager Manual [Internet]. [cited 2025 Jan 3]. Available from: https://nix-community.github.io/home-manager/index.xhtml#sec-flakes-nixos-module

  46. nix-collect-garbage - Nix Reference Manual [Internet]. [cited 2025 Jan 4]. Available from: https://nix.dev/manual/nix/2.25/command-ref/nix-collect-garbage.html

  47. Yin R. Other Useful Tips [Internet]. NixOS & Flakes Book. 2024 [cited 2025 Jan 3]. Available from: https://nixos-and-flakes.thiscute.world/nixos-with-flakes/other-useful-tips#managing-the-configuration-with-git