Skip to main content

NixOS: Ephemeral Root with Impermanence

NixOS - This article is part of a series.
Part 5: This Article

Importing the Impermanence module
#

As we use Nix to define our system state, the only directories actually required for boot are /boot and /nix. We can leverage this to ensure only desired files are actually retained on boot, eliminating cruft and ensuring pristine state upon each reboot. This can be done declaratively by importing the Impermanence module flake.

Destroying root
#

Using our BTRFS volume defined in the previous section, we can add an initrd service to delete the entire /root sub-partition on boot. Nix will then regenerate all required files with symlinks to the nix store located in the separate /nix sub-partition.

{
  inputs,
  ...
}:
{
  flake.modules.nixos.impermanence =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      options = {
        impermanence.enable = lib.mkEnableOption "impermanence";
      };

      config = lib.mkIf config.impermanence.enable {
        fileSystems."/persist".neededForBoot = true;
        boot.initrd.systemd.services.rollback = {
          description = "Rollback BTRFS root subvolume to a pristine state";
          wantedBy = [ "initrd.target" ];
          after = [ "initrd-root-device.target" ];
          before = [ "sysroot.mount" ];
          unitConfig.DefaultDependencies = "no";
          serviceConfig.Type = "oneshot";
          script = ''
            set -e
            mkdir -p /mnt
            mount ${config.fileSystems."/".device} /mnt

            if [[ -e /mnt/root ]]; then
              btrfs subvolume delete -R /mnt/root
            fi

            btrfs subvolume create /mnt/root
            umount /mnt
          '';
        };
      };
    };
}

Persisting files
#

Using the environment.persistence.<persistence sub-partition> configuration option we can set desired system directories such as bluetooth and network settings to be mounted to the persisted sub-partition and symlinked to their actual location in the root sub-partition:

        impermanence.enable = true;
        environment.persistence."/persist" = {
          hideMounts = true;
          directories = [
            "/var/log"
            "/var/lib/bluetooth"
            "/var/lib/nixos"
            "/var/lib/systemd/coredump"
            "/var/lib/libvirt"
            "/var/lib/netbird"
            "/etc/NetworkManager/system-connections"
            "/etc/nixos"
            "/root/.ssh"
          ];
        };

Similarly, we can persist user files such as Desktop, Documents and Flatpak applications:

        (lib.mkIf config.impermanence.enable {
          environment.persistence."/persist".users.${username} = {
            directories = [
              "Desktop"
              "Documents"
              "Downloads"
              "Music"
              "Pictures"
              "Videos"
              "Games"
              "Books"
              "nix-config"
              {
                directory = ".ssh";
                mode = "0700";
              }
              {
                directory = ".local/share/keyrings";
                mode = "0700";
              }
              {
                directory = ".local/share/kwalletd";
                mode = "0700";
              }
              ".local/share/flatpak"
              ".local/share/Steam"
              ".local/share/PrismLauncher"
              ".local/state/cosmic"
              ".local/state/cosmic-comp"
              ".config"
              ".var"
              ".vscode-oss/extensions"
            ];
            files = [
              ".bash_history"
              ".zsh_history"
            ];
          };
        })

NixOS - This article is part of a series.
Part 5: This Article