--- /dev/null
+{ 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]
+ )
+ ) + "'';
+ };
+ };
+}