Installing NixOS on a ThinkPad T480s (with encrypted ZFS)

I set up my ThinkPad T480S to run NixOS with native ZFS encryption and a swap partition encrypted with a new random key every boot. Here's how.

Installing NixOS on a ThinkPad T480s (with encrypted ZFS)

I've been running NixOS on my ThinkPad T480s for about 18 months now and when i initally installed it i used the standard installer which used LUKS encryption and ext4 for the filesystem.

All my other Linux systems (apart from the server hosting this blog) use ZFS, so i want to use ZFS on this ThinkPad too. ZFS has its own native encryption so i can ditch LUKS and use that instead.

I'm going to enable Zram support for swap but on the off chance i need more i'm going to create a small swap partition. I don't use hibernation on my laptops so i'll set it up so that the swap partition uses a new random encryption key for every boot.

Partitioning the drive and creating the Zpool

Boot from the NixOS installer and open a terminal. Switch to the root user:

sudo su -

Partition the drive with 1GB EFI, 4GB Swap and rest for ZFS:

parted /dev/nvme0n1 -- mklabel gpt
parted /dev/nvme0n1 -- mkpart ESP fat32 1MB 1GB
parted /dev/nvme0n1 -- mkpart zfs "" 1GB -4GB
parted /dev/nvme0n1 -- mkpart swap linux-swap -4GB 100%
parted /dev/nvme0n1 -- set 1 esp on

Create the encrypted Zpool (change tank to the desired name):

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 \
tank /dev/nvme0n1p2

Enter the desired encryption passphrase (and again for confirmation).

Create the basic datasets for installation:

zfs create -o mountpoint=legacy tank/root
zfs create -o mountpoint=legacy tank/nix
zfs create -o mountpoint=legacy tank/var
zfs create -o mountpoint=legacy tank/home

Create mountpoints and mount the datasets:

mount -t zfs tank/root /mnt
mkdir /mnt/nix /mnt/var /mnt/home /mnt/boot
mount -t zfs tank/nix /mnt/nix
mount -t zfs tank/var /mnt/var
mount -t zfs tank/home /mnt/home

Format and mount the boot partition:

mkfs.fat -F 32 -n boot /dev/nvme0n1p1
mount -o umask=077 /dev/disk/by-label/boot /mnt/boot

Format and enable the Swap partition:

mkswap -L swap /dev/nvme0n1p3
swapon /dev/nvme0n1p3

Create the hardware-configuration.nix and configuration.nix files:

nixos-generate-config --no-filesystems --root /mnt

Replace the /mnt/etc/nixos/configuration.nix file with the following:

{ config, pkgs, ... }:

{
  imports =
    [ # Include tweaks from NixOS-Hardware.
      <nixos-hardware/lenovo/thinkpad/t480s>
      # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  # Bootloader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;
  boot.supportedFilesystems = [ "zfs" ];
  boot.initrd.systemd.enable = true;

  fileSystems."/" = {
    device = "tank/root";
    fsType = "zfs";
    };
  fileSystems."/nix" = {
    device = "tank/nix";
    fsType = "zfs";
    };
  fileSystems."/var" = {
    device = "tank/var";
    fsType = "zfs";
    };
  fileSystems."/home" = {
    device = "tank/home";
    fsType = "zfs";
    };

  fileSystems."/boot" = {
    device = "/dev/disk/by-uuid/02CA-3441";
    fsType = "vfat";
    options = [ "fmask=0077" "dmask=0077" ];
    };

  swapDevices = [
    {
      device = "/dev/disk/by-partuuid/436dad18-5d2b-49da-a549-b4234f9bd7e0";
      randomEncryption.enable = true; 
    }
  ];

  zramSwap = {
    enable = true;
    algorithm = "zstd";
  };

  # Networking
  networking = {
    hostId = "4b3c47ff";
    hostName = "orange";
    nftables.enable = true;
    firewall.enable = false;
    networkmanager.enable = true;
  };

  # System Settings.
  system.stateVersion = "24.11";
  time.timeZone = "Europe/London";
  console.keyMap = "uk";
  i18n.defaultLocale = "en_GB.UTF-8";
  i18n.extraLocaleSettings = {
    LC_ADDRESS = "en_GB.UTF-8";
    LC_IDENTIFICATION = "en_GB.UTF-8";
    LC_MEASUREMENT = "en_GB.UTF-8";
    LC_MONETARY = "en_GB.UTF-8";
    LC_NAME = "en_GB.UTF-8";
    LC_NUMERIC = "en_GB.UTF-8";
    LC_PAPER = "en_GB.UTF-8";
    LC_TELEPHONE = "en_GB.UTF-8";
    LC_TIME = "en_GB.UTF-8";
  };

# Services
  services = {
    libinput.enable = true; # Libinput
    fwupd.enable = true; # LVFS Firmware updates
    printing.enable = true; # CUPS
    flatpak.enable = true; # Flatpak
    tailscale.enable = true; # Tailscale
    # ZFS
    zfs = {
      autoScrub.enable = true;
      trim.enable = true;
    };
    # OpenSSH
    openssh = {
      enable = true;
      settings = {
        PasswordAuthentication = false;
        KbdInteractiveAuthentication = false;
        PermitRootLogin = "no";
      };
    };
  };

  # Enable Wayland, setup Gnome, etc
  services.xserver = {
    enable = true;
    xkb = {
      layout = "gb";
      variant = "";
    };
    displayManager.gdm.enable = true;
    desktopManager.gnome.enable = true;
    excludePackages = [
      pkgs.xterm
    ];
  };

  # Enable sound with pipewire.
  hardware.pulseaudio.enable = false;
  security.rtkit.enable = true;
  services.pipewire = {
    enable = true;
    alsa.enable = true;
    alsa.support32Bit = true;
    pulse.enable = true;
    jack.enable = true;
  };

  # Install some additional packages
  environment.systemPackages = with pkgs; [
    zip
    unzip
    wget
    lm_sensors
    whois
    dig
    calibre
    gnupg
    dino
    strawberry-qt6
    pika-backup
    parabolic
    tuba
    localsend
    # Gaming
    duckstation
    pcsx2
    simple64
    dolphin-emu
    caprice32
    # Gnome Packages
    gnome-tweaks
    gnome-software
    gnomeExtensions.tailscale-qs
    gnomeExtensions.alphabetical-app-grid
    shotwell
    celluloid
  ];

  # Exclude some default Gnome packages
  environment.gnome.excludePackages = with pkgs; [
    geary
    gnome-clocks
    yelp
    gnome-initial-setup
    totem
    gnome-music
    gnome-weather
    gnome-tour
    gnome-photos
  ];

  # Enable program features
  programs = {
    firefox = {
      enable = true;
      languagePacks = [ "en-GB" ];
    };
    chromium = {
      enable = true;
      extensions = [
        "ponfpcnoihfmfllpaingbgckeeldkhle" # Enhancer for YouTube
        "cjpalhdlnbpafiamejdnhcphjbkeiagm" # uBlock Origin
        "aapbdbdomjkkjkaonfhkkikfgjllcleb" # Google Translate
      ];
    };
    thunderbird.enable = true;
  };

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.USERNAME = {
    isNormalUser = true;
    description = "FIRSTNAME LASTNAME";
    extraGroups = [ "networkmanager" "wheel" ];
    openssh.authorizedKeys.keys = [
      "ssh-ed25519 AAAAC3..."
      "ssh-ed25519 AAAAC3..."
    ];
  };
}

Edit the above to suit.

The UUID for /boot and the partition UUID for swap will definitely need to changed.

The /boot UUID can be found by running ls -l /dev/disk/by-uuid/ and looking for the UUID pointing to ../../nvme0n1p1.

The swap partition UUID can be found by running ls -l /dev/disk/by-partuuid/ and looking for the UUID pointing to ../../nvme0n1p3.

The partition UUID for swap is needed because the standard filesystem UUID will change each boot.

Installation

The configuration.nix file above pulls in hardware tweaks for the T480s from the NixOS-Hardware project. The channel needs to be added for it:

nix-channel --add https://github.com/NixOS/nixos-hardware/archive/master.tar.gz nixos-hardware
nix-channel --update

Then install NixOS

nixos-install

Set the root password after installation.

A password for the user(s) defined in configuration.nix can be set by running:

nixos-enter --root /mnt -c 'passwd USERNAME'

Reboot the system and remove installation media.

Congratulations the ThinkPad T480s is now running NixOS with native ZFS encryption and random-key encrypted swap!