From 57762cf82c68ac47c978366dbf95d8cff906f5b1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 1 Oct 2024 14:49:37 +0200 Subject: [PATCH] Introduce UnifiedKernelImageProfiles= Same concept as PEAddons=, except these are added as additional profiles to every UKI built by mkosi. Accompanying systemd PR: https://github.com/systemd/systemd/pull/34608 --- mkosi/__init__.py | 108 ++++++++++++++++++++++++++++----- mkosi/config.py | 11 ++++ mkosi/resources/man/mkosi.1.md | 8 +++ tests/test_json.py | 4 ++ 4 files changed, 115 insertions(+), 16 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 28c24eaf5..e444cbde8 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -70,6 +70,7 @@ from mkosi.config import ( format_bytes, parse_boolean, parse_config, + parse_ini, summary, systemd_tool_version, want_selinux_relabel, @@ -1487,6 +1488,7 @@ def run_ukify( cmdline: str = "", arguments: Sequence[PathString] = (), options: Sequence[PathString] = (), + sign: bool = True, ) -> None: ukify = context.config.find_binary("ukify", "/usr/lib/systemd/ukify") if not ukify: @@ -1518,7 +1520,7 @@ def run_ukify( "--ro-bind", context.workspace / "cmdline", context.workspace / "cmdline", ] # fmt: skip - if context.config.secure_boot: + if sign and context.config.secure_boot: assert context.config.secure_boot_key assert context.config.secure_boot_certificate @@ -1565,6 +1567,7 @@ def build_uki( microcodes: list[Path], initrds: list[Path], cmdline: Sequence[str], + profiles: Sequence[Path], output: Path, ) -> None: if not (ukify := context.config.find_binary("ukify", "/usr/lib/systemd/ukify")): @@ -1574,12 +1577,14 @@ def build_uki( "--os-release", f"@{context.root / 'usr/lib/os-release'}", "--uname", kver, "--linux", kimg, + *flatten(["--join-profile", os.fspath(profile)] for profile in profiles), ] # fmt: skip options: list[PathString] = [ "--ro-bind", context.workspace / "cmdline", context.workspace / "cmdline", "--ro-bind", context.root / "usr/lib/os-release", context.root / "usr/lib/os-release", "--ro-bind", kimg, kimg, + *flatten(["--ro-bind", os.fspath(profile), os.fspath(profile)] for profile in profiles), ] # fmt: skip if context.config.secure_boot: @@ -1754,6 +1759,7 @@ def install_type1( kimg: Path, token: str, partitions: Sequence[Partition], + cmdline: list[str], ) -> None: dst = context.root / "boot" / token / kver entry = context.root / f"boot/loader/entries/{token}-{kver}.conf" @@ -1762,7 +1768,6 @@ def install_type1( entry.parent.mkdir(parents=True, exist_ok=True) kmods = build_kernel_modules_initrd(context, kver) - cmdline = finalize_cmdline(context, partitions, finalize_roothash(partitions)) with umask(~0o600): if ( @@ -1854,7 +1859,13 @@ def expand_kernel_specifiers(text: str, kver: str, token: str, roothash: str, bo def install_uki( - context: Context, kver: str, kimg: Path, token: str, partitions: Sequence[Partition] + context: Context, + kver: str, + kimg: Path, + token: str, + partitions: Sequence[Partition], + profiles: Sequence[Path], + cmdline: list[str], ) -> None: bootloader_entry_format = context.config.unified_kernel_image_format or "&e-&k" @@ -1916,7 +1927,8 @@ def install_uki( context.root / kimg, microcodes, initrds, - finalize_cmdline(context, partitions, roothash), + cmdline, + profiles, boot_binary, ) @@ -1973,6 +1985,46 @@ def systemd_addon_stub_binary(context: Context) -> Path: return stub +def build_uki_profiles(context: Context, cmdline: Sequence[str]) -> list[Path]: + if not want_uki(context) or not context.config.unified_kernel_image_profiles: + return [] + + stub = systemd_addon_stub_binary(context) + if not stub.exists(): + die(f"sd-stub not found at /{stub.relative_to(context.root)} in the image") + + (context.workspace / "uki-profiles").mkdir() + + profiles = [] + + for profile in context.config.unified_kernel_image_profiles: + output = context.workspace / "uki-profiles" / profile.with_suffix(".efi").name + + # We want to append the cmdline from the ukify config file to the base kernel command line so parse + # it from the ukify config file and append it to our own kernel command line. + + profile_cmdline = "" + + for section, k, v in parse_ini(profile): + if section == "UKI" and k == "Cmdline": + profile_cmdline = v.replace("\n", " ") + + with complete_step(f"Generating UKI profile '{profile.stem}'"): + run_ukify( + context, + stub, + output, + cmdline=f"{' '.join(cmdline)} {profile_cmdline}", + arguments=["--config", profile], + options=["--ro-bind", profile, profile], + sign=False, + ) + + profiles += [output] + + return profiles + + def install_kernel(context: Context, partitions: Sequence[Partition]) -> None: # Iterates through all kernel versions included in the image and generates a combined # kernel+initrd+cmdline+osrelease EFI file from it and places it in the /EFI/Linux directory of @@ -2004,12 +2056,14 @@ def install_kernel(context: Context, partitions: Sequence[Partition]) -> None: die("A bootable image was requested but no kernel was found") token = find_entry_token(context) + cmdline = finalize_cmdline(context, partitions, finalize_roothash(partitions)) + profiles = build_uki_profiles(context, cmdline) for kver, kimg in gen_kernel_images(context): if want_uki(context): - install_uki(context, kver, kimg, token, partitions) + install_uki(context, kver, kimg, token, partitions, profiles, cmdline) if not want_uki(context) or want_grub_bios(context, partitions): - install_type1(context, kver, kimg, token, partitions) + install_type1(context, kver, kimg, token, partitions, cmdline) if context.config.bootloader == Bootloader.uki: break @@ -2024,8 +2078,18 @@ def make_uki( ) initrds = [context.workspace / "initrd"] + build_uki( + context, + stub, + kver, + kimg, + microcode, + initrds, + context.config.kernel_command_line, + build_uki_profiles(context, context.config.kernel_command_line), + output, + ) - build_uki(context, stub, kver, kimg, microcode, initrds, context.config.kernel_command_line, output) extract_pe_section(context, output, ".linux", context.staging / context.config.output_split_kernel) extract_pe_section(context, output, ".initrd", context.staging / context.config.output_split_initrd) @@ -2369,15 +2433,27 @@ def check_tools(config: Config, verb: Verb) -> None: check_tool(config, "depmod", reason="generate kernel module dependencies") if want_efi(config) and config.unified_kernel_images == ConfigFeature.enabled: - check_ukify( - config, - version="254", - reason="build bootable images", - hint=( - "Use ToolsTree=default to download most required tools including ukify automatically " - "or use Bootable=no to create a non-bootable image which doesn't require ukify" - ), - ) + if config.unified_kernel_image_profiles: + check_ukify( + config, + version="257", + reason="build unified kernel image profiles", + hint=( + "Use ToolsTree=default to download most required tools including ukify " + "automatically" + ), + ) + else: + check_ukify( + config, + version="254", + reason="build bootable images", + hint=( + "Use ToolsTree=default to download most required tools including ukify " + "automatically or use Bootable=no to create a non-bootable image which doesn't " + "require ukify" + ), + ) if config.output_format in (OutputFormat.disk, OutputFormat.esp): check_systemd_tool(config, "systemd-repart", version="254", reason="build disk images") diff --git a/mkosi/config.py b/mkosi/config.py index abac5df54..a80e4d45b 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1590,6 +1590,7 @@ class Config: shim_bootloader: ShimBootloader unified_kernel_images: ConfigFeature unified_kernel_image_format: str + unified_kernel_image_profiles: list[Path] initrds: list[Path] initrd_packages: list[str] initrd_volatile_packages: list[str] @@ -2549,6 +2550,15 @@ SETTINGS = ( # default= help="Specify the format used for the UKI filename", ), + ConfigSetting( + dest="unified_kernel_image_profiles", + long="--uki-profile", + metavar="PATH", + section="Content", + parse=config_make_list_parser(delimiter=",", parse=make_path_parser()), + recursive_paths=("mkosi.uki-profiles/",), + help="Configuration files to generate UKI profiles", + ), ConfigSetting( dest="initrds", long="--initrd", @@ -4395,6 +4405,7 @@ def summary(config: Config) -> str: Shim Bootloader: {config.shim_bootloader} Unified Kernel Images: {config.unified_kernel_images} Unified Kernel Image Format: {config.unified_kernel_image_format} + Unified Kernel Image Profiles: {line_join_list(config.unified_kernel_image_profiles)} Initrds: {line_join_list(config.initrds)} Initrd Packages: {line_join_list(config.initrd_packages)} Initrd Volatile Packages: {line_join_list(config.initrd_volatile_packages)} diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index 7b181267a..3252f317d 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -989,6 +989,14 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, | `&h` | `roothash=` or `usrhash=` value of kernel argument | | `&c` | Number of tries used for boot attempt counting | +`UnifiedKernelImageProfiles=`, `--uki-profile=` +: Build additional UKI profiles. Takes a comma separated list of paths + to `ukify` config files. This option may be used multiple times in + which case each config gets built into a corresponding UKI profile. + Config files in the `mkosi.uki-profiles/` directory are + automatically picked up. All configured UKI profiles are added as + additional UKI profiles to each UKI built by mkosi. + `PeAddons=`, `--pe-addon` : Build additional PE addons. Takes a comma separated list of paths to `ukify` config files. This option may be used multiple times in which case diff --git a/tests/test_json.py b/tests/test_json.py index c27e1102f..4ce60ed4c 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -344,6 +344,9 @@ def test_config() -> None: } ], "UnifiedKernelImageFormat": "myuki", + "UnifiedKernelImageProfiles": [ + "/profile" + ], "UnifiedKernelImages": "auto", "UnitProperties": [ "PROPERTY=VALUE" @@ -520,6 +523,7 @@ def test_config() -> None: tools_tree_release=None, tools_tree_repositories=["abc"], unified_kernel_image_format="myuki", + unified_kernel_image_profiles=[Path("/profile")], unified_kernel_images=ConfigFeature.auto, unit_properties=["PROPERTY=VALUE"], use_subvolumes=ConfigFeature.auto, -- 2.47.2