Second generation fills up 1 GiB boot partition

Hi!

I’ve got a problem that when I try to create a second generation with nixos-rebuild switch, the command fails because my entire 1 GiB boot directory is full.

With only one generation the boot directory is about 20% full. My system consists of encrypted boot, root, and home partitions and a efi partition. To generate a new generation, I’ve emptied the boot partition, except for the decryption key situated in the partition, then running nix-collect-garbage -d, and finally running the switch command. I remember even trying setting the boot partition to 10 GiB, but still the second generation filling up the partition.

Is this expected behavior? Does anybody know how to fix this?

That’s not normal, but can’t advise further without seeing your config. I have a 500 MB ESP and it can hold a fair number of generations.

1 Like

Here’s my configs:

flake.nix:

  description = "NixOS configuration";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; # nixpkgs.url = "github:nixos/nixpkgs/<commit-hash>";
    disko.url = "github:nix-community/disko";
    disko.inputs.nixpkgs.follows = "nixpkgs";
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    sops-nix.url = "github:Mic92/sops-nix";
    sops-nix.inputs.nixpkgs.follows = "nixpkgs";
    chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";

    nixvim = {
      url = "github:nix-community/nixvim";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { nixpkgs, disko, home-manager, sops-nix, nixvim, chaotic, ... }:
  let
    system = "x86_64-linux";

    overlays = builtins.map (_: (import (./overlays + _))) [
      /flameshot.nix
      /libreoffice-still.nix
      /ncspot.nix
      /neomutt.nix
      /ranger.nix
      /ventoy-bin.nix
      /sway.nix
    ];

    homeSettings = {
      name        = "name";
      email       = "email@email.com";

      nameGit     = "nameGit";
      emailGit    = "emailGit@emailGit.com";

      terminal = "wezterm";

      desktopGaps = 4;
    };

  in {
    nixosConfigurations = {
      pc = nixpkgs.lib.nixosSystem {
        inherit system;
              specialArgs = {
                disabledModules = [
                  (nixpkgs + "/nixos/modules/profiles/all-hardware.nix")
                  (nixpkgs + "/nixos/modules/profiles/base.nix")
                ];
                inherit overlays;
              };
              modules = [
                ./configuration.nix
                (nixpkgs + "/nixos/modules/profiles/minimal.nix")
          disko.nixosModules.disko
          ./disk-config.nix
          {
            _module.args.disks = [ "/dev/sda" ];
          }
                home-manager.nixosModules.home-manager {
                  home-manager.useGlobalPkgs   = true;
                  home-manager.useUserPackages = true;
            home-manager.backupFileExtension = "bak";
                  home-manager.users.name      = import ./home.nix;
            home-manager.sharedModules = [
             nixvim.homeModules.nixvim
             sops-nix.homeManagerModules.sops
            ];
            home-manager.extraSpecialArgs = {
              inherit homeSettings;
            };
                }
          chaotic.nixosModules.default
              ];
      };
    };
  };
}

configuration.nix:

{ lib, pkgs, disabledModules, overlays, ... }:
let
  username = "username";
  hostName = "hostname";
  deviceIP = "192.168.1.2";

  linuxVersion = "linux_6_16";
in {
  imports = [
    ./hardware-configuration.nix # Include the results of the hardware scan.
    ./configuration/texlive.nix
  ];

  inherit disabledModules;

  chaotic.mesa-git.enable = true;

  hardware = {
    bluetooth = {
      enable                           = true;
      settings.General.FastConnectable = true;
    };

    cpu = {
      intel.updateMicrocode = true;
      x86.msr.enable        = true;
    };

    amdgpu.overdrive.enable = true;
  };

  powerManagement.cpuFreqGovernor = "performance";

  nixpkgs = {
    inherit overlays;

    config = {
      rocmSupport = true; # In order for btop w/ AMDGPU support to work.

      permittedInsecurePackages = [
        "dotnet-runtime-7.0.20"
        "libsoup-2.74.3"
        "ventoy-1.1.05"
      ];

      allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
        "discord"
        "furmark"
        "spotify"
        "steam"
        "steam-unwrapped"
        "unrar"
        "ventoy"
        "vintagestory"
      ];
    };
  };

  boot = {
    kernelPackages = pkgs.linuxPackages_latest;
    #extraModulePackages = with config.boot.kernelPackages; [ cpupower perf turbostat ];
    #consoleLogLevel = 3; # Hide unnecessary ACPI error messages at boot
    supportedFilesystems = [
      "btrfs"
    ];

    kernelModules = [
      "bfq"
    ];

    kernelParams = [
      "zswap.enabled=1"
      "iomem=relaxed"
      #"tsc=reliable"
      #"clocksource=tsc"
      #"scsi_mod.use_blk_mq=1"
    ];

    extraModprobeConfig = ''
      options snd_hda_intel power_save=0
    '';

    # Use the GRUB 2 boot loader.
    loader = {
      timeout = 1;
      grub = {
        enableCryptodisk      = true;
        efiSupport            = true;
        configurationLimit    = 100; # NOTE: Default value, should probably be
                                     # radically decreased.
        # Define on which harddrive you want to install Grub:
        device                = "nodev"; # or "nodev" for efi only
        efiInstallAsRemovable = true;
        default               = "0"; # "saved" does not work w/ encrypted /boot
        timeoutStyle          = "hidden";
        gfxmodeEfi            = "1920x1080,1024x768,auto";
        gfxpayloadEfi         = "keep";

        extraEntries = ''
          menuentry "Reboot"        { reboot }
          menuentry "Poweroff"      { halt }
          menuentry "UEFI-firmware" { fwsetup }
        '';
      };

      efi = {
        efiSysMountPoint     = "/efi";
        canTouchEfiVariables = false;
      };
    };

    initrd = {
      compressor            = "zstd";
      includeDefaultModules = true;
      secrets               = { "/boot" = null; };

      luks.devices.crypted  = {
        keyFile            = "/boot/cryptlvm.key";
        fallbackToPassword = true;
      };
    };
  };

  networking = {
    inherit hostName; # Define your hostname.
    extraHosts = "0.0.0.0 apresolve.spotify.com"; # TODO: Try to remove after ncspot update.

    wireless.iwd = {
      enable                                      = true;
      settings.General.EnableNetworkConfiguration = true; # Enable IWD's DHCP.
    };
  };

  # Set your time zone.
  time.timeZone = "<timezone>";

  # Select internationalisation properties.
  i18n.defaultLocale   = "en_US.UTF-8";
  console.useXkbConfig = true; # use xkb.options in tty.

  documentation = {
    enable = true;

    man.enable   = true;
    nixos.enable = true;
  };

  programs = {
    #ccache.enable                  = true;
    #nix-index.enableZshIntegration = true;
    #ssh.setXAuthLocation           = false;  # Fix conflicting values (maybe
                                              # later try to remove).
    corectrl.enable                = true;
    #corefreq.enable                = true; # FIXME: Try to enable after new update.
    firejail.enable                = true;
    flashrom.enable                = true;
    gamemode.enable                = true;
    gamescope.enable               = true;
    git.enable                     = true;
    git.lfs.enable                 = true;
    gnome-disks.enable             = true;
    htop.enable                    = true;
    less.enable                    = true;
    nix-ld.enable                  = true;
    virt-manager.enable            = true;

    bat = {
      enable = true;
      extraPackages = lib.mkForce [];
    };

    steam = {
      enable                  = true;
      #gamescopeSession.enable = true;
      extraPackages           = lib.mkForce [];
      extraCompatPackages     = lib.mkForce [];
    };

    thunar = {
      enable  = true;
      plugins = lib.mkForce [];
    };
  };


  # Enable sound.

  # PipeWire

  services.pipewire = {
    enable            = true;
    alsa.enable       = true;
    alsa.support32Bit = true;
    pulse.enable      = true;
    #jack.enable       = true;

    wireplumber.extraConfig.bluetoothEnhancements = {
      "monitor.bluez.properties" = {
        "bluez5.roles" = [ "a2dp_sink" "a2dp_source" "bap_sink" "bap_source" "hsp_hs" "hsp_ag" "hfp_hf" "hfp_ag" ];
        "bluez5.codecs" = [ "sbc" "sbc_xq" ];
        "bluez5.enable-sbc-xq" = true;
        #"bluez5.hfphsp-backend" = "native";
      };
    };
  };


  # Enable touchpad support (enabled default in most desktopManager).
  # services.xserver.libinput.enable = true;

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users = lib.attrsets.setAttrByPath [ username ] {
    isNormalUser = true;
    extraGroups = [
      "wheel" # Enable ‘sudo’ for the user.
      "libvirtd"
      "gamemode"
      "networkmanager"
      "input"
      "docker"
    ];
    shell = pkgs.bash;
  };

  security = {
    apparmor.enable = true;
    rtkit.enable    = true;

    doas = {
      enable = true;

      extraRules = [
        {
          groups  = [ "wheel" ];
          runAs   = "root";
          persist = true;
        }
      ];
    };

  fonts = {
    packages = with pkgs; [
      font-awesome # Waybar
      jetbrains-mono
      noto-fonts-emoji
      #dejavu_fonts
    ];
  };

  environment = {
    defaultPackages = [];

    etc = {
      "dict.conf".text = "server dict.org";
    };

    # Remove if don't needed:
    #noXlibs = false;

    pathsToLink = [
      "/libexec"
      "/share/fzf"
    ];

    # List packages installed in system profile. To search, run:
    # $ nix search wget
    systemPackages = with pkgs; lib.lists.flatten [
      (with linuxKernel.packages.${linuxVersion}; [ # TODO: Can the Linux version be determined programmatically?
        cpupower
        perf
        turbostat
      ])

      (with nodePackages; [
        alex
        cspell
      ])

      (with xfce; [
        ristretto
        xfce4-power-manager
      ])

      (with xorg; [
        xhost
        xlsclients
      ])

      (python3.withPackages (ps: with ps; [
        pdftotext
        numpy
        matplotlib
        pip
        unrardll
      ]))

      (wl-kbptr.overrideAttrs (_: {
        #mesonFlags = [ "-Dopencv=enabled" ];
        #buildInputs = lib.mkOptionDefault [ opencv ];
      }))

      #conky
      #trickle
      adwaita-icon-theme
      adwaita-qt
      adwaita-qt6
      age
      qadwaitadecorations
      qadwaitadecorations-qt6
      android-file-transfer
      atop
      bash-language-server
      bc
      beancount
      bison
      bitwarden-cli
      bleachbit
      bluez
      bluez-tools
      btrfs-progs
      buku
      cloc
      compsize
      cpu-x
      curl
      darktable
      dash
      ddrescue
      dialog
      dict
      discord
      disko
      dmidecode
      dos2unix
      du-dust
      dunst
      element-desktop
      exiftool
      ext4magic
      extundelete
      fastfetch
      fd
      fdupes
      file
      flex
      foot
      freetts # Arch linux: festival, festival-us
      furmark
      fzf
      gcc
      gimp
      git
      git-lfs
      git-repo
      gitRepo
      glances
      glib # Enable gio [trash]
      glmark2
      glxinfo
      gnumake
      gparted
      gperf
      grim
      gsettings-desktop-schemas
      guestfs-tools
      hdparm
      hledger
      home-manager
      htop
      hypnotix
      imagemagick
      imgcat
      inteltool # Show temperature in Conky
      iotop-c
      iw
      jabref
      jq
      krita
      lact
      languagetool
      libqalculate
      liberation_ttf
      libnotify
      libreoffice-still
      libsixel
      libvirt-glib
      libxml2
      lsix
      lua-language-server
      mailcap
      man-pages # Requires for 'man 7 signal' to work.
      mangohud
      mate.engrampa
      moreutils
      ncurses5
      nethogs
      nettools
      nix-index
      nix-output-monitor
      nix-tree
      nixd
      nodejs-slim
      ntfs3g
      numlockx
      nvtopPackages.amd
      openssl
      p7zip
      pamixer
      pass
      pavucontrol
      pciutils
      perl
      pigz
      piper
      playerctl
      pmbootstrap
      polkit_gnome
      poppler_utils
      procps
      proselint
      protontricks
      protonup
      protonup-qt
      pstree
      pv
      qemu_kvm
      qrencode
      rclone
      resources
      ripgrep
      ripgrep-all
      rsync
      rust-analyzer
      schedtool
      scrcpy
      signal-cli
      signal-desktop-bin
      smartmontools
      songrec
      sops
      sox
      spectre-meltdown-checker
      speedtest-cli
      spotify
      squashfsTools
      stress
      stress-ng
      sway-contrib.grimshot
      sysstat # iostat
      swayidle
      testdisk
      texlab
      textidote
      textlint
      tigervnc
      tldr
      tree
      tree-sitter
      ttyplot
      ueberzugpp # Used for the cover feature in ncspot.
      umu-launcher
      unrar
      unzip
      util-linux
      uuu
      vale
      ventoy-bin
      vim-language-server
      vintagestory
      visidata
      vlc
      vulkan-tools
      w3m
      wavemon
      wayvnc
      wdiff
      wev
      wget
      which
      winetricks
      wineWowPackages.waylandFull
      wl-clipboard
      write-good
      xdg-desktop-portal-gtk
      xdg-user-dirs
      xdg-utils # <- xdg-open
      xdotool
      xz
      zip
    ];
  };

  # Some programs need SUID wrappers, can be configured further or are started
  # in user sessions.
  # programs.mtr.enable = true;
  # programs.gnupg.agent = {
  #   enable           = true;
  #   enableSSHSupport = true;
  # };

  # List services that you want to enable:

  services = {
    #fstrim.enable                = true;
    #rsyncd.enable                = true;
    #smartd.enable                = true;
    #spotifyd.enable              = true;
    btrfs.autoScrub.enable       = true;
    envfs.enable                 = true;
    flatpak.enable               = true;
    fwupd.enable                 = true;
    getty.autologinUser          = username;
    minidlna.enable              = false;
    mpd.enable                   = true;
    ntfy-sh.enable               = true;
    ntfy-sh.settings.base-url    = "http://${deviceIP}";
    ntp.enable                   = true;
    power-profiles-daemon.enable = true;
    ratbagd.enable               = true;
    tumbler.enable               = true; # Enable thumbnails for images in Thunar.
    udisks2.enable               = true;

    locate = {
      enable = true;
      prunePaths = [
        "/tmp"
        "/var/tmp"
        "/var/cache"
        "/var/lock"
        "/var/run"
        "/var/spool"
        #"/nix/store"
        "/nix/var/log/nix"
      ];
    };

    openssh = {
      enable = true;
      settings.PasswordAuthentication = false;
    };

    udev = {
      enable = true;
      extraRules = ''
        # HDD
        ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"

        # SSD
        ACTION=="add|change", KERNEL=="sd[a-z]*|mmcblk[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="bfq"

        # NVMe SSD
        ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
      '';
    };

    clamav = {
      daemon.enable  = true;
      updater.enable = true;
    };

    gvfs.enable = true; # Fix gio trash.

    # Enable the X11 windowing system.
    xserver = {
      enable                       = true;
      displayManager.startx.enable = true;
    };

    # Enable CUPS to print documents.
    # printing.enable = true;
  };

  xdg.portal = {
      enable                = true;
      extraPortals          = [ pkgs.xdg-desktop-portal-gtk ];
      config.common.default = [ "gtk" ];
  };

  virtualisation = {
    libvirtd.enable = true;
    waydroid.enable = false;

    docker = {
      enable   = true;
      extraPackages = lib.mkForce [];
    };
  };

  networking = {
    firewall = {
      # Disable the firewall altogether.
      enable = true;
      extraPackages = lib.mkForce [];

      # Open ports in the firewall.
      allowedTCPPortRanges = [
        { from = 1714; to = 1764; } # KDE Connect.
      ];

      allowedUDPPortRanges = [
        { from = 1714; to = 1764; } # KDE Connect.
        { from = 2001; to = 2001; } # ARMA Reforger.
        #{ from = 2302; to = 2306; } # ARMA 3.
      ];
    };

    nftables.enable = false;
  };

  system = {
    autoUpgrade = {
      enable    = false;
      operation = "boot";
    };
  };

  systemd = {
    tpm2.enable = false;
    oomd.enable = true;

    services.lactd = {
      description = "AMDGPU Control Daemon";
      after       = [ "multi-user.target" ];
      wantedBy    = [ "multi-user.target" ];
      serviceConfig.ExecStart = "${lib.getExe pkgs.lact} daemon";
      enable = true;
    };

  nix.settings = {
    auto-optimise-store = true;

    max-jobs = 4;
    cores    = 4;

    experimental-features = [
      "nix-command"
      "flakes"
    ];
  };

  system.stateVersion = "25.05"; # Did you read the comment?
}

disk-config.nix:

let
  device = "/dev/sda";
  # If you want to use the key for interactive login be sure there is no
  # trailing newline. For example, use: `echo -n "password" > /tmp/secret.key`
  passwordFile = "/tmp/secret.key";
  # For the key, use:
  # dd bs=512 count=4 if=/dev/random of=/tmp/cryptlvm.key iflag=fullblock
  additionalKeyFiles = [ "/tmp/cryptlvm.key" ];
  EFISize = "512M";
  bootSize = "1G";
  rootSize = "76294M"; # 80 GigaB
  swapSize = "22888M"; # 24 GigaB
in {
  disko.devices = {
    disk = {
      sda = {
        inherit device;
        type = "disk";
        content = {
          type = "gpt";
          partitions = {
            ESP = {
              type = "EF00";
              size = EFISize;
              content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/efi";
              };
            };
            luks = {
              size = "100%";
              content = {
                type = "luks";
                name = "crypted";
                extraFormatArgs = [
                  "--type luks2"
                ];
                settings.allowDiscards = true;
                inherit passwordFile;
                # For the key, use:
                # dd bs=512 count=4 if=/dev/random of=/tmp/cryptlvm.key iflag=fullblock
                inherit additionalKeyFiles;
                content = {
                  type = "lvm_pv";
                  vg = "pool";
                };
              };
            };
          };
        };
      };
    };
    lvm_vg = {
      pool = {
        type = "lvm_vg";
        lvs = {
          boot = {
            size = bootSize;
            content = {
              type = "filesystem";
              format = "ext4";
              mountpoint = "/boot";
            };
          };
          root = {
            size = rootSize;
            content = {
              type = "filesystem";
              format = "btrfs";
              mountpoint = "/";
              mountOptions = [
                "compress=zstd:3"
              ];
            };
          };
          swap = {
            size = swapSize;
            content = {
              type = "swap";
              #discardPolicy = "both";
              resumeDevice = true;
            };
          };
          home = {
            size = "100%FREE";
            content = {
              type = "filesystem";
              format = "btrfs";
              mountpoint = "/home";
              mountOptions = [
                "compress=zstd:3"
              ];
            };
          };
        };
      };
    };
  };
}

Thanks for the help!

Would you like to share your configs, so that I could at least try to troubleshoot the issue with mine?

Well that’s not right. That’s saying, every time you nixos-rebuild, it should install the kernels / initrds in /boot and then copy the entirety of /boot into each generation’s initrd. You probably just want that to be

secrets = { "/boot/cryptlvm.key" = null; };

So that it only copies the key into the initrd.


Sidenote, encrypted /boot i.e. grub cryptodisk is strongly recommended against. It provides no added security, slows down boot a lot, requires a downgraded PBKDF, and creates an easy way to accidentally expose your LUKS key file if you’re not careful.

3 Likes

That was indeed the issue, thank you very much!

Could you provide material that explains why encrypting the boot partition is ill advised?

Well, I enumerated the main reasons before. But I’ll elaborate.

  • It provides no added security: Encrypting your kernels / initrds does not prevent tampering. The only thing that does that is Secure Boot. Without Secure Boot, it is just as easy to tamper with your unencrypted boot loader as it is to tamper with unencrypted kernels or initrd. If the boot loader is tampered with, it’s free to boot whatever it likes, including tampered kernels. EDIT: I should have clarified, a good Secure Boot setup will cover the boot loader and the kernel / initrd as a UKI or something along those lines, e.g. Lanzaboote, which is why Secure Boot does not need them to be encrypted.
  • It slows down boot a lot: Grub cryptodisk is insanely slow. I’ve seen it add as much as a minute and a half to boot times, depending on hardware and LUKS parameters.
  • It requires a downgraded PBKDF: Grub cryptodisk is usually behind on standard LUKS features, and for a good while now that has meant you cannot use argon2id as your PBKDF, despite this being the recommended default by cryptsetup.
  • It creates an easy way to accidentally expose your LUKS key file if you’re not careful: The keyfile added to the initrd with boot.initrd.secrets is just an easy file to accidentally leak. This one isn’t that big a deal, since hopefully you can keep a file secret. But I have seen many examples where people got this wrong and didn’t know they were leaking their disk encryption key.

Furthermore, NixOS maintainers generally recommend against grub in general. For one, see this laundry list of security vulnerabilities we had to patch. Grub just isn’t very well maintained, either in nixpkgs or upstream (AFAICT). Also, in my experience, grub is pretty buggy.

My recommendation is systemd-boot for its simplicity, or limine if you have to use legacy BIOS. A common criticism of systemd-boot is that it makes boot look less pretty with its text-only interface, but I find this is negated by setting boot.loader.timeout = 0;, which tells systemd-boot not to appear at all unless you hold spacebar during boot.

7 Likes

Thank you very much, I really appreciate your elaborate response!

I now understand why an encrypted boot makes no sense and my plan is to move to Lanzaboote.

1 Like