Merge pull request #263442 from JulienMalka/systemd-boot-bootspec

authored by Ryan Lahfa and committed by GitHub 75fbff25 100f28fe

+49 -50
+49 -50
nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
··· 11 11 import subprocess 12 12 import sys 13 13 import warnings 14 - from typing import NamedTuple 14 + import json 15 + from typing import NamedTuple, Dict, List 16 + from dataclasses import dataclass 17 + 18 + 19 + @dataclass 20 + class BootSpec: 21 + init: str 22 + initrd: str 23 + initrdSecrets: str 24 + kernel: str 25 + kernelParams: List[str] 26 + label: str 27 + system: str 28 + toplevel: str 29 + specialisations: Dict[str, "BootSpec"] 30 + 15 31 16 32 17 33 libc = ctypes.CDLL("libc.so.6") ··· 71 87 os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf") 72 88 73 89 74 - def profile_path(profile: str | None, generation: int, specialisation: str | None, name: str) -> str: 75 - return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name)) 90 + def get_bootspec(profile: str | None, generation: int) -> BootSpec: 91 + boot_json_path = os.path.realpath("%s/%s" % (system_dir(profile, generation, None), "boot.json")) 92 + boot_json_f = open(boot_json_path, 'r') 93 + bootspec_json = json.load(boot_json_f) 94 + return bootspec_from_json(bootspec_json) 76 95 96 + def bootspec_from_json(bootspec_json: Dict) -> BootSpec: 97 + specialisations = bootspec_json['org.nixos.specialisation.v1'] 98 + specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()} 99 + return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations) 77 100 78 - def copy_from_profile(profile: str | None, generation: int, specialisation: str | None, name: str, dry_run: bool = False) -> str: 79 - store_file_path = profile_path(profile, generation, specialisation, name) 101 + 102 + def copy_from_file(file: str, dry_run: bool = False) -> str: 103 + store_file_path = os.path.realpath(file) 80 104 suffix = os.path.basename(store_file_path) 81 105 store_dir = os.path.basename(os.path.dirname(store_file_path)) 82 106 efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix) ··· 84 108 copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path)) 85 109 return efi_file_path 86 110 87 - 88 - def describe_generation(profile: str | None, generation: int, specialisation: str | None) -> str: 89 - try: 90 - with open(profile_path(profile, generation, specialisation, "nixos-version")) as f: 91 - nixos_version = f.read() 92 - except IOError: 93 - nixos_version = "Unknown" 94 - 95 - kernel_dir = os.path.dirname(profile_path(profile, generation, specialisation, "kernel")) 96 - module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0] 97 - kernel_version = os.path.basename(module_dir) 98 - 99 - build_time = int(os.path.getctime(system_dir(profile, generation, specialisation))) 100 - build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F') 101 - 102 - description = "@distroName@ {}, Linux Kernel {}, Built on {}".format( 103 - nixos_version, kernel_version, build_date 104 - ) 105 - 106 - return description 107 - 108 - 109 111 def write_entry(profile: str | None, generation: int, specialisation: str | None, 110 - machine_id: str, current: bool) -> None: 111 - kernel = copy_from_profile(profile, generation, specialisation, "kernel") 112 - initrd = copy_from_profile(profile, generation, specialisation, "initrd") 112 + machine_id: str, bootspec: BootSpec, current: bool) -> None: 113 + if specialisation: 114 + bootspec = bootspec.specialisations[specialisation] 115 + kernel = copy_from_file(bootspec.kernel) 116 + initrd = copy_from_file(bootspec.initrd) 113 117 114 118 title = "@distroName@{profile}{specialisation}".format( 115 119 profile=" [" + profile + "]" if profile else "", 116 120 specialisation=" (%s)" % specialisation if specialisation else "") 117 121 118 122 try: 119 - append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets") 120 - subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)]) 123 + subprocess.check_call([bootspec.initrdSecrets, "@efiSysMountPoint@%s" % (initrd)]) 121 124 except FileNotFoundError: 122 125 pass 123 126 except subprocess.CalledProcessError: ··· 132 135 entry_file = "@efiSysMountPoint@/loader/entries/%s" % ( 133 136 generation_conf_filename(profile, generation, specialisation)) 134 137 tmp_path = "%s.tmp" % (entry_file) 135 - kernel_params = "init=%s " % profile_path(profile, generation, specialisation, "init") 138 + kernel_params = "init=%s " % bootspec.init 136 139 137 - with open(profile_path(profile, generation, specialisation, "kernel-params")) as params_file: 138 - kernel_params = kernel_params + params_file.read() 140 + kernel_params = kernel_params + " ".join(bootspec.kernelParams) 141 + build_time = int(os.path.getctime(system_dir(profile, generation, specialisation))) 142 + build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F') 143 + 139 144 with open(tmp_path, 'w') as f: 140 145 f.write(BOOT_ENTRY.format(title=title, 141 146 generation=generation, 142 147 kernel=kernel, 143 148 initrd=initrd, 144 149 kernel_params=kernel_params, 145 - description=describe_generation(profile, generation, specialisation))) 150 + description=f"{bootspec.label}, built on {build_date}")) 146 151 if machine_id is not None: 147 152 f.write("machine-id %s\n" % machine_id) 148 153 f.flush() ··· 173 178 return configurations[-configurationLimit:] 174 179 175 180 176 - def get_specialisations(profile: str | None, generation: int, _: str | None) -> list[SystemIdentifier]: 177 - specialisations_dir = os.path.join( 178 - system_dir(profile, generation, None), "specialisation") 179 - if not os.path.exists(specialisations_dir): 180 - return [] 181 - return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)] 182 - 183 - 184 181 def remove_old_entries(gens: list[SystemIdentifier]) -> None: 185 182 rex_profile = re.compile(r"^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$") 186 183 rex_generation = re.compile(r"^@efiSysMountPoint@/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$") 187 184 known_paths = [] 188 185 for gen in gens: 189 - known_paths.append(copy_from_profile(*gen, "kernel", True)) 190 - known_paths.append(copy_from_profile(*gen, "initrd", True)) 186 + bootspec = get_bootspec(gen.profile, gen.generation) 187 + known_paths.append(copy_from_file(bootspec.kernel, True)) 188 + known_paths.append(copy_from_file(bootspec.initrd, True)) 191 189 for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"): 192 190 if rex_profile.match(path): 193 191 prof = rex_profile.sub(r"\1", path) ··· 279 277 remove_old_entries(gens) 280 278 for gen in gens: 281 279 try: 282 - is_default = os.path.dirname(profile_path(*gen, "init")) == args.default_config 283 - write_entry(*gen, machine_id, current=is_default) 284 - for specialisation in get_specialisations(*gen): 285 - write_entry(*specialisation, machine_id, current=is_default) 280 + bootspec = get_bootspec(gen.profile, gen.generation) 281 + is_default = os.path.dirname(bootspec.init) == args.default_config 282 + write_entry(*gen, machine_id, bootspec, current=is_default) 283 + for specialisation in bootspec.specialisations.keys(): 284 + write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default) 286 285 if is_default: 287 286 write_loader_conf(*gen) 288 287 except OSError as e: