]> git.scottworley.com Git - nixos-boot-usage/commitdiff
Begin
authorScott Worley <scottworley@scottworley.com>
Fri, 14 Aug 2026 05:47:28 +0000 (22:47 -0700)
committerScott Worley <scottworley@scottworley.com>
Fri, 14 Aug 2026 05:47:28 +0000 (22:47 -0700)
modules/boot-usage.nix [new file with mode: 0644]

diff --git a/modules/boot-usage.nix b/modules/boot-usage.nix
new file mode 100644 (file)
index 0000000..d30bd9e
--- /dev/null
@@ -0,0 +1,76 @@
+{ lib, config, ... }:
+{
+  options = {
+    chk.bootDiskUsage = lib.mkOption {
+      description = ''
+        Choose boot.*.configurationLimit based on disk usage: Keep adding
+        entries up to this limit of disk space, in megabytes.
+
+        This allows the boot menu length to shrink and grow depending upon
+        how much kernel/initrd overlap there is between system profiles.
+
+        Note: This implementation doesn't yet support:
+          * Named profiles / specialisations
+          * Devicetrees
+          * Grub / refind / limine (systemd-boot only)
+
+      '';
+      type = with lib.types; nullOr int;
+      default = null;
+    };
+    boot.loader.systemd-boot.configurationLimit = lib.mkOption {
+      # Normally, this is inserted into systemd-boot-builder.py like this:
+      #
+      #   CONFIGURATION_LIMIT = int("@configurationLimit@")
+      #
+      # When chk.bootDiskUsage is set, instead of a simple integer constant,
+      # we instead insert a program snippet to dynamically calculate a
+      # configurationLimit (boot list length) as large as possible without
+      # going over the allowed disk space consumption.
+      #
+      # * We're stuck in expression context here, so we can't have nice things
+      #   like names; it has to be one giant expression.  :(
+      # * This declaration appears early in systemd-boot-builder.py, so we
+      #   can't use the various helper functions it defines below this point.
+      # * We can't get access to the output of 'nix-env --list-generations'
+      #   that it runs later, so we have to also run it here.  Running it twice
+      #   introduces a TOCTOU race risk, but systemd-boot-builder.py is already
+      #   full of those, so one more doesn't change much.
+      apply =
+        prev:
+        if builtins.isNull (config.chk.bootDiskUsage) then
+          prev
+        else
+          ''
+            " + (
+              "%d" % (
+                __import__('functools').reduce(
+                  lambda acc, gen: (
+                    gen[0] if sum(x[0] for x in acc[1]) < (${toString config.chk.bootDiskUsage} * 1024 * 1024) else acc[0],
+                    acc[1].union(gen[1])
+                  ),
+                  enumerate(
+                    [[(os.stat(f).st_size, f) for f in gen]
+                     for gen in map(
+                       lambda j: [j["kernel"], j["initrd"]],
+                       reversed(
+                         [json.load(
+                            open(f'/nix/var/nix/profiles/system-{line.split()[0]}-link/boot.json', 'r')
+                          )["org.nixos.bootspec.v1"]
+                          for line in subprocess.run(
+                            ['nix-env', '--list-generations', '-p', '/nix/var/nix/profiles/system'],
+                            check=True, text=True, stdout=subprocess.PIPE, stderr=sys.stderr
+                          ).stdout.split('\n')[:-1]
+                          ]
+                         )
+                       )
+                     ],
+                     start=1
+                    ),
+                  (0, set())
+                )[0]
+              )
+            ) + "'';
+    };
+  };
+}