diff --git a/nixos/modules/system/boot/loader/limine/limine-install.py b/nixos/modules/system/boot/loader/limine/limine-install.py index 384584f05db0..1992be83d73a 100644 --- a/nixos/modules/system/boot/loader/limine/limine-install.py +++ b/nixos/modules/system/boot/loader/limine/limine-install.py @@ -6,7 +6,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple import datetime import hashlib import json -import ctypes +from ctypes import CDLL import os import psutil import re @@ -16,20 +16,31 @@ import sys import tempfile import textwrap +@dataclass +class BootSpec: + system: str + init: str + kernel: str + kernelParams: List[str] + label: str + toplevel: str + specialisations: Dict[str, "BootSpec"] + initrd: str | None = None + initrdSecrets: str | None = None -limine_dir = None -can_use_direct_paths = False install_config = json.load(open('@configPath@', 'r')) -libc = ctypes.CDLL("libc.so.6") +libc = CDLL("libc.so.6") +limine_install_dir: Optional[str] = None +can_use_direct_paths = False +paths: Dict[str, bool] = {} -def config(*path: List[str]) -> Optional[Any]: +def config(*path: str) -> Optional[Any]: result = install_config for component in path: result = result[component] return result - def get_system_path(profile: str = 'system', gen: Optional[str] = None, spec: Optional[str] = None) -> str: basename = f'{profile}-{gen}-link' if gen is not None else profile profiles_dir = '/nix/var/nix/profiles' @@ -52,7 +63,7 @@ def get_profiles() -> List[str]: def get_gens(profile: str = 'system') -> List[Tuple[int, List[str]]]: - nix_env = os.path.join(config('nixPath'), 'bin', 'nix-env') + nix_env = os.path.join(str(config('nixPath')), 'bin', 'nix-env') output = subprocess.check_output([ nix_env, '--list-generations', '-p', get_system_path(profile), @@ -66,7 +77,7 @@ def get_gens(profile: str = 'system') -> List[Tuple[int, List[str]]]: def is_encrypted(device: str) -> bool: - for name, _ in config('luksDevices').items(): + for name in config('luksDevices'): if os.readlink(os.path.join('/dev/mapper', name)) == os.readlink(device): return True @@ -76,15 +87,13 @@ def is_encrypted(device: str) -> bool: def is_fs_type_supported(fs_type: str) -> bool: return fs_type.startswith('vfat') -paths = {} - def get_copied_path_uri(path: str, target: str) -> str: result = '' package_id = os.path.basename(os.path.dirname(path)) suffix = os.path.basename(path) dest_file = f'{package_id}-{suffix}' - dest_path = os.path.join(limine_dir, target, dest_file) + dest_path = os.path.join(str(limine_install_dir), target, dest_file) if not os.path.exists(dest_path): copy_file(path, dest_path) @@ -117,20 +126,6 @@ def get_file_uri(profile: str, gen: Optional[str], spec: Optional[str], name: st def get_kernel_uri(kernel_path: str) -> str: return get_copied_path_uri(kernel_path, "kernels") - -@dataclass -class BootSpec: - system: str - init: str - kernel: str - kernelParams: List[str] - label: str - toplevel: str - specialisations: Dict[str, "BootSpec"] - initrd: str | None = None - initrdSecrets: str | None = None - - def bootjson_to_bootspec(bootjson: dict) -> BootSpec: specialisations = bootjson['org.nixos.specialisation.v1'] specialisations = {k: bootjson_to_bootspec(v) for k, v in specialisations.items()} @@ -150,12 +145,11 @@ def config_entry(levels: int, bootspec: BootSpec, label: str, time: str) -> str: entry += f'module_path: ' + get_kernel_uri(bootspec.initrd) + '\n' if bootspec.initrdSecrets: - initrd_secrets_path = limine_dir + '/kernels/' + os.path.basename(toplevel) + '-secrets' + initrd_secrets_path = str(limine_install_dir) + '/kernels/' + os.path.basename(bootspec.toplevel) + '-secrets' os.makedirs(initrd_secrets_path) - old_umask = os.umask() - os.umask(0o137) - initrd_secrets_path_temp = tempfile.mktemp(os.path.basename(toplevel) + '-secrets') + old_umask = os.umask(0o137) + initrd_secrets_path_temp = tempfile.mktemp(os.path.basename(bootspec.toplevel) + '-secrets') if os.system(bootspec.initrdSecrets + " " + initrd_secrets_path_temp) != 0: print(f'warning: failed to create initrd secrets for "{label}"', file=sys.stderr) @@ -235,7 +229,7 @@ def option_from_config(name: str, config_path: List[str], conversion: Callable[[ def install_bootloader() -> None: - global limine_dir + global limine_install_dir boot_fs = None @@ -244,9 +238,9 @@ def install_bootloader() -> None: boot_fs = fs if config('efiSupport'): - limine_dir = os.path.join(config('efiMountPoint'), 'limine') + limine_install_dir = os.path.join(str(config('efiMountPoint')), 'limine') elif boot_fs and is_fs_type_supported(boot_fs['fsType']) and not is_encrypted(boot_fs['device']): - limine_dir = '/boot/limine' + limine_install_dir = '/boot/limine' else: possible_causes = [] if not boot_fs: @@ -266,14 +260,14 @@ def install_bootloader() -> None: partition formatted as FAT. ''')) - if config('secureBoot')['enable'] and not config('secureBoot')['createAndEnrollKeys'] and not os.path.exists("/var/lib/sbctl"): + if config('secureBoot', 'enable') and not config('secureBoot', 'createAndEnrollKeys') and not os.path.exists("/var/lib/sbctl"): print("There are no sbctl secure boot keys present. Please generate some.") sys.exit(1) - if not os.path.exists(limine_dir): - os.makedirs(limine_dir) + if not os.path.exists(limine_install_dir): + os.makedirs(limine_install_dir) else: - for dir, dirs, files in os.walk(limine_dir, topdown=True): + for dir, dirs, files in os.walk(limine_install_dir, topdown=True): for file in files: paths[os.path.join(dir, file)] = False @@ -290,7 +284,7 @@ def install_bootloader() -> None: last_gen_json = json.load(open(os.path.join(get_system_path('system', last_gen), 'boot.json'), 'r')) last_gen_boot_spec = bootjson_to_bootspec(last_gen_json) - config_file = config('extraConfig') + '\n' + config_file = str(config('extraConfig')) + '\n' config_file += textwrap.dedent(f''' timeout: {timeout} editor_enabled: {editor_enabled} @@ -334,10 +328,10 @@ def install_bootloader() -> None: config_file += generate_config_entry(profile, gen, isFirst) isFirst = False - config_file_path = os.path.join(limine_dir, 'limine.conf') + config_file_path = os.path.join(limine_install_dir, 'limine.conf') config_file += '\n# NixOS boot entries end here\n\n' - config_file += config('extraEntries') + config_file += str(config('extraEntries')) with open(f"{config_file_path}.tmp", 'w') as file: file.truncate() @@ -349,13 +343,14 @@ def install_bootloader() -> None: paths[config_file_path] = True for dest_path, source_path in config('additionalFiles').items(): - dest_path = os.path.join(limine_dir, dest_path) + dest_path = os.path.join(limine_install_dir, dest_path) copy_file(source_path, dest_path) - limine_binary = os.path.join(config('liminePath'), 'bin', 'limine') + limine_binary = os.path.join(str(config('liminePath')), 'bin', 'limine') cpu_family = config('hostArchitecture', 'family') if config('efiSupport'): + boot_file = "" if cpu_family == 'x86': if config('hostArchitecture', 'bits') == 32: boot_file = 'BOOTIA32.EFI' @@ -369,8 +364,8 @@ def install_bootloader() -> None: else: raise Exception(f'Unsupported CPU family: {cpu_family}') - efi_path = os.path.join(config('liminePath'), 'share', 'limine', boot_file) - dest_path = os.path.join(config('efiMountPoint'), 'efi', 'boot' if config('efiRemovable') else 'limine', boot_file) + efi_path = os.path.join(str(config('liminePath')), 'share', 'limine', boot_file) + dest_path = os.path.join(str(config('efiMountPoint')), 'efi', 'boot' if config('efiRemovable') else 'limine', boot_file) copy_file(efi_path, dest_path) @@ -383,9 +378,9 @@ def install_bootloader() -> None: print('error: failed to enroll limine config.', file=sys.stderr) sys.exit(1) - if config('secureBoot')['enable']: - sbctl = os.path.join(config('secureBoot')['sbctl'], 'bin', 'sbctl') - if config('secureBoot')['createAndEnrollKeys']: + if config('secureBoot', 'enable'): + sbctl = os.path.join(str(config('secureBoot', 'sbctl')), 'bin', 'sbctl') + if config('secureBoot', 'createAndEnrollKeys'): print("TEST MODE: creating and enrolling keys") try: subprocess.run([sbctl, 'create-keys']) @@ -412,8 +407,8 @@ def install_bootloader() -> None: if config('efiRemovable'): print('note: boot.loader.limine.efiInstallAsRemovable is true, no need to add EFI entry.') else: - efibootmgr = os.path.join(config('efiBootMgrPath'), 'bin', 'efibootmgr') - efi_partition = find_mounted_device(config('efiMountPoint')) + efibootmgr = os.path.join(str(config('efiBootMgrPath')), 'bin', 'efibootmgr') + efi_partition = find_mounted_device(str(config('efiMountPoint'))) efi_disk = find_disk_device(efi_partition) efibootmgr_output = subprocess.check_output([efibootmgr], stderr=subprocess.STDOUT, universal_newlines=True) @@ -457,24 +452,24 @@ def install_bootloader() -> None: if cpu_family != 'x86': raise Exception(f'Unsupported CPU family for BIOS install: {cpu_family}') - limine_sys = os.path.join(config('liminePath'), 'share', 'limine', 'limine-bios.sys') - limine_sys_dest = os.path.join(limine_dir, 'limine-bios.sys') + limine_sys = os.path.join(str(config('liminePath')), 'share', 'limine', 'limine-bios.sys') + limine_sys_dest = os.path.join(limine_install_dir, 'limine-bios.sys') copy_file(limine_sys, limine_sys_dest) - device = config('biosDevice') + device = str(config('biosDevice')) if device == 'nodev': print("note: boot.loader.limine.biosSupport is set, but device is set to nodev, only the stage 2 bootloader will be installed.", file=sys.stderr) return - else: - limine_deploy_args = [limine_binary, 'bios-install', device] + + limine_deploy_args: List[str] = [limine_binary, 'bios-install', device] if config('partitionIndex'): limine_deploy_args.append(str(config('partitionIndex'))) if config('forceMbr'): - limine_deploy_args += '--force-mbr' + limine_deploy_args.append('--force-mbr') try: subprocess.run(limine_deploy_args) @@ -496,9 +491,9 @@ def main() -> None: # it can leave the system in an unbootable state, when a crash/outage # happens shortly after an update. To decrease the likelihood of this # event sync the efi filesystem after each update. - rc = libc.syncfs(os.open(f"{config('efiMountPoint')}", os.O_RDONLY)) + rc = libc.syncfs(os.open(f"{str(config('efiMountPoint'))}", os.O_RDONLY)) if rc != 0: - print(f"could not sync {config('efiMountPoint')}: {os.strerror(rc)}", file=sys.stderr) + print(f"could not sync {str(config('efiMountPoint'))}: {os.strerror(rc)}", file=sys.stderr) if __name__ == '__main__': main() diff --git a/nixos/modules/system/boot/loader/limine/limine.nix b/nixos/modules/system/boot/loader/limine/limine.nix index c56474969fcd..92b4de374313 100644 --- a/nixos/modules/system/boot/loader/limine/limine.nix +++ b/nixos/modules/system/boot/loader/limine/limine.nix @@ -14,7 +14,7 @@ let liminePath = cfg.package; efiMountPoint = efi.efiSysMountPoint; fileSystems = config.fileSystems; - luksDevices = config.boot.initrd.luks.devices; + luksDevices = builtins.attrNames config.boot.initrd.luks.devices; canTouchEfiVariables = efi.canTouchEfiVariables; efiSupport = cfg.efiSupport; efiRemovable = cfg.efiInstallAsRemovable; @@ -45,6 +45,7 @@ in options.boot.loader.limine = { enable = lib.mkEnableOption "the Limine Bootloader"; + package = lib.mkPackageOption pkgs "limine" { }; enableEditor = lib.mkEnableOption null // { description = '' @@ -117,8 +118,6 @@ in ''; }; - package = lib.mkPackageOption pkgs "limine" { }; - efiSupport = lib.mkEnableOption null // { default = pkgs.stdenv.hostPlatform.isEfi; defaultText = lib.literalExpression "pkgs.stdenv.hostPlatform.isEfi";