NixOS Demo Script
03/27/2024 - ~5 Minutes
Install NixOS via the graphical installer
Nothing really special; just remember to select Allow Non-Free near the end of the (Calamares) installation wizard. It will be easier if the user created is named “demo”, otherwise there is a deviation from the script later on. Choose the graphical desktop you’d like to use.
Add a new package to the
Since the default installation does not include vim, let’s use nix-shell -p vim to temporarily make it available.
Add the google-chrome package to the user’s package list (sudo vim /etc/nixos/configuration.nix).
While you’re in there, rename the system via networking.hostname = "demo"; in the config.
Also, it is important to add the settings that enable flakes (‘cause we’re getting there shortly); add nix.settings.experimental-features = [ "nix-command" "flakes" ]; to the bottom of the configuration.nix file.
Then perform a sudo nixos-rebuild switch. Show it running!
You will have to reboot to see the new hostname, and you should since we’ll use it below….
Exit the nix-shell and show that vim goes away from the user environment; remind everyone that the package and its dependencies still exist in /nix/store.
A quick summary of this step:
nix-shell -p vim- set hostname to demo by modifying existing declaration to read
networking.hostname = "demo";in the configuration.nix file - add
google-chrometo the user’s package list in the configuration.nix file - add
nix.settings.experimental-features = [ "flakes" "nix-command" ];to the bottom of the configuration.nix file exitnixos-rebuild switch- [optional]
reboot
Get Flakey!
Change directory into /etc/nixos and run sudo nix flake init to create a new instance of a basic flake template. Everything in the inputs section is good; the entirety of the outputs section can be replaced with the following:
nixosConfigurations.demo = nixpkgs.lib.nixosSystem { modules = [ ./configuration.nix ]; };
In the above, the demo token represents the hostname (which we changed from nixos to demo in the previous section).
Now, run sudo nixos-rebuild boot and a lot will happen!
If you have not yet rebooted, be sure to add --flake .#demo to override the hostname to flake configuration mapping.
We’re using nixos-rebuild boot since the drastic level of change implies we should reboot (likely an update kernel, for instance).
- The flake will lock in the current git hash of the nixos-unstable branch, and download the nixpkgs metadata for that revision
- All packages will be updated to the current unstable version
- The flake.lock file will be created to preserve this version of the system.
Now would be a good time to show off the lock file and display how it “follows” the nixos-unstable branch, but “locks” the particular rev.
Might be a good idea to reboot now (since our changes are waiting for us in the next generation).
There’s no place like Home (Manager)
After this big reboot, login as the unprivileged user. Let’s install home-manager! I chose the standalone configuration to underscore the distinction between user configuration and system configuration. There is another way (home-manager as a nixos module) which is left as an exercise for the reader.
As the user, we first need to add a nix-channel. This is something that happened (for the system user) behind the scenes during the install. It is also something that will be quickly surplanted by the flake we will create. Nonetheless
nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
nix-channel --update
For whatever reason (still haven’t quite sorted this out, honestly), you have to log out and back in again. Something needs to be sourced into the environment, but I haven’t found it.
Once you’re back in you can install home-manager with the following incantation
nix-shell '<home-manager>' -A install
This actually creates our first home-manager generation.
It also makes the home-manager tool generally available to us.
From now on out, new generations of home-manager can be created with home-manager switch.
Customizing our user environment
Though the magic of nix, we can configure our user environment, and be reasonable isolated from the system environment.
Let’s add and configure vim so we have access to it in our user environment.
First let’s do our nix-shell -p vim trick from earlier so we don’t have to fumble through using nano.
Then let’s add the following into our home.nix file via vim ~/.config/home-manager/home.nix
programs.vim = {
enable = true;
settings = {
expandtab = true;
tabstop = 2;
number = true;
relativenumber = true;
shiftwidth = 2;
};
};
Without even needing to add the package to the list, we will have vim and it will be configured to our (well, my) liking.
A quick home-manager switch will make this a reality.
A more complicated Flake for Home-Manager
The home-manager flake is more complicated than the nixos flake, because home-manager has multiple inputs (home-manager and nixpkgs), and we want to keep the two closely tied to each other.
We can start with the template, but here’s the full flake.nix file after modifications… or you can just write if from scratch using vim
The occurrence of “demo” below corresponds to the presumed username demo. If you have created a different unprivileged user, then replace the existing token with your new name.
{
description = "Demo Flake for Home-Manager (Standalone)";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { system = "${system}"; config.allowUnfree = true; };
in {
homeConfigurations."demo" = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
modules = [ ./home.nix ];
};
};
}
As I understand it, the follows line indicates the the nixpkgs supporting home-manager should follow the main nixpkages input (e.g. nixos-unstable). Again as I understand it, the is designed primarily to reduce system size by not duplicating packages that differ ever so slightly. However, Nix is fully capable of supporting (successfully) these inputs being disconnected. Any package used by one will pull in the correct versions of its dependencies, while the same package (different version) included in by the other input will pull its dependencies.
Regardless, now is the time to implement!
home-manager switch
Watch all the fun!
Additional Materials/Content
The slides are available online.
NixOS Configuration Search/Wiki
Home Manager Options Search
NixOS
03/12/2024 - ~12 Minutes
Introduction
Get started with
NixOS
. NixOS is a unique Linux Distribution. Traditional Linux distros install a single version
of a library or an application into the accepted filesystem hierarchy (e.g. /bin, /lib, /usr/bin, /usr/lib, etc.). NixOS uses the
nix package manager to install everything into the /nix/store, and then creates links to interconnect the currently chosen packages.
This allows for an amazing rollback option, since every change to the build creates an entirely new configuration, and old configurations can be left on the system for as long as you’d like. This has some throwbacks to Microsoft’s System Restore Points (the major difference being that nix seems to actually work).
NixOS GNOME Installer
Download the installer from here . This is a traditional Calamares installer running on a GNOME Live-CD. There is one caveat, if you are using a laptop: The installer will automatically launch before you have a chance to establish wi-fi connectivity; it will complain, not allowing you to proceed, but will never detect the presence of the internet after that. The installer will need to be stopped and restarted after wi-fi is connected. Sorry about that.
There is also a KDE-based Live-CD which is identical, aside from the underlying desktop manager.
A More Complicated Install
Download the minimal installer from here .
As a recovering Arch user, this is where I’m most comfortable because here I have actual control over the disk layout supporting my installation. I have options not available in the Calamares installer. The Calamares installer will offer the option of LUKS encryption for your files, but will only be able to create a single ext4 filesystem on the decrypted device. There is no ability to use LVM (either on or under LUKS), or to use btrfs to supply volumes on top of LUKS, or to do something truly nutty like ZFS.
Unlike Arch, the installer throws you to a prompt as an unprivleged user! First things first, sudo su -. You can’t get anything
useful done unless you are root!
Just like Arch, the first challenge is getting the installer online with commandline only tools. The method of choice here is
wpa_supplicant. You will need to start it up with systemctl start wpa_supplicant and then enter wpa_cli. Here instructions
are at a premium, but the process is straightforward.
- create a new network configuration with
add_network; this will return the network number (0 zero) which will be used below - identify the SSID you want with
set_network 0 ssid "TheNameOfYourSSID"(replacing TheNameOfYourSSID with the actual name of your SSID) - provide the wi-fi key with
set_network 0 psk "YourWifiPassphrase"(replacing YourWifiPassphrase with your actuall wi-fi passphrase) - then finish with
enable_network 0
Wait for the wpa_supplicant to report CONNECTED in the output, then you can exit the wpa_cli. At this point, the dhcp client should
take over and get you and address, gateway, nameserver, etc. There is sometimes a short delay while this is happening. Checking with
ip a should let you know when you have an address and are ready to proceed.
Below are my formulae for setting this up. All these examples use GRUB on UEFI, but should be just as easily implemented on legacy (BIOS) hardware by either creaing a 1M BIOS Boot partition in the GPT, or by using a DOS partition table. Additionally, all examples leverage a legacy SCSI/SATA drive (/dev/sda), instead of a more modern PCIe NVME drive (/dev/nvme0n1); the most notable difference is that the SCSI driver merely appends the partition number to the end of the device name (e.g. /dev/sda1) whereas the newer driver adds a “p” because the device name already ends in a digit (e.g. /dev/nvme0n1p1).
NB –> You might want to enable one of the networking packages before installing and rebooting, especially if you are using wi-fi. NetworkManager is the obvious choice if you are going to be using any of the graphical desktops (though you might want to familiarize yourself with nmcli, if you’re holding off on the actual desktop setup); the other option is to use wpa_supplicant, like the minimal installer did. But, by default, neither of these will be included in your installation without uncommenting a line from the generated configuration.nix file.
LUKS Encryption with Btrfs subvolumes
This scenario is the closest to what we can get with the Calamares installer. We will encrypt a single partition and put a single filesystem on the decrypted volume. However, we will make use to btrfs subvolumes to make our installation more interesting. So, the partitioning is pretty straightforward.
parted /dev/sda -- mktable gpt
parted /dev/sda -- mkpart fat32 1M 512M
parted /dev/sda -- set 1 esp on
parted /dev/sda -- mkpart linux 512M 100%
The next step is to create the LUKS encrypted volume (on /dev/sda2) and to get it decrypted to /dev/mapper/crypt. The use of the name crypt is arbitrary. You can use any name you’d like; just be sure to update any reference to crypt and /dev/mapper/crypt to use your name.
cryptsetup luksFormat /dev/sda2
cryptsetup open /dev/sda2 crypt
The formatting is a little special since we incorporate the creation of btrfs subvolumes into this step. To do this, we mount the btrfs filesystem and use the btrfs tooling to create the subvolumes; the we unmount the btrfs filesystem, so it can be remounted appropriately (using those subvolumes we created).
mkfs.fat -F 32 /dev/sda1
mkfs.btrfs /dev/mapper/crypt
mount /dev/mapper/crypt /mnt
for sv in @{,nix,var,home}; do
btrfs subv create /mnt/$sv
done
umount /mnt
Now it’s time to get everything mounted, and kick off the install.
mount -o subvol=@ /dev/mapper/crypt /mnt
for sv in {nix,var,home}; do
mount --mkdir -o subvol=@$sv /dev/mapper/crypt /mnt/$sv
done
mount --mkdir -o umask=0077 /dev/sda1 /mnt/boot
nixos-generate-config --root /mnt
Due to the simplicity of the configuration (read: its similarity to what the Calamares installer can do), there isn’t any special configuration to get this to work. All the magic for the LUKS encryption and the brtfs filesystems should be in /mnt/etc/nixos/hardware-configuration.nix.
With this configuration is in place, now is the time to confirm that you have enabled a networking package, and that you have done any additional NixOS configuration you’d like to do.
Then install (you will be prompted for a root password) and reboot. Then enjoy!
nixos-install
reboot
LVM on LUKS
This scenario involves creating a single encrypted partition, and using the decrypted device as a physical volume for LVM. In this configuration, we can have multiple filesystems (logical volumes) all tied to a single encrypted volume. Begin by partitioning the disk with
parted /dev/sda -- mktable gpt
parted /dev/sda -- mkpart fat32 1M 512M
parted /dev/sda -- set 1 esp on
parted /dev/sda -- mkpart linux 512M 100%
Now we can format the EFI partition (/dev/sda1) and use cryptsetup to encrypt /dev/sda2; follow the prompts for cryptsetup.
In the final line, we open the encrypted partition and create named device (crypt) which will be at /dev/mapper/crypt. This is
where we will begin our LVM work.
mkfs.fat -F 32 /dev/sda1
cryptsetup luksFormat /dev/sda2
cryptsetup open /dev/sda2 crypt
Now we can use /dev/mapper/crypt as a physical volume and create a volume group (named nixos, here).
pvcreate /dev/mapper/crypt
vgcreate nixos /dev/mapper/crypt
Now we can create some logical volumes, and format them. I chose ext4, but you can use any filesystem you see fit.
lvcreate -L 16G -n system nixos
lvcreate -L 32G -n nix nixos
lvcreate -L 8G -n var nixos
lvcreate -L 8G -n home nixos
for lv in {system,nix,var,home}; do
mkfs.ext4 /dev/nixos/$lv
done
Now comes the magical time to mount all the filesystems where we want them, and have NixOS generate an initial configuration. We will have to update the configuration to make it work, though.
mount /dev/nixos/system /mnt
for lv in {nix,var,home}; do
mount --mkdir /dev/nixos/$lv /mnt/$lv
done
mount --mkdir -o umask=0077 /dev/sda1 /mnt/boot
nixos-generate-config --root /mnt
Now, we need to add some carefully crafted nix-speak into our newly generated /mnt/etc/nixos/configuration.nix file.
We want to find the line that says boot.loader.systemd-boot.enable = true; and replace it with the block below.
This will tell it to use GRUB over UEFI (instead of systemd-boot), and provide some key configuration:
boot.loader.grub.enableCryptodisk = truewill allow grub to unlock the LUKS volumeboot.initrd.luks.devices.cryptidentifies the namecryptas the decrypted volume (you can change this value if you wish)boot.initrd.luks.devices.crypt.deviceidentifies encrypted volume we need to decrypt, here using its UUID (you must change this value to the correct UUID of the /dev/sda2 partition)boot.initrd.luks.devices.crypt.preLVM = true;tells the initrd to unlock the disk before scanning for LVM volume groupsboot.initrd.services.lvm.enable = true;tells the initrd to do the LVM magic
boot = {
loader = {
systemd-boot.enable = false;
grub = {
enable = true;
device = "nodev";
efiSupport = true;
enableCryptodisk = true;
};
efi = {
canTouchEfiVariables = true;
efiSysMountPoint = "/boot";
};
};
initrd = {
luks.devices.crypt = {
device = "/dev/disk/by-uuid/3348173a-e1dc-44ef-aeb8-cb263273719b";
preLVM = true;
};
services.lvm.enable = true;
};
};
With this configuration is in place, now is the time to confirm that you have enabled a networking package, and that you have done any additional NixOS configuration you’d like to do.
Then install (you will be prompted for a root password) and reboot. Then enjoy!
nixos-install
reboot
LUKS on LVM
This scheme is the logical reverse of LVM on LUKS. Here we setup the logical volume manager on an unencrypted partition and encrypt logical volumes, as needed. Included here is the use of Nix’s randomEncryption to create a SWAP partition (logical volume) that is dynamically encrypted with a random key at each boot, making swap data completely unavailable (read: safe) betwwen boots.
Partition and format the disk.
This differs from previous examples only in that we tagging /dev/sda2 as a physical volume for LVM using parted.
Of course, we still have to do the pvcreate step.
parted /dev/sda -- mklable gpt
parted /dev/sda -- mkpart fat32 1MiB 512MiB
parted /dev/sda -- set 1 esp on
parted /dev/sda -- mkpart 512MiB 100%
parted /dev/sda -- set 2 lvm on
mkfs.fat -F 32 /dev/sda1
pvcreate /dev/sda2
Setup the Logical Volume Manager. Create a Volume Group named (arbitrarily) nixos.
Then we’ll add a 32G Logical Volume named crypt for the OS and an 8G one named swap for the
encrypted swap.
vgcreate nixos /dev/sda2
lvcreate -L 32G -n crypt nixos
lvcreate -L 8G -n swap nixos
Now that we have a logical volume (/dev/nixos/crypt) we can use cryptsetup to, well, setup our encryption.
cryptsetup luksFormat /dev/nixos/crypt
cryptsetup open /dev/nixos/crypt system
Having created /dev/mapper/system, we can format (I’ll use ext4 for simplicity).
mkfs.ext4 /dev/mapper/system
mount /dev/mapper/system /mnt
mount --mkdir -o umask=0077 /dev/sda1 /mnt/boot
Now we can generate the initial configuration.
nixos-generate-config --root /mnt
The following extra configuration will need to go into /mnt/etc/nixos/configuration.nix, replacing the line
boot.loader.systemd-boot.enable = true;. Much of the explanation of this block appear in the LVM on LUKS section,
but I’ll highlight the one difference
boot.initrd.luks.devices.btrfs.preLVM = false;because we want LUKS to sort itself after we’ve processed all the LVM devices
Just like the LVM on LUKS configuration, you will need to use the correct UUID in the device line; but this time, it’s a little simpler.
boot = {
loader = {
systemd-boot.enable = false;
grub = {
enable = true;
device = "nodev";
efiSupport = true;
enableCryptodisk = true;
};
efi = {
canTouchEfiVariables = true;
efiSysMountPoint = "/boot";
};
};
initrd = {
luks.devices.system = {
device = "/dev/nixos/crypt";
preLVM = false;
};
services.lvm.enable = true;
};
};
Adding encrypted swap. This results in the same configuration that’s outlined in the ArchLinux Wiki section 5.6 _Configuring fstab and crypttab. Of course here it’s declarative, instead of procedural.
swapDevices = [
{ device = "/dev/nixos/swap";
randomEncryption = {
enable = true;
cipher = "aes-xts-plain64";
source = "/dev/urandom";
keySize = 512;
};
}
];
Encrypted ZFS
Using ZFS (with it’s internal encryption) allows the best of all three of the preceding methods, but leveraging only a single technology. That being said, ZFS on Linux is relatively niche; the understanding level is low and the number of examples is correspondingly small.
The initial partitioning should look familiar.
parted /dev/sda -- mklable gpt
parted /dev/sda -- mkpart fat32 1MiB 512MiB
parted /dev/sda -- set 1 esp on
parted /dev/sda -- mkpart 512MiB 100%
Now we can format the /boot partition and do the ZFS magic on the second partition to create a zpool named nixos. The first three -O
parameters can be omitted if you do not want encryption.
mkfs.fat -F 32 /dev/sda1
zpool create -O encryption=on -O keyformat=passphrase -O keylocation=prompt \
-O compression=on -O mountpoint=none -O xattr=sa -O acltype=posixacl \
-o ashift=12 -f nixos /dev/sda2
Now, we can create ZFS datasets within the zpool. Datasets are like the logical volumes of LVM (except they already have a filesystem on them).
for ds in {root,nix,var,home}; do
zfs create -o mountpoint=legacy nixos/$ds
done
Now these datasets can be mounted for the config generation. Also, mount the /boot partition.
mount -t zfs nixos/root /mnt
for ds in {nix,var,home}; do
mount --mkdir -t zfs nixos/$ds /mnt/$ds
done
mount --mkdir -o umask=0077 /dev/sda1 /mnt/boot
Now we can generate the initial config with
nixos-generate-config --root /mnt
And edit the config, once again replacing the boot.loader.systemd-boot.enable = true; line with the following.
boot.loader = {
systemd-boot.enable = false;
grub = {
enable = true;
zfsSupport = true;
efiSupport = true;
efiInstallAsRemovable = true;
device = "nodev";
};
efi = {
canTouchEfiVariables = false;
efiSysMountPoint = "/boot";
};
};
networking.hostId = "00000000";
The networking.hostId is used by ZFS and should be generated with the following incantation .
head -c4 /dev/urandom | od -A none -t x4
FWIW, I keep my networking.hostId in a separate networking configuration (.nix); it can go anywhere, but you have to have one.
A final word about swap space
Only one of the above examples included swap. I largely don’t go in for swap,
as I seem to have enough RAM on my systems these days for whatever I want to do.
Also, these days, all the cool kids are using compressed RAM swap. On Arch I used
the zram-generator package; on NixOS I use zramSwap.enable = true; in my configuration.nix file.
Feel free to add this in. It will nominally use 50% of your RAM as compressed swap.
Here’s a link to some additional information about
zram
.
Resources
- NixOS.org website.
- NixOS package and configuration database search .
- ArchLinux Wiki