Running a Raspberry Pi 4 with NixOS

For quite some time I've been wanting to run a small homelab with NixOS. I don't host much services myself, however I feel that I can have a lot of fun (and learn a bit) by maintaining my own server. All the services I run on the Cloud™ (Matrix Dendrite and a Nix Binary Cache) could be running on a Raspberry Pi inside my drawer. So that be it!

A picture of Raspberry Pi inside an Argon One case and a Keychron K2V2 keyboard on the background

Setup

At the time of writing my setup looks like this:

  • Case Argon ONE M.2
  • KingSpec SSD M.2 SATA - 512GB
  • Random Flash Drive - 8GB (you can also use a SD Card)
  • Raspberry Pi 4 - 8GB

Flashing

Download the NixOS aarch64 image. Personally I went with the unstable branch as I like to live dangerously but you can choose other versions if you want to. After that you just need to dd it to your flash drive and boot it:

$ sudo dd if=nixos.img of=/dev/sdX bs=4096 conv=fsync status=progress

Notes:

  • Don't forget to extract the image before flashing it.
  • If using the Argon One M.2 case, don't boot the USB Drive with your SSD connected. Otherwise your raspberry will try to boot from the SSD and not your Flash Drive/SD Card.

Formatting

You can actually follow the NixOS Manual to partition your hard drive. However I've written a script to help me do this:

# replace /dev/sda with your SSD
export FMT_DISK=/dev/sda

wipefs -a $FMT_DISK

export DISK=/dev/disk/by-id/ata*

parted $FMT_DISK -- mklabel msdos
parted $FMT_DISK -- mkpart primary fat32 0MiB 512MiB # $DISK-part1 is /boot
parted $FMT_DISK -- mkpart primary 512MiB -4GiB # $DISK-part2 is the ext4 partition
parted $FMT_DISK -- mkpart primary linux-swap -4GiB 100% # Swap

mkfs.ext4 -L nixos $DISK-part2
mount $DISK-part2 /mnt

mkfs.vfat -F32 $DISK-part1
mkdir -p /mnt/boot
mount $DISK-part1 /mnt/boot

NixOS Configuration

In order to boot correctly, you need to define some boot options1:

{
  boot = {
    initrd.availableKernelModules = [ "usbhid" "usb_storage" ];
    kernelPackages = pkgs.linuxPackages_rpi4;
    kernelParams = [
      "8250.nr_uarts=1"
      "cma=128M"
      "console=tty1"
      "console=ttyAMA0,115200"
    ];

    loader = {
      raspberryPi = {
        enable = true;
        version = 4;
      };

      grub.enable = false;
      generic-extlinux-compatible.enable = true;
    };
  };

  hardware.enableRedistributableFirmware = true;
}

Boot firmware

The installer disk has a partition containing the necessary firmwares to boot (it was on /dev/sda1/ for me). Just copy it to your boot partition.

mkdir /firmware
mount /dev/sda1 /firmware
cp /firmware/* /mnt/boot

Installing

With Channels

The only step left is to install the system:

nixos-install --root /mnt

With Flakes

Another way to install it is to make use of Nix Flakes. This way we can ensure that our build is completely reproducible and/or running the same software version as the other machines.

This is a rather simple process if you already have a repo configured with your NixOS configurations. First, I need a shell with git and a Nix version that supports the experimental Flakes commands.

nix-shell -p git nixUnstable

After that I just clone my repository, copy the hardware-configuration.nix file over and install the system.

# clone the repository
git clone https://git.sr.ht/~glorifiedgluer/dotfiles
cd dotfiles

# copy hardware-configuration.nix
cp /mnt/etc/nixos/hardware-configuration.nix hosts/rpi4/

# install the system
nixos-install --flake .#rpi4