Unverified Commit 6701c2c8 authored by Julien Malka's avatar Julien Malka Committed by GitHub
Browse files

Merge pull request #323421 from Mic92/systemd-boot-builder

parents c7411f7d ebfee308
Loading
Loading
Loading
Loading
+56 −33
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ import subprocess
import sys
import warnings
import json
from typing import NamedTuple, Dict, List
from typing import NamedTuple, Any
from dataclasses import dataclass

# These values will be replaced with actual values during the package build
@@ -21,7 +21,7 @@ BOOT_MOUNT_POINT = "@bootMountPoint@"
LOADER_CONF = f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"  # Always stored on the ESP
NIXOS_DIR = "@nixosDir@"
TIMEOUT = "@timeout@"
EDITOR = "@editor@" == "1"
EDITOR = "@editor@" == "1" # noqa: PLR0133
CONSOLE_MODE = "@consoleMode@"
BOOTSPEC_TOOLS = "@bootspecTools@"
DISTRO_NAME = "@distroName@"
@@ -38,17 +38,22 @@ class BootSpec:
    init: str
    initrd: str
    kernel: str
    kernelParams: List[str]
    kernelParams: list[str]  # noqa: N815
    label: str
    system: str
    toplevel: str
    specialisations: Dict[str, "BootSpec"]
    sortKey: str
    initrdSecrets: str | None = None
    specialisations: dict[str, "BootSpec"]
    sortKey: str  # noqa: N815
    initrdSecrets: str | None = None  # noqa: N815


libc = ctypes.CDLL("libc.so.6")

FILE = None | int

def run(cmd: list[str], stdout: FILE = None) -> subprocess.CompletedProcess[str]:
    return subprocess.run(cmd, check=True, text=True, stdout=stdout)

class SystemIdentifier(NamedTuple):
    profile: str | None
    generation: int
@@ -112,17 +117,20 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec:
        boot_json_f = open(boot_json_path, 'r')
        bootspec_json = json.load(boot_json_f)
    else:
        boot_json_str = subprocess.check_output([
        boot_json_str = run(
            [
                f"{BOOTSPEC_TOOLS}/bin/synthesize",
                "--version",
                "1",
                system_directory,
        "/dev/stdout"],
        universal_newlines=True)
                "/dev/stdout",
            ],
            stdout=subprocess.PIPE,
        ).stdout
        bootspec_json = json.loads(boot_json_str)
    return bootspec_from_json(bootspec_json)

def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec:
    specialisations = bootspec_json['org.nixos.specialisation.v1']
    specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
    systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {})
@@ -157,7 +165,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None

    try:
        if bootspec.initrdSecrets is not None:
            subprocess.check_call([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)])
            run([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)])
    except subprocess.CalledProcessError:
        if current:
            print("failed to create initrd secrets!", file=sys.stderr)
@@ -192,13 +200,17 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None


def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
    gen_list = subprocess.check_output([
    gen_list = run(
        [
            f"{NIX}/bin/nix-env",
            "--list-generations",
            "-p",
        "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system")],
        universal_newlines=True)
    gen_lines = gen_list.split('\n')
            "/nix/var/nix/profiles/%s"
            % ("system-profiles/" + profile if profile else "system"),
        ],
        stdout=subprocess.PIPE,
    ).stdout
    gen_lines = gen_list.split("\n")
    gen_lines.pop()

    configurationLimit = CONFIGURATION_LIMIT
@@ -230,10 +242,10 @@ def remove_old_entries(gens: list[SystemIdentifier]) -> None:
            gen_number = int(rex_generation.sub(r"\1", path))
        except ValueError:
            continue
        if not (prof, gen_number, None) in gens:
        if (prof, gen_number, None) not in gens:
            os.unlink(path)
    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/*"):
        if not path in known_paths and not os.path.isdir(path):
        if path not in known_paths and not os.path.isdir(path):
            os.unlink(path)


@@ -263,9 +275,7 @@ def install_bootloader(args: argparse.Namespace) -> None:
        # be there on newly installed systems, so let's generate one so that
        # bootctl can find it and we can also pass it to write_entry() later.
        cmd = [f"{SYSTEMD}/bin/systemd-machine-id-setup", "--print"]
        machine_id = subprocess.run(
          cmd, text=True, check=True, stdout=subprocess.PIPE
        ).stdout.rstrip()
        machine_id = run(cmd, stdout=subprocess.PIPE).stdout.rstrip()

    if os.getenv("NIXOS_INSTALL_GRUB") == "1":
        warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning)
@@ -288,11 +298,20 @@ def install_bootloader(args: argparse.Namespace) -> None:
        if os.path.exists(LOADER_CONF):
            os.unlink(LOADER_CONF)

        subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"])
        run(
            [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"]
            + bootctl_flags
            + ["install"]
        )
    else:
        # Update bootloader to latest if needed
        available_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", "--version"], universal_newlines=True).split()[2]
        installed_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}", "status"], universal_newlines=True)
        available_out = run(
            [f"{SYSTEMD}/bin/bootctl", "--version"], stdout=subprocess.PIPE
        ).stdout.split()[2]
        installed_out = run(
            [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}", "status"],
            stdout=subprocess.PIPE,
        ).stdout

        # See status_binaries() in systemd bootctl.c for code which generates this
        installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
@@ -311,7 +330,11 @@ def install_bootloader(args: argparse.Namespace) -> None:

        if installed_version < available_version:
            print("updating systemd-boot from %s to %s" % (installed_version, available_version))
            subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"])
            run(
                [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"]
                + bootctl_flags
                + ["update"]
            )

    os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}", exist_ok=True)
    os.makedirs(f"{BOOT_MOUNT_POINT}/loader/entries", exist_ok=True)
@@ -362,7 +385,7 @@ def install_bootloader(args: argparse.Namespace) -> None:

    os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", exist_ok=True)

    subprocess.check_call(COPY_EXTRA_FILES)
    run([COPY_EXTRA_FILES])


def main() -> None:
@@ -370,7 +393,7 @@ def main() -> None:
    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot")
    args = parser.parse_args()

    subprocess.check_call(CHECK_MOUNTPOINTS)
    run([CHECK_MOUNTPOINTS])

    try:
        install_bootloader(args)