How to Install NixOS with Full Disk Encryption + Secure Boot + Unlock LUKS via TPM2
Bonus feature: Intro to Home Manager
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:
- Install NixOS with Full Disk Encryption.
- Use a
flake.nix
to configure your NixOS system. - Install and configure
Lanzaboote
to enable secure boot on your machine. - Use
systemd-cryptenroll
to unlock your LUKS devices via your motherboard's TPM chip. - Use
home-manager
to configure your shell andgit
/gh
.
Part 01: From Zero to NixOS
Prerequisites
- A machine with a UEFI motherboard
- 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 enablesystemd-cryptenroll
to enroll your LUKS keys into the TPM chip.16) - A bootable USB (4 GB minimum) with the Minimal NixOS ISO17 loaded onto it
Booting the installer
- Repeatedly mash
F2
orF10
orDEL
or whichever key let’s you into your system’s UEFI/Bios. - Disable secure boot (if enabled).
- Disable the CSM/Legacy Support (if enabled/applicable).
- 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.
- Move the NixOS USB to the highest boot priority (if applicable)
- Save your changes, and boot into your NixOS USB.
- 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:
n
- (partition number; default)
enter
- (first sector: default)
enter
- (last sector)
+1G
- (remove signature if applicable)
Y
t
(change partition type)- (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:
n
- (partition number: default)
enter
- (first sector: default)
enter
- (last sector: default)
enter
- (remove signature if applicable)
Y
t
(change partition type)- (partition number: (1, 2, default 2))
enter
- (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.
-
Format the ESP partition:
# mkfs.fat -F 32 -n NIXBOOT /dev/nvme0n1p1
-
Format the Root LV:
# mkfs.ext4 -L NIXROOT /dev/mapper/lvmroot-root
-
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.
-
Mount the Root LV:
# mount /dev/disk/by-label/NIXROOT /mnt
-
Create the ESP Directory:
# mkdir /mnt/boot
-
Mount the ESP:
# mount -o umask=0077 /dev/disk/by-label/NIXBOOT /mnt/boot
-
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
toboot.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
totrue
.
# 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.nix
25 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; # optionalservices.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 configurationenvironment.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 managerprograms.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 recommendedsecurity.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 Bluetoothhardware.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 fish
36 or even nushell
37.
[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.
- Enable secure boot
- Install factory default keys (if they’re empty)
- Delete every key except the Forbidden Signature Database (dbx)
- This is typically the key at the bottom of the list
- 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-cryptenroll
42, 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
-
Urban Dictionary: distrohopper [Internet]. Urban Dictionary. [cited 2025 Jan 4]. Available from: https://www.urbandictionary.com/define.php?term=distrohopper ↩ ↩2
-
Full Disk Encryption - NixOS Wiki [Internet]. NixOS Wiki. 2024 [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Full_Disk_Encryption ↩
-
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
-
Schulke M. Installing NixOS with Full Disk Encryption [Internet]. Gist. 2021 [cited 2025 Jan 3]. Available from: https://gist.github.com/mara-schulke/43e2632ce73d94028f50f438037c1578 ↩
-
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 ↩
-
Vermaat M. Installation of NixOS with encrypted root [Internet]. Gist. 2016 [cited 2025 Jan 3]. Available from: https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134 ↩
-
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/ ↩
-
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
-
NixOS contributors. NixOS Manual [Internet]. Nix & NixOS | Declarative builds and deployments. [cited 2025 Jan 3]. Available from: https://nixos.org/manual/nixos/unstable/ ↩ ↩2
-
NixOS contributors. How Nix Works | Nix & NixOS [Internet]. [cited 2025 Jan 3]. Available from: https://nixos.org/guides/how-nix-works/ ↩ ↩2 ↩3
-
Packaging/Binaries - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Packaging/Binaries ↩ ↩2
-
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/ ↩
-
Steam - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Steam#FHS_environment_only ↩
-
Installation guide - ArchWiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.archlinux.org/title/Installation_guide ↩
-
Gentoo AMD64 Handbook - Gentoo wiki [Internet]. [cited 2025 Jan 4]. Available from: https://wiki.gentoo.org/wiki/Handbook:AMD64 ↩
-
systemd-cryptenroll - ArchWiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.archlinux.org/title/Systemd-cryptenroll ↩ ↩2
-
Download | Nix & NixOS [Internet]. [cited 2025 Jan 3]. Available from: https://nixos.org/download/#nixos-iso ↩
-
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 ↩
-
cryptsetup(8) - Linux man page [Internet]. [cited 2025 Jan 3]. Available from: https://linux.die.net/man/8/cryptsetup ↩
-
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 ↩
-
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 ↩
-
FAT - OSDev Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.osdev.org/FAT#FAT_32_2 ↩
-
Global Structures — The Linux Kernel documentation [Internet]. [cited 2025 Jan 3]. Available from: https://docs.kernel.org/filesystems/ext4/globals.html#super-block ↩
-
swaplabel(8) - Linux manual page [Internet]. [cited 2025 Jan 3]. Available from: https://man7.org/linux/man-pages/man8/swaplabel.8.html ↩
-
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 ↩
-
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 ↩
-
Munroe R. Incident [Internet]. xkcd. [cited 2025 Jan 3]. Available from: https://xkcd.com/838/ ↩
-
GNOME - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/GNOME ↩
-
KDE - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/KDE ↩
-
Sway - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Sway ↩
-
PipeWire - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/PipeWire#Bluetooth_Configuration ↩
-
PipeWire - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/PipeWire#Bluetooth_Configuration ↩
-
Bluetooth - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Bluetooth ↩
-
Printing - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Printing ↩
-
Zsh - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Zsh ↩
-
fish - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Fish ↩
-
Nushell - NixOS Wiki [Internet]. [cited 2025 Jan 3]. Available from: https://wiki.nixos.org/wiki/Nushell ↩
-
Starship: Cross-Shell Prompt [Internet]. [cited 2025 Jan 3]. Available from: https://starship.rs/ ↩
-
zsh-users/zsh-syntax-highlighting [Internet]. zsh-users; 2025 [cited 2025 Jan 3]. Available from: https://github.com/zsh-users/zsh-syntax-highlighting ↩
-
Yin R. NixOS & Flakes Book [Internet]. NixOS & Flakes Book. 2024 [cited 2025 Jan 3]. Available from: https://nixos-and-flakes.thiscute.world/ ↩
-
Nix channels - NixOS Wiki [Internet]. [cited 2025 Jan 4]. Available from: https://nixos.wiki/wiki/Nix_channels ↩
-
systemd-cryptenroll [Internet]. [cited 2025 Jan 4]. Available from: https://www.freedesktop.org/software/systemd/man/latest/systemd-cryptenroll.html ↩
-
Home Manager Manual [Internet]. [cited 2025 Jan 3]. Available from: https://nix-community.github.io/home-manager/ ↩
-
Snel P. Home Manager - Option Search [Internet]. [cited 2025 Jan 3]. Available from: https://home-manager-options.extranix.com/ ↩
-
Issues · nix-community/home-manager [Internet]. GitHub. [cited 2025 Jan 3]. Available from: https://github.com/nix-community/home-manager ↩
-
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 ↩
-
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 ↩
-
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 ↩