(Re-)installing Lunix

Warning

This is a guide written for me. As I say in my readme, please do not use Lunix in your own machine. It’s not meant for that. If you want to use NixOS, I strongly advise you learn Nix! If you don’t want to do that, I recommend Fedora :) That said, if you would like to use this as a reference to install NixOS with an existing NixOS config with impermanence set up, this might be helpful for you.

Pre-Installation

Install the minimal NixOS installation media to a USB Drive of your choice.

As this is happening, you should make sure all your git repos are pushed to remote, and any files you want to keep are backed up. This process will wipe your entire drive!

After this is done, reboot your system into your installation media. Since I use colemak, I always have to properly change my keyboard layout for the installation media:

\# loadkeys colemak

If you use wifi, make sure to run the proper commands to set it up:

\# systemctl start wpa_supplicant
\$ wpa_cli

Congrats! You have begun the installation process.

Formatting Your Drives

Note

This guide is heavily borrows from NotAShelf’s [Full Disk Encryption and Impermanence on NixOS] blogpost, as well as my own experience with impermanence. I would prefer you not use this, but if you do, please do not send any complaints to anyone BUT ME.

Before doing anything, set an easy $DISK variable to simplify matters―this is especially useful on an NVME.

DISK=/dev/nvme0nX # replace this with the name of the device you are using

The next step is to wipe your drive clean. Since we're assuming the disk is unencrypted beforehand, it's best to use a secure command like shred.

sudo shred -v -n 1 "$DISK"

Finally, we can start partitioning the drive. Firstly, setup the boot partition. I use 1GB, but you might want to use more depending on how many generations you might want to keep as well as other variables.

\# parted "$DISK" -- mklabel gpt
\# parted "$DISK" -- mkpart ESP fat32 1MiB 1GiB
\# parted "$DISK" -- set 1 boot on # assumes UEFI

\# mkfs.vfat -n BOOT "$DISK"p1 # On a non-nvme drive, this would be "$DISK"1

Next is swap. Both my hosts currently have 16GB, so I use 8GB of swap:

\# parted "$DISK" -- mkpart Swap linux-swap 1GiB 9GiB
\# mkswap -L SWAP "$DISK"p2 # On a non-nvme drive, this would be "$DISK"2
\# swapon "$DISK"p2

Keep in mind, we will be encrypting our swap space later, which disables hibernation.

Next, create and encrypt your primary partition:

\# parted "$DISK" -- mkpart primary 9GiB 100%
\# cryptsetup --verify-passphrase -v luksFormat "$DISK"p3
\# cryptsetup open "$DISK"p3 enc
\# mkfs.btrfs -L NIXOS /dev/mapper/enc

The first step is to mount your primary partition and create its BTRFS subvolumes.

\# mount -t btrfs /dev/mapper/enc /mnt

# First we create the subvolumes, those may differ as per your preferences
\# btrfs subvolume create /mnt/root
\# btrfs subvolume create /mnt/home
\# btrfs subvolume create /mnt/nix
\# btrfs subvolume create /mnt/persist # some people may choose to put /persist in /mnt/nix, I am not one of those people.
\# btrfs subvolume create /mnt/log

Now that we have our subvolumes, we can create a snapshot of our primary partition, used to rollback our system for impermanence.

\# btrfs subvolume snapshot -r /mnt/root /mnt/root-blank

# Make sure to unmount, otherwise nixos-rebuild will try to remove /mnt
# and fail
\# umount /mnt

Mounting Your Partitions

Once the subvolumes and the snapshot are all created, you can begin mounting your system. We also use the options noatime and zstd compression to optimize our drives.

# /
\# mount -o subvol=root,compress=zstd,noatime /dev/mapper/enc /mnt

# /home
\# mkdir /mnt/home
\# mount -o subvol=home,compress=zstd,noatime /dev/mapper/enc /mnt/home

# /nix
\# mkdir /mnt/nix
\# mount -o subvol=nix,compress=zstd,noatime /dev/mapper/enc /mnt/nix

# /persist
\# mkdir /mnt/persist
\# mount -o subvol=persist,compress=zstd,noatime /dev/mapper/enc /mnt/persist

# /var/log
\# mkdir -p /mnt/var/log
\# mount -o subvol=log,compress=zstd,noatime /dev/mapper/enc /mnt/var/log

# Do not forget to mount the boot partition!
\# mkdir /mnt/boot
\# mount "$DISK"p1 /mnt/boot # On a non-nvme drive this would be "$DISK"1

Installing NixOS

Once all your partitions have been mounted in the proper places, you can generate your NixOS hardware configuration that you will modify and use in your system:

\# nixos-generate-config --root /mnt

Then edit your hardware-configuration.nix so that it 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,
  modulesPath,
  ...
}: let
  inherit (lib.lists) singleton;
in {
  imports = [
    (modulesPath + "/installer/scan/not-detected.nix")
  ];

  boot = {
    initrd = {
      availableKernelModules = ["nvme" "xhci_pci" "thunderbolt" "usb_storage" "sd_mod"];
      kernelModules = [];
      luks.devices."enc".device = "/dev/disk/by-uuid/6f59b641-0730-4a1f-9e01-543932aaf303";
    };
    kernelModules = ["kvm-amd"];
    extraModulePackages = [];
  };

  fileSystems = {
    "/" = {
      device = "/dev/disk/by-uuid/c3da71cf-1c23-49c0-a15d-2cd43df8bebd";
      fsType = "btrfs";
      options = [
        "subvol=root"
        "compress=zstd"
        "noatime"
      ];
    };

    "/nix" = {
      device = "/dev/disk/by-uuid/c3da71cf-1c23-49c0-a15d-2cd43df8bebd";
      fsType = "btrfs";
      options = [
        "subvol=nix"
        "compress=zstd"
        "noatime"
      ];
    };

    "/persist" = {
      device = "/dev/disk/by-uuid/c3da71cf-1c23-49c0-a15d-2cd43df8bebd";
      fsType = "btrfs";
      neededForBoot = true; # <― This is important
      options = [
        "subvol=persist"
        "compress=zstd"
        "noatime"
      ];
    };

    "/var/log" = {
      device = "/dev/disk/by-uuid/c3da71cf-1c23-49c0-a15d-2cd43df8bebd";
      fsType = "btrfs";
      neededForBoot = true; # <― This is important
      options = [
        "subvol=log"
        "compress=zstd"
        "noatime"
      ];
    };

    "/home" = {
      device = "/dev/disk/by-uuid/c3da71cf-1c23-49c0-a15d-2cd43df8bebd";
      fsType = "btrfs";
      options = [
        "subvol=home"
        "compress=zstd"
        "noatime"
      ];
    };

    "/boot" = {
      device = "/dev/disk/by-uuid/B2B9-3115";
      fsType = "vfat";
      options = ["fmask=0022" "dmask=0022"];
    };
  };

  swapDevices = [
    {device = "/dev/disk/by-uuid/0d1fc824-623b-4bb8-bf7b-63a3e657889d";}
    # if you encrypt your swap, it'll also need to be configured here
  ];

  # 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 declarations with `networking.interfaces.<interface>.useDHCP`.
  networking.useDHCP = lib.mkDefault true;
  # networking.interfaces.wlan0.useDHCP = lib.mkDefault true;

  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
  hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

After this is done, you can clone your NixOS configuration and replace your host's existing hardware-configuration.nix with your new one:

\# git clone https://github.com/Lunarnovaa/lunix.git
\# cp /mnt/etc/nixos/hardware-configuration.nix lunix/hosts/$HOSTNAME/hardware/hardware-configuration.nix

Install your new system:

\# nixos-install --flake './lunix#$HOSTNAME'

Setup your user's password:

\# nixos-enter --root /mnt -c 'passwd lunarnova'

If all went well, you can reboot into your system.

Post-Install

Now, this is the important part. After booting into your new system, we can start setting up impermanence, swap encryption, and the like. Firstly, let's make our ssh keys.

# Generate system SSH key
\# ssh-keygen -A
# Generate user SSH Key
\$ ssh-keygen

After uploading that to your GitHub account, clone your repo and, replace the hardware-configuration.nix again, commit, and setup swap encryption:

\$ git clone git@github.com:Lunarnovaa/lunix.git
\$ cd lunix
\$ cp /etc/nixos/hardware-configuration.nix lunix/hosts/$HOSTNAME/hardware/hardware-configuration.nix
\$ git commit -m "$HOSTNAME: init btrfs encryption"

Your swap settings might look something like this:

# $HOSTNAME/hardware/hardware-configuration.nix
swapDevices = singleton {
  # We use partuuid for the swap since the uuid and label get randomized
  # You can find this with `sudo blkid`, find the PARTUUID of your swap partition
  device = "/dev/disk/by-partuuid/d988f16a-ab52-426b-ad41-964b0ae8dabb";
  randomEncryption = {
    enable = true;
    cipher = "aes-xts-plain64"; # Run `cryptsetup benchmark` to find which one is the fastest
    keySize = 256;
    sectorSize = 4096;
  };
};

Commit again, then enable impermanence:

# $HOSTNAME/hardware/system.nix
  config.lunix.hardware = {
    impermanence.enable = true;
  };

Make sure to add your passwords:

\# mkdir /persist/passwords
# Run the rest of the commands in root
\$ su
\# mkpasswd -m sha-512 > /persist/passwords/lunarnova
\# mkpasswd -m sha-512 > /persist/passwords/root

You should then be able to switch into your new configuration without a hitch:

\$ nh os switch --ask

Finally, we have to give Agenix our host key so that we can decrypt our secrets. First, update secrets/secrets.nix with the key generated as /persist/etc/ssh/ssh_host_ed25519_key.pub:

# secrets.nix
let
    $HOSTNAME = "ssh-ed25519 AAAAC3NzaC1lnnsISLTN291ltEINTKtnvkTN"; # Some string of characters
    # ... other host keys
    publicKeys = [polaris procyon $HOSTNAME];
in # secrets

Push your changes to GitHub, and have another machine re-key your secrets with the new key.