at 17.09-beta 7.4 kB view raw
1#! @python3@/bin/python3 -B 2import argparse 3import shutil 4import os 5import sys 6import errno 7import subprocess 8import glob 9import tempfile 10import errno 11import warnings 12import ctypes 13libc = ctypes.CDLL("libc.so.6") 14import re 15 16def copy_if_not_exists(source, dest): 17 if not os.path.exists(dest): 18 shutil.copyfile(source, dest) 19 20def system_dir(profile, generation): 21 if profile: 22 return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation) 23 else: 24 return "/nix/var/nix/profiles/system-%d-link" % (generation) 25 26BOOT_ENTRY = """title NixOS{profile} 27version Generation {generation} 28linux {kernel} 29initrd {initrd} 30options {kernel_params} 31""" 32 33def write_loader_conf(profile, generation): 34 with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f: 35 if "@timeout@" != "": 36 f.write("timeout @timeout@\n") 37 if profile: 38 f.write("default nixos-%s-generation-%d\n" % (profile, generation)) 39 else: 40 f.write("default nixos-generation-%d\n" % (generation)) 41 if not @editor@: 42 f.write("editor 0"); 43 os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf") 44 45def profile_path(profile, generation, name): 46 return os.readlink("%s/%s" % (system_dir(profile, generation), name)) 47 48def copy_from_profile(profile, generation, name, dry_run=False): 49 store_file_path = profile_path(profile, generation, name) 50 suffix = os.path.basename(store_file_path) 51 store_dir = os.path.basename(os.path.dirname(store_file_path)) 52 efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix) 53 if not dry_run: 54 copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path)) 55 return efi_file_path 56 57def write_entry(profile, generation, machine_id): 58 kernel = copy_from_profile(profile, generation, "kernel") 59 initrd = copy_from_profile(profile, generation, "initrd") 60 try: 61 append_initrd_secrets = profile_path(profile, generation, "append-initrd-secrets") 62 subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)]) 63 except FileNotFoundError: 64 pass 65 if profile: 66 entry_file = "@efiSysMountPoint@/loader/entries/nixos-%s-generation-%d.conf" % (profile, generation) 67 else: 68 entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation) 69 generation_dir = os.readlink(system_dir(profile, generation)) 70 tmp_path = "%s.tmp" % (entry_file) 71 kernel_params = "systemConfig=%s init=%s/init " % (generation_dir, generation_dir) 72 with open("%s/kernel-params" % (generation_dir)) as params_file: 73 kernel_params = kernel_params + params_file.read() 74 with open(tmp_path, 'w') as f: 75 f.write(BOOT_ENTRY.format(profile=" [" + profile + "]" if profile else "", 76 generation=generation, 77 kernel=kernel, 78 initrd=initrd, 79 kernel_params=kernel_params)) 80 if machine_id is not None: 81 f.write("machine-id %s\n" % machine_id) 82 os.rename(tmp_path, entry_file) 83 84def mkdir_p(path): 85 try: 86 os.makedirs(path) 87 except OSError as e: 88 if e.errno != errno.EEXIST or not os.path.isdir(path): 89 raise 90 91def get_generations(profile=None): 92 gen_list = subprocess.check_output([ 93 "@nix@/bin/nix-env", 94 "--list-generations", 95 "-p", 96 "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system"), 97 "--option", "build-users-group", ""], 98 universal_newlines=True) 99 gen_lines = gen_list.split('\n') 100 gen_lines.pop() 101 return [ (profile, int(line.split()[0])) for line in gen_lines ] 102 103def remove_old_entries(gens): 104 rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$") 105 rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-(.*)\.conf$") 106 known_paths = [] 107 for gen in gens: 108 known_paths.append(copy_from_profile(*gen, "kernel", True)) 109 known_paths.append(copy_from_profile(*gen, "initrd", True)) 110 for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"): 111 try: 112 if rex_profile.match(path): 113 prof = rex_profile.sub(r"\1", path) 114 else: 115 prof = "system" 116 gen = int(rex_generation.sub(r"\1", path)) 117 if not (prof, gen) in gens: 118 os.unlink(path) 119 except ValueError: 120 pass 121 for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"): 122 if not path in known_paths: 123 os.unlink(path) 124 125def get_profiles(): 126 if os.path.isdir("/nix/var/nix/profiles/system-profiles/"): 127 return [x 128 for x in os.listdir("/nix/var/nix/profiles/system-profiles/") 129 if not x.endswith("-link")] 130 else: 131 return [] 132 133def main(): 134 parser = argparse.ArgumentParser(description='Update NixOS-related systemd-boot files') 135 parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot') 136 args = parser.parse_args() 137 138 try: 139 with open("/etc/machine-id") as machine_file: 140 machine_id = machine_file.readlines()[0] 141 except IOError as e: 142 if e.errno != errno.ENOENT: 143 raise 144 # Since systemd version 232 a machine ID is required and it might not 145 # be there on newly installed systems, so let's generate one so that 146 # bootctl can find it and we can also pass it to write_entry() later. 147 cmd = ["@systemd@/bin/systemd-machine-id-setup", "--print"] 148 machine_id = subprocess.check_output(cmd).rstrip() 149 150 if os.getenv("NIXOS_INSTALL_GRUB") == "1": 151 warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning) 152 os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1" 153 154 if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1": 155 # bootctl uses fopen() with modes "wxe" and fails if the file exists. 156 if os.path.exists("@efiSysMountPoint@/loader/loader.conf"): 157 os.unlink("@efiSysMountPoint@/loader/loader.conf") 158 159 if "@canTouchEfiVariables@" == "1": 160 subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "install"]) 161 else: 162 subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "--no-variables", "install"]) 163 164 mkdir_p("@efiSysMountPoint@/efi/nixos") 165 mkdir_p("@efiSysMountPoint@/loader/entries") 166 167 gens = get_generations() 168 for profile in get_profiles(): 169 gens += get_generations(profile) 170 remove_old_entries(gens) 171 for gen in gens: 172 write_entry(*gen, machine_id) 173 if os.readlink(system_dir(*gen)) == args.default_config: 174 write_loader_conf(*gen) 175 176 # Since fat32 provides little recovery facilities after a crash, 177 # it can leave the system in an unbootable state, when a crash/outage 178 # happens shortly after an update. To decrease the likelihood of this 179 # event sync the efi filesystem after each update. 180 rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY)) 181 if rc != 0: 182 print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr) 183 184if __name__ == '__main__': 185 main()