]> git.scottworley.com Git - nixos-boot-usage/blob - modules/boot-usage.nix
Specify the full path to nix-env
[nixos-boot-usage] / modules / boot-usage.nix
1 { lib, config, ... }:
2 {
3 options = {
4 chk.bootDiskUsage = lib.mkOption {
5 description = ''
6 Choose boot.*.configurationLimit based on disk usage: Keep adding
7 entries up to this limit of disk space, in megabytes.
8
9 This allows the boot menu length to shrink and grow depending upon
10 how much kernel/initrd overlap there is between system profiles.
11
12 Note: This implementation doesn't yet support:
13 * Named profiles / specialisations
14 * Devicetrees
15 * Grub / refind / limine (systemd-boot only)
16
17 '';
18 type = with lib.types; nullOr int;
19 default = null;
20 };
21 boot.loader.systemd-boot.configurationLimit = lib.mkOption {
22 # Normally, this is inserted into systemd-boot-builder.py like this:
23 #
24 # CONFIGURATION_LIMIT = int("@configurationLimit@")
25 #
26 # When chk.bootDiskUsage is set, instead of a simple integer constant,
27 # we instead insert a program snippet to dynamically calculate a
28 # configurationLimit (boot list length) as large as possible without
29 # going over the allowed disk space consumption.
30 #
31 # * We're stuck in expression context here, so we can't have nice things
32 # like names; it has to be one giant expression. :(
33 # * This declaration appears early in systemd-boot-builder.py, so we
34 # can't use the various helper functions it defines below this point.
35 # * We can't get access to the output of 'nix-env --list-generations'
36 # that it runs later, so we have to also run it here. Running it twice
37 # introduces a TOCTOU race risk, but systemd-boot-builder.py is already
38 # full of those, so one more doesn't change much.
39 apply =
40 prev:
41 if builtins.isNull (config.chk.bootDiskUsage) then
42 prev
43 else
44 ''
45 " + (
46 "%d" % (
47 __import__('functools').reduce(
48 lambda acc, gen: (
49 gen[0] if sum(x[0] for x in acc[1]) < (${toString config.chk.bootDiskUsage} * 1024 * 1024) else acc[0],
50 acc[1].union(gen[1])
51 ),
52 enumerate(
53 [[(os.stat(f).st_size, f) for f in gen]
54 for gen in map(
55 lambda j: [j["kernel"], j["initrd"]],
56 reversed(
57 [json.load(
58 open(f'/nix/var/nix/profiles/system-{line.split()[0]}-link/boot.json', 'r')
59 )["org.nixos.bootspec.v1"]
60 for line in subprocess.run(
61 [f'{NIX}/bin/nix-env', '--list-generations', '-p', '/nix/var/nix/profiles/system'],
62 check=True, text=True, stdout=subprocess.PIPE, stderr=sys.stderr
63 ).stdout.split('\n')[:-1]
64 ]
65 )
66 )
67 ],
68 start=1
69 ),
70 (0, set())
71 )[0]
72 )
73 ) + "'';
74 };
75 };
76 }