Setting up Impermanence with disko and luks with btrfs and also nuking everything on reboot

So The problem is it nukes everything on reboot even nukes /etc/nixos/* and so on. So what Impermanence does is to have the directory or maybe recreates it but no directory had any files it was all empty directory.
here is my config.

copyfile /etc/nixos/system/hardware/impermanence.nix.bak
{
  inputs,
  username,
  ...
}: {
  # Enable FUSE for impermanence
  programs.fuse.userAllowOther = true;

  # Add user to 'fuse' group
  users.users.${username}.extraGroups = ["fuse"];

  # πŸ’₯ Nuke root AND home subvolumes on every boot
  boot.initrd.postDeviceCommands = ''
    mkdir -p /btrfs_tmp
    mount /dev/mapper/cryptroot /btrfs_tmp

    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"
    }

    # Nuke and recreate root
    delete_subvolume_recursively /btrfs_tmp/root
    btrfs subvolume create /btrfs_tmp/root

    # Nuke and recreate home (wipes /home β€” but we restore via impermanence)
    delete_subvolume_recursively /btrfs_tmp/home
    btrfs subvolume create /btrfs_tmp/home

    umount /btrfs_tmp
  '';

  # System persistence β€” survives reboots
  environment.persistence."/persist" = {
    enable = true;
    hideMounts = true;

    directories = [
      # System state
      "/var/lib/nixos"
      "/var/lib/systemd/coredump"
      "/var/lib/bluetooth"
      "/var/lib/NetworkManager"

      # NetworkManager connections
      "/etc/NetworkManager/system-connections"

      # SSH host keys (critical!)
      {
        directory = "/etc/ssh";
        mode = "0755";
      }

      # Keep your NixOS config
      "/etc/nixos"
    ];

    files = [
      "/etc/machine-id"
      # "/etc/hosts"  # uncomment if needed
    ];
  };

  # Prevent sudo lecture after reboot
  security.sudo.extraConfig = "Defaults lecture = never";

  # βœ… CRITICAL: Ensure both persisted AND ephemeral home dirs exist
  systemd.tmpfiles.rules = [
    # Persisted storage (source of truth)
    "d /persist/home 0755 root root -"
    "d /persist/home/${username} 0750 ${username} ${username} -"

    # Ephemeral home directory β€” MUST exist for bind mounts to work
    "d /home/${username} 0750 ${username} ${username} -"
  ];
}
copyfile /etc/nixos/home/hardware/impermanence.nix 
{username, ...}: {
  home.persistence."/persist/home/${username}" = {
    allowOther = true;

    directories = [
      # Standard user directories
      "Downloads"
      "Music"
      "Pictures"
      "Documents"
      "Videos"
      "Desktop"
      "Public"
      "Templates"

      # Security
      ".gnupg"
      ".local/share/keyrings"

      # Development & Config
      ".config/git"

      # Browsers
      ".zen"
      ".cache/zen"

      # Media
      #".config/mpv"
      #".local/share/vlc"

      # System
      #".local/state/wireplumber"
      #".config/pulse"

      # Shell
      #".bash_history"

      # Desktop & Apps
      ".local/share/applications"
      ".local/share/icons"
      ".config/fontconfig"

      # Your custom game/mod dirs
      "Games"
      "Game-Mods"
      "Appimages"
    ];

    files = [
      #".bashrc"
      #".profile"
      #".bash_history"
      #".config/mimeapps.list"
      #".config/user-dirs.dirs"
      #".config/user-dirs.locale"
    ];
  };
}

Also here is the disko config.

{inputs, ...}: {
  imports = [
    inputs.disko.nixosModules.disko
  ];

  disko.devices = {
    disk = {
      nvme0n1 = {
        type = "disk";
        device = "/dev/nvme0n1";
        content = {
          type = "gpt";
          partitions = {
            ESP = {
              label = "boot";
              name = "ESP";
              size = "512M";
              type = "EF00";
              content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/boot";
                mountOptions = [
                  "defaults"
                ];
              };
            };
            luks = {
              size = "100%";
              label = "luks";
              content = {
                type = "luks";
                name = "cryptroot";
                extraOpenArgs = [
                  "--allow-discards"
                  "--perf-no_read_workqueue"
                  "--perf-no_write_workqueue"
                ];
                content = {
                  type = "btrfs";
                  extraArgs = ["-L" "nixos" "-f"];
                  subvolumes = {
                    "/root" = {
                      mountpoint = "/";
                      mountOptions = ["subvol=root" "compress=zstd:1" "noatime" "ssd" "discard=async" "space_cache=v2" "commit=120"];
                    };
                    "/home" = {
                      mountpoint = "/home";
                      mountOptions = ["subvol=home" "compress=zstd:1" "noatime" "ssd" "discard=async" "space_cache=v2" "commit=120" "autodefrag"];
                    };
                    "/nix" = {
                      mountpoint = "/nix";
                      mountOptions = ["subvol=nix" "compress=zstd:1" "noatime" "ssd" "discard=async" "space_cache=v2" "commit=120" "nodatacow" "nodatasum"];
                    };
                    "/persist" = {
                      mountpoint = "/persist";
                      mountOptions = ["subvol=persist" "compress=zstd:1" "noatime" "ssd" "discard=async" "space_cache=v2" "commit=120"];
                    };
                    "/log" = {
                      mountpoint = "/var/log";
                      mountOptions = ["subvol=log" "compress=zstd:1" "noatime" "ssd" "discard=async" "space_cache=v2" "commit=120" "nodatacow" "nodatasum"];
                    };
                    "/swap" = {
                      mountpoint = "/swap";
                      swap.swapfile.size = "4G";
                    };
                  };
                };
              };
            };
          };
        };
      };
    };
  };

  fileSystems."/persist".neededForBoot = true;
  fileSystems."/var/log".neededForBoot = true;
}

My guess would be that wiping β€ž/β€ž also removes the folder where you mount the other subvolumes to. Have you tried recreating those folder Γ€s after creating the new root subvolume?

I recreated /home/ which even did not existed and my username also i recreated /etc/nixos/ and copied my backup files into it. the directory /etc/nixos existed but it was empty. All what i want to do is i want my system to nuke everything except which is declared with impermanence.

Did you recreate it in this script? Otherwise when delete you recreate the root subvolume the folder /etc/nixos does not exist hence the other subvolume cannot be mounted.

In my last message i did that time manually by commands. And i updated the script little bit.
copyfile /etc/nixos/system/hardware/impermanence.nix

{username, ...}: {
  # Enable FUSE for impermanence
  programs.fuse.userAllowOther = true;

  # Add user to 'fuse' group
  users.users.${username}.extraGroups = ["fuse"];

  boot.initrd.postDeviceCommands = ''
    echo "=== IMPERMANENCE: NUKE SCRIPT STARTING ===" > /dev/kmsg

    mkdir -p /btrfs_tmp

    if mount /dev/mapper/cryptroot /btrfs_tmp; then
      echo "Mounted cryptroot successfully" > /dev/kmsg

      # --- NUKE ROOT ---
      if btrfs subvolume delete /btrfs_tmp/root 2>/dev/null; then
        echo "Deleted root subvolume" > /dev/kmsg
      else
        echo "Root subvolume already gone or error (normal on first boot)" > /dev/kmsg
      fi
      btrfs subvolume create /btrfs_tmp/root
      echo "Created fresh root subvolume" > /dev/kmsg

      # --- NUKE HOME ---
      if btrfs subvolume delete /btrfs_tmp/home 2>/dev/null; then
        echo "Deleted home subvolume" > /dev/kmsg
      else
        echo "Home subvolume already gone or error (normal on first boot)" > /dev/kmsg
      fi
      btrfs subvolume create /btrfs_tmp/home
      echo "Created fresh home subvolume" > /dev/kmsg

      # --- NUKE USER PERSIST DATA ---
      if [ -d "/btrfs_tmp/persist/home" ]; then
        echo "Deleting ALL persisted home data..." > /dev/kmsg
        find /btrfs_tmp/persist/home -mindepth 1 -delete
        echo "βœ… All persisted home data deleted" > /dev/kmsg
      else
        echo "No persist/home directory found (first boot?)" > /dev/kmsg
      fi

      umount /btrfs_tmp
      echo "Unmounted btrfs_tmp" > /dev/kmsg

    else
      echo "❌ ERROR: Failed to mount cryptroot!" > /dev/kmsg
      echo "Available devices:" > /dev/kmsg
      ls -la /dev/mapper/ > /dev/kmsg
      exit 1
    fi

    echo "=== IMPERMANENCE: NUKE SCRIPT COMPLETE ===" > /dev/kmsg
  '';

  # System persistence β€” survives reboots
  environment.persistence."/persist" = {
    enable = true;
    hideMounts = true;

    directories = [
      # System state
      "/var/lib/nixos"
      "/var/lib/systemd/coredump"
      "/var/lib/bluetooth"
      "/var/lib/NetworkManager"

      # NetworkManager connections
      "/etc/NetworkManager/system-connections"

      # SSH host keys (critical!)
      {
        directory = "/etc/ssh";
        mode = "0755";
      }

      # Keep your NixOS config
      "/etc/nixos"
    ];

    files = [
      "/etc/machine-id"
      # "/etc/hosts"  # uncomment if needed
    ];
  };

  # Prevent sudo lecture after reboot
  security.sudo.extraConfig = "Defaults lecture = never";

  # βœ… CRITICAL: Ensure both persisted AND ephemeral home dirs exist
  systemd.tmpfiles.rules = [
    # Persisted storage (source of truth)
    "d /persist 0755 root root -"
    "d /persist/home 0755 root root -"
    "d /persist/home/${username} 0750 ${username} ${username} -"

    # Ephemeral home directory β€” MUST exist for bind mounts to work
    "d /home/${username} 0750 ${username} ${username} -"
  ];
}

copyfile /etc/nixos/home/hardware/impermanence.nix

{username, ...}: {
  home.persistence."/persist/home/${username}" = {
    allowOther = true;
    removePrefixDirectory = true; # ← CRITICAL: Only mount specific directories

    directories = [
      # Standard user directories
      "Downloads"
      "Music"
      "Pictures"
      "Documents"
      "Videos"
      "Desktop"
      "Public"
      "Templates"

      # Security
      ".gnupg"
      ".local/share/keyrings"

      # Development & Config
      ".config/git"

      # Browsers
      ".zen"
      ".cache/zen"

      # Media
      #".config/mpv"
      #".local/share/vlc"

      # System
      #".local/state/wireplumber"
      #".config/pulse"

      # Shell
      #".bash_history"

      # Desktop & Apps
      ".local/share/applications"
      ".local/share/icons"
      ".config/fontconfig"

      # Your custom game/mod dirs
      "Games"
      "Game-Mods"
      "Appimages"
    ];

    files = [
      #".bashrc"
      #".profile"
      #".bash_history"
      #".config/mimeapps.list"
      #".config/user-dirs.dirs"
      #".config/user-dirs.locale"
    ];
  };
}

And this time the script does nothing at all IMO, As i can not see deleting any file from /home directory. Such as random-test-file.txt This file exist in my home directory which is not managed by impermanence but it does not get deleted it just exist.

I suspect you are using an LLM? Generally the way using tmpfiles to creat folders is correct but I doubt in this case. I meant literally just adding mkdir -p /what/ever in the first version of the script.

I’m using zfs so I followed this guide: Erase your darlings: immutable infrastructure for mutable systems - Graham Christensen which you could adapt.

I also found: Encypted Btrfs Root with Opt-in State on NixOS.

I think before going into a setup like this it is helpful to do some more research on how it generally works.

Yes, I am using LLM even the LLM is getting failed in this case.
So i found my issue about why the second script and now the third script never runs, The issue is likely that /dev/mapper/cryptroot isn’t available yet when postDeviceCommands runs.
boot.initrd.postDeviceCommands or boot.initrd.postMountCommands is not running after luks decrypt, or maybe it is running before the luks decryption.

Again I would like to highlight you are probably best of looking at the links I provided. I can give you an example that works for me for zfs but that would probably not help (it is also quite close to the links I provided)

You are right but the script does not run no matter what i try. So i tried this config to verify if the script actually runs.

{
  username,
  pkgs,
  ...
}: {
  # Enable FUSE for impermanence
  programs.fuse.userAllowOther = true;

  # Add user to 'fuse' group
  users.users.${username}.extraGroups = ["fuse"];

  boot.initrd = {
    # Ensure Btrfs support
    supportedFilesystems = ["btrfs"];

    # Include necessary kernel modules
    kernelModules = ["btrfs" "dm-mod" "dm-crypt"];

    # Add btrfs-progs to initrd (other utilities should already be available)
    extraUtilsCommands = ''
      copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
    '';

    # MINIMAL TEST - Just check if the script runs at all
    postDeviceCommands = ''
      echo "=== IMPERMANENCE TEST STARTED ===" > /dev/kmsg
      echo "Script is running during boot" > /dev/kmsg
      echo "=== IMPERMANENCE TEST FINISHED ===" > /dev/kmsg
    '';
  };

  # System persistence configuration
  environment.persistence."/persist" = {
    enable = true;
    hideMounts = true;
    directories = [
      "/etc/nixos"
      "/etc/NetworkManager/system-connections"
      "/var/lib/nixos"
      "/var/lib/systemd/coredump"
      "/var/lib/bluetooth"
      "/var/lib/NetworkManager"
      {
        directory = "/etc/ssh";
        mode = "0755";
      }
    ];
    files = [
      "/etc/machine-id"
    ];
  };

  systemd.tmpfiles.rules = [
    "d /persist 0755 root root -"
    "d /persist/home 0755 root root -"
    "d /persist/home/${username} 0700 ${username} users -"
  ];

  fileSystems."/persist".neededForBoot = true;
  fileSystems."/var/log".neededForBoot = true;
  security.sudo.extraConfig = "Defaults lecture = never";
}

And the result is no output.

❯ sudo dmesg | grep -i impermanence
[sudo] password for Linux-DADDY: 
[ble: exit 1]
❯ sudo dmesg | grep -i IMPERMANENCE
[ble: exit 1]

As i said before The issue is likely that /dev/mapper/cryptroot isn’t available yet when postDeviceCommands runs.
boot.initrd.postDeviceCommands or boot.initrd.postMountCommands is not running after luks decrypt, or maybe it is running before the luks decryption.
If the boot.initrd.* does not run then no other good or bad script will also not run.

@eblechschmidt Hey! So i found the issue which was with home manager impermanence, It was the cause for bricking my whole impermanence config And i removed impermanence from home manager now it works fine. Anyway thank you.

2 Likes

Also if someone really needs the nuke script here it is.

{
  pkgs,
  lib,
  declarative,
  inputs,
  ...
}: let
  persistPath = declarative.persistPath;

  initrdNukeScript = ''
    echo "πŸš€ Initrd: Starting btrfs impermanence cleanup..."

    if [ ! -e /dev/mapper/cryptroot ]; then
        echo "❌ LUKS device not found - trying to open..."
        cryptsetup luksOpen /dev/disk/by-label/luks cryptroot || {
            echo "❌ Failed to open LUKS device"
            exit 1
        }
    fi

    mkdir -p /tmp/mnt-btrfs-root
    if ! mount -t btrfs /dev/mapper/cryptroot /tmp/mnt-btrfs-root -o subvolid=5,compress=zstd:1; then
        echo "❌ Failed to mount btrfs root (subvolid=5)"
        exit 1
    fi

    cd /tmp/mnt-btrfs-root
    timestamp=$(date +%Y%m%d-%H%M%S)

    if [ ! -d "@nix" ] || [ ! -d "@persist" ]; then
        echo "❌ CRITICAL: @nix or @persist missing! Aborting."
        cd /
        umount /tmp/mnt-btrfs-root
        exit 1
    fi

    delete_subvolume_recursively() {
        local subvol_path="$1"
        btrfs subvolume list -o "$subvol_path" 2>/dev/null | cut -f9 -d' ' | while read -r nested; do
            btrfs subvolume delete "$nested" 2>/dev/null || true
        done
        btrfs subvolume delete "$subvol_path" 2>/dev/null || true
    }

    if [ -e "@root" ]; then
        btrfs subvolume snapshot "@root" "@root-old-$timestamp" 2>/dev/null || true
        delete_subvolume_recursively "@root"
    fi
    btrfs subvolume create "@root"
    mkdir -p "@root"/{etc,var,tmp,usr}

    if [ -e "@home" ]; then
        btrfs subvolume snapshot "@home" "@home-old-$timestamp" 2>/dev/null || true
        delete_subvolume_recursively "@home"
    fi
    btrfs subvolume create "@home"

    # βœ… FIXED: Use $base (no braces) to avoid editor warnings
    for base in "@root" "@home"; do
        find . -maxdepth 1 -name "$base-old-*" -type d 2>/dev/null | sort -r | tail -n +4 | while IFS= read -r old; do
            btrfs subvolume delete "./$old" 2>/dev/null || true
        done
    done

    cd /
    umount /tmp/mnt-btrfs-root
    rmdir /tmp/mnt-btrfs-root 2>/dev/null || true
    echo "βœ… Impermanence cleanup completed"
  '';
in {
  imports = [
    inputs.impermanence.nixosModules.impermanence
  ];

  boot.initrd = {
    supportedFilesystems = ["btrfs" "vfat"];
    availableKernelModules = ["btrfs" "dm-mod" "dm-crypt"];
    kernelModules = ["btrfs" "dm-mod" "dm-crypt"];
    extraUtilsCommands = ''
      copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
      copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
    '';
    postDeviceCommands = lib.mkAfter initrdNukeScript;
  };

  fileSystems.${persistPath}.neededForBoot = true;
}
1 Like