nix-community/impermanence

repository·master·Indexed 23 days ago

https://github.com/nix-community/impermanence

A tool for NixOS and Home Manager users to manage ephemeral root filesystems. It allows users to declare specific files and directories that should persist across reboots while discarding the rest of the system state by linking or bind mounting them from persistent storage.

Tokens
2.8K
Snippets
5
Records
6
Agent score
33%

What's inside impermanence

  1. What is Impermanence

    master

    Impermanence is a tool that allows you to choose which files and directories are kept between reboots, while the rest are discarded. This is achieved by using an ephemeral root filesystem (which is wiped on every boot) and using Impermanence modules to link or bind mount specific files and directories from a persistent storage volume back into the root filesystem.

    Core Requirements:

    1. An ephemeral root filesystem: A filesystem that gets wiped on reboot (e.g., tmpfs or BTRFS subvolumes).
    2. Persistent storage: At least one mounted volume where files you want to keep are stored permanently.
    3. Impermanence modules: Modules that handle the linking/mounting between persistent storage and the root filesystem.
  2. Set up an ephemeral root using BTRFS subvolumes

    master

    A more advanced method uses a regular filesystem with BTRFS subvolumes. You can boot into a fresh subvolume each time and use postResumeCommands to move the previous root to an old_roots directory and clean up subvolumes older than a certain threshold (e.g., 30 days).

    Note: This example assumes the BTRFS filesystem is in an LVM volume group named root_vg. Adjust paths as necessary.

    {
      fileSystems."/" = {
        device = "/dev/root_vg/root";
        fsType = "btrfs";
        options = [ "subvol=root" ];
      };
    
      boot.initrd.postResumeCommands = lib.mkAfter ''
        mkdir /btrfs_tmp
        mount /dev/root_vg/root /btrfs_tmp
        if [[ -e /btrfs_tmp/root ]]; then
            mkdir -p /btrfs_tmp/old_roots
            timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/root)" "+%Y-%m-%-d_%H:%M:%S")
            mv /btrfs_tmp/root "/btrfs_tmp/old_roots/$timestamp"
        fi
    
        delete_subvolume_recursively() {
            IFS=$'\n'
            for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
                delete_subvolume_recursively "/btrfs_tmp/$i"
            done
            btrfs subvolume delete "$1"
        }
    
        for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do
            delete_subvolume_recursively "$i"
        done
    
        btrfs subvolume create /btrfs_tmp/root
        umount /btrfs_tmp
      '';
    
      fileSystems."/persistent" = {
        device = "/dev/root_vg/root";
        neededForBoot = true;
        fsType = "btrfs";
        options = [ "subvol=persistent" ];
      };
    
      fileSystems."/nix" = {
        device = "/dev/root_vg/root";
        fsType = "btrfs";
        options = [ "subvol=nix" ];
      };
    
      fileSystems."/boot" = {
        device = "/dev/disk/by-uuid/XXXX-XXXX";
        fsType = "vfat";
      };
    }
  3. Install the Impermanence NixOS module

    master

    You can import the module directly from a local path or via a Flake.

    Via Flake (Recommended): Use the nixosModules.impermanence output from the Impermanence flake.

    Note on dependencies: Impermanence lists nixpkgs and home-manager as dependencies for development. If you want to keep your flake.lock clean, you can follow them to empty strings in your inputs.

    Important: Ensure all your persistent and ephemeral storage volumes are marked with neededForBoot to avoid boot issues.

    {
      inputs = {
        impermanence.url = "github:nix-community/impermanence";
      };
    
      outputs = { self, nixpkgs, impermanence, ... }: 
        {
          nixosConfigurations.sythe = nixpkgs.lib.nixosSystem {
            system = "x86_64-linux";
            modules = [
              impermanence.nixosModules.impermanence
              ./machines/sythe/configuration.nix
            ];
          };
        };
    }
  4. Configure persistence in Home Manager

    master

    The Home Manager module adds the home.persistence.<path> option. It works similarly to the NixOS module, but paths are automatically prefixed with the user's home directory.

    Requirement: You must use the Home Manager NixOS module (from the nixos directory in the Home Manager repo) and the NixOS environment.persistence module for this to work correctly.

    Usage: Define home.persistence.<path> within your Home Manager configuration. The directories and files submodules behave identically to the NixOS version.

    {
      inputs = {
        home-manager.url = "github:nix-community/home-manager";
        impermanence.url = "github:nix-community/impermanence";
      };
    
      outputs = {
        home-manager,
        nixpkgs,
        impermanence,
        ...
      }: {
        nixosConfigurations.sythe = nixpkgs.lib.nixosSystem {
          system = "x86_64-linux";
          modules = [
            {
              imports = [
                impermanence.nixosModules.impermanence
                home-manager.nixosModules.home-manager
              ];
    
              home-manager.users.bird = {
                home.persistence."/persistent" = {
                  directories = [
                    "Downloads"
                    { directory = ".ssh"; mode = "0700"; }
                  ];
                  files = [
                    ".screenrc"
                  ];
                };
              };
            }
          ];
        };
      };
    }
  5. Set up an ephemeral root using tmpfs

    master

    The easiest way to achieve an ephemeral root is to use tmpfs. All data resides in system memory and is automatically cleared on reboot.

    Drawbacks:

    • Large files or high data generation can lead to Out-of-Memory (OOM) or disk-full scenarios.
    • Data loss occurs if the system crashes or loses power before files are moved to persistent storage.

    Example Configuration: In this setup, / is a tmpfs, while /persistent and /nix are stored on a persistent BTRFS subvolume.

    {
      fileSystems."/" = {
        device = "none";
        fsType = "tmpfs";
        options = [ "defaults" "size=25%" "mode=755" ];
      };
    
      fileSystems."/persistent" = {
        device = "/dev/root_vg/root";
        neededForBoot = true;
        fsType = "btrfs";
        options = [ "subvol=persistent" ];
      };
    
      fileSystems."/nix" = {
        device = "/dev/root_vg/root";
        fsType = "btrfs";
        options = [ "subvol=nix" ];
      };
    
      fileSystems."/boot" = {
        device = "/dev/disk/by-uuid/XXXX-XXXX";
        fsType = "vfat";
      };
    }
  6. Configure persistence in NixOS

    master

    The NixOS module adds the environment.persistence.<path> option. Each attribute name under environment.persistence represents a unique persistent storage location (e.g., /persistent).

    Root Persistence Options

    • enable: Enables this storage location. Defaults to true.
    • hideMounts: If true, sets x-gvfs-hide on bind mounts to hide them from file managers.
    • allowTrash: If true, sets x-gvfs-trash to allow GTK-based apps to use the trash.

    directories submodule

    Used to bind mount directories. A directory can be a string (path) or a submodule with:

    • directory: The path to the directory.
    • persistentStoragePath: The path to the persistent storage (defaults to the attribute name, e.g., /persistent).
    • user: Owner of the directory.
    • group: Group of the directory.
    • mode: Permissions (octal or symbolic).

    files submodule

    Used to link or bind files. A file can be a string or a submodule with:

    • file: The path to the file.
    • persistentStoragePath: The path to the persistent storage (defaults to the attribute name).
    • parentDirectory: Permissions (user, group, mode) for the file's parent directory if it doesn't exist.
    • method: The linking method. "auto" (default) uses a bind mount if the file exists in storage, otherwise a symlink. Use "symlink" to force a symlink.
    {
      environment.persistence."/persistent" = {
        enable = true;
        hideMounts = true;
        directories = [
          "/var/log"
          "/var/lib/bluetooth"
          { directory = "/var/lib/colord"; user = "colord"; group = "colord"; mode = "u=rwx,g=rx,o="; }
        ];
        files = [
          "/etc/machine-id"
          { file = "/var/keys/secret_file"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
        ];
        users.bird = {
          directories = [
            "Downloads"
            { directory = ".ssh"; mode = "0700"; }
          ];
          files = [
            ".screenrc"
          ];
        };
      };
    }