]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Support building unified kernel images
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 7 Sep 2023 14:25:47 +0000 (16:25 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 7 Sep 2023 17:00:30 +0000 (19:00 +0200)
All the necessary pieces for this already exists, so let's add this
as an additional output format.

mkosi/__init__.py
mkosi/config.py
mkosi/qemu.py
mkosi/resources/mkosi.md

index fbe56030e55d98ce0a5a4bcf584c1f1b3523ae13..7f8f458bbcce7acfebf3cdc6f48dade193d8a42c 100644 (file)
@@ -979,7 +979,89 @@ def extract_pe_section(state: MkosiState, binary: Path, section: str, output: Pa
     run([python], input=pefile)
 
 
-def install_unified_kernel(state: MkosiState, partitions: Sequence[Partition]) -> None:
+def build_uki(
+    state: MkosiState,
+    kimg: Path,
+    initrds: Sequence[Path],
+    output: Path,
+    roothash: Optional[str] = None,
+) -> None:
+    if (state.root / "etc/kernel/cmdline").exists():
+        cmdline = [(state.root / "etc/kernel/cmdline").read_text().strip()]
+    elif (state.root / "usr/lib/kernel/cmdline").exists():
+        cmdline = [(state.root / "usr/lib/kernel/cmdline").read_text().strip()]
+    else:
+        cmdline = []
+
+    if roothash:
+        cmdline += [roothash]
+
+    cmdline += state.config.kernel_command_line
+
+    # Older versions of systemd-stub expect the cmdline section to be null terminated. We can't embed
+    # nul terminators in argv so let's communicate the cmdline via a file instead.
+    (state.workspace / "cmdline").write_text(f"{' '.join(cmdline).strip()}\x00")
+
+    stub = state.root / f"usr/lib/systemd/boot/efi/linux{state.config.architecture.to_efi()}.efi.stub"
+    if not stub.exists():
+        die(f"sd-stub not found at /{stub.relative_to(state.root)} in the image")
+
+    cmd: list[PathString] = [
+        shutil.which("ukify") or "/usr/lib/systemd/ukify",
+        "--cmdline", f"@{state.workspace / 'cmdline'}",
+        "--os-release", f"@{state.root / 'usr/lib/os-release'}",
+        "--stub", stub,
+        "--output", output,
+        "--efi-arch", state.config.architecture.to_efi(),
+    ]
+
+    if not state.config.tools_tree:
+        for p in state.config.extra_search_paths:
+            cmd += ["--tools", p]
+
+    uki_config = state.pkgmngr / "etc/kernel/uki.conf"
+    if uki_config.exists():
+        cmd += ["--config", uki_config]
+
+    if state.config.secure_boot:
+        assert state.config.secure_boot_key
+        assert state.config.secure_boot_certificate
+
+        cmd += ["--sign-kernel"]
+
+        if state.config.secure_boot_sign_tool != SecureBootSignTool.pesign:
+            cmd += [
+                "--signtool", "sbsign",
+                "--secureboot-private-key", state.config.secure_boot_key,
+                "--secureboot-certificate", state.config.secure_boot_certificate,
+            ]
+        else:
+            pesign_prepare(state)
+            cmd += [
+                "--signtool", "pesign",
+                "--secureboot-certificate-dir", state.workspace / "pesign",
+                "--secureboot-certificate-name", certificate_common_name(state.config.secure_boot_certificate),
+            ]
+
+        sign_expected_pcr = (state.config.sign_expected_pcr == ConfigFeature.enabled or
+                            (state.config.sign_expected_pcr == ConfigFeature.auto and
+                                shutil.which("systemd-measure") is not None))
+
+        if sign_expected_pcr:
+            cmd += [
+                "--pcr-private-key", state.config.secure_boot_key,
+                "--pcr-banks", "sha1,sha256",
+            ]
+
+    cmd += ["build", "--linux", state.root / kimg]
+
+    for initrd in initrds:
+        cmd += ["--initrd", initrd]
+
+    run(cmd)
+
+
+def install_uki(state: MkosiState, 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 the ESP.
     # sd-boot iterates through them and shows them in the menu. These "unified" single-file images have the
@@ -996,7 +1078,7 @@ def install_unified_kernel(state: MkosiState, partitions: Sequence[Partition]) -
         shutil.copy(state.root / kimg, state.staging / state.config.output_split_kernel)
         break
 
-    if state.config.output_format == OutputFormat.cpio and state.config.bootable == ConfigFeature.auto:
+    if state.config.output_format in (OutputFormat.cpio, OutputFormat.uki) and state.config.bootable == ConfigFeature.auto:
         return
 
     roothash = finalize_roothash(partitions)
@@ -1026,86 +1108,14 @@ def install_unified_kernel(state: MkosiState, partitions: Sequence[Partition]) -
             else:
                 boot_binary = state.root / f"efi/EFI/Linux/{image_id}-{kver}{boot_count}.efi"
 
-            if (state.root / "etc/kernel/cmdline").exists():
-                cmdline = [(state.root / "etc/kernel/cmdline").read_text().strip()]
-            elif (state.root / "usr/lib/kernel/cmdline").exists():
-                cmdline = [(state.root / "usr/lib/kernel/cmdline").read_text().strip()]
-            else:
-                cmdline = []
-
-            if roothash:
-                cmdline += [roothash]
-
-            cmdline += state.config.kernel_command_line
-
-            # Older versions of systemd-stub expect the cmdline section to be null terminated. We can't embed
-            # nul terminators in argv so let's communicate the cmdline via a file instead.
-            (state.workspace / "cmdline").write_text(f"{' '.join(cmdline).strip()}\x00")
-
-            stub = state.root / f"usr/lib/systemd/boot/efi/linux{state.config.architecture.to_efi()}.efi.stub"
-            if not stub.exists():
-                die(f"sd-stub not found at /{stub.relative_to(state.root)} in the image")
-
-            cmd: list[PathString] = [
-                shutil.which("ukify") or "/usr/lib/systemd/ukify",
-                "--cmdline", f"@{state.workspace / 'cmdline'}",
-                "--os-release", f"@{state.root / 'usr/lib/os-release'}",
-                "--stub", stub,
-                "--output", boot_binary,
-                "--efi-arch", state.config.architecture.to_efi(),
-            ]
-
-            if not state.config.tools_tree:
-                for p in state.config.extra_search_paths:
-                    cmd += ["--tools", p]
-
-            uki_config = state.pkgmngr / "etc/kernel/uki.conf"
-            if uki_config.exists():
-                cmd += ["--config", uki_config]
-
-            if state.config.secure_boot:
-                assert state.config.secure_boot_key
-                assert state.config.secure_boot_certificate
-
-                cmd += ["--sign-kernel"]
-
-                if state.config.secure_boot_sign_tool != SecureBootSignTool.pesign:
-                    cmd += [
-                        "--signtool", "sbsign",
-                        "--secureboot-private-key", state.config.secure_boot_key,
-                        "--secureboot-certificate", state.config.secure_boot_certificate,
-                    ]
-                else:
-                    pesign_prepare(state)
-                    cmd += [
-                        "--signtool", "pesign",
-                        "--secureboot-certificate-dir", state.workspace / "pesign",
-                        "--secureboot-certificate-name", certificate_common_name(state.config.secure_boot_certificate),
-                    ]
-
-                sign_expected_pcr = (state.config.sign_expected_pcr == ConfigFeature.enabled or
-                                    (state.config.sign_expected_pcr == ConfigFeature.auto and
-                                     shutil.which("systemd-measure") is not None))
-
-                if sign_expected_pcr:
-                    cmd += [
-                        "--pcr-private-key", state.config.secure_boot_key,
-                        "--pcr-banks", "sha1,sha256",
-                    ]
-
-            cmd += ["build", "--linux", state.root / kimg]
-
-            for initrd in initrds:
-                cmd += ["--initrd", initrd]
-
             if state.config.kernel_modules_initrd:
-                cmd += ["--initrd", build_kernel_modules_initrd(state, kver)]
+                initrds += [build_kernel_modules_initrd(state, kver)]
 
             # Make sure the parent directory where we'll be writing the UKI exists.
             with umask(~0o700):
                 boot_binary.parent.mkdir(parents=True, exist_ok=True)
 
-            run(cmd)
+            build_uki(state, kimg, initrds, boot_binary, roothash=roothash)
 
             if not (state.staging / state.config.output_split_initrd).exists():
                 # Extract the combined initrds from the UKI so we can use it to direct kernel boot with qemu
@@ -1128,6 +1138,21 @@ def install_unified_kernel(state: MkosiState, partitions: Sequence[Partition]) -
         die("A bootable image was requested but no kernel was found")
 
 
+def make_uki(state: MkosiState, output: Path) -> None:
+    make_cpio(state.root, state.staging / state.config.output_split_initrd)
+    maybe_compress(state.config, state.config.compress_output,
+                   state.staging / state.config.output_split_initrd,
+                   state.staging / state.config.output_split_initrd)
+
+    try:
+        _, kimg = next(gen_kernel_images(state))
+    except StopIteration:
+        die("A kernel must be installed in the image to build a UKI")
+
+    build_uki(state, kimg, [state.staging / state.config.output_split_initrd], output)
+    extract_pe_section(state, output, ".linux", state.staging / state.config.output_split_kernel)
+
+
 def compressor_command(compression: Compression) -> list[PathString]:
     """Returns a command suitable for compressing archives."""
 
@@ -1810,7 +1835,7 @@ def build_image(args: MkosiArgs, config: MkosiConfig) -> None:
 
         normalize_mtime(state.root, state.config.source_date_epoch)
         partitions = make_image(state, skip=("esp", "xbootldr"))
-        install_unified_kernel(state, partitions)
+        install_uki(state, partitions)
         prepare_grub_efi(state)
         prepare_grub_bios(state, partitions)
         normalize_mtime(state.root, state.config.source_date_epoch, directory=Path("boot"))
@@ -1823,12 +1848,15 @@ def build_image(args: MkosiArgs, config: MkosiConfig) -> None:
             make_tar(state.root, state.staging / state.config.output_with_format)
         elif state.config.output_format == OutputFormat.cpio:
             make_cpio(state.root, state.staging / state.config.output_with_format)
+        elif state.config.output_format == OutputFormat.uki:
+            make_uki(state, state.staging / state.config.output_with_format)
         elif state.config.output_format == OutputFormat.directory:
             state.root.rename(state.staging / state.config.output_with_format)
 
-        maybe_compress(state.config, state.config.compress_output,
-                       state.staging / state.config.output_with_format,
-                       state.staging / state.config.output_with_compression)
+        if config.output_format != OutputFormat.uki:
+            maybe_compress(state.config, state.config.compress_output,
+                           state.staging / state.config.output_with_format,
+                           state.staging / state.config.output_with_compression)
 
         copy_nspawn_settings(state)
         calculate_sha256sum(state)
index 399063dd42b56933445cc376aa2998170a135d62..3aac18daf5f6c8adefd8e0ecb728c50719180383 100644 (file)
@@ -86,6 +86,7 @@ class OutputFormat(StrEnum):
     tar       = enum.auto()
     cpio      = enum.auto()
     disk      = enum.auto()
+    uki       = enum.auto()
     none      = enum.auto()
 
 
@@ -285,7 +286,7 @@ def config_parse_source_date_epoch(value: Optional[str], old: Optional[int]) ->
 
 
 def config_default_compression(namespace: argparse.Namespace) -> Compression:
-    if namespace.output_format == OutputFormat.cpio:
+    if namespace.output_format in (OutputFormat.cpio, OutputFormat.uki):
         return Compression.xz if namespace.distribution.is_centos_variant() and int(namespace.release) <= 8 else Compression.zst
     else:
         return Compression.none
@@ -751,7 +752,8 @@ class MkosiConfig:
         output += {
             OutputFormat.disk: ".raw",
             OutputFormat.cpio: ".cpio",
-            OutputFormat.tar: ".tar",
+            OutputFormat.tar:  ".tar",
+            OutputFormat.uki:  ".efi",
         }.get(self.output_format, "")
 
         return output
@@ -760,7 +762,7 @@ class MkosiConfig:
     def output_with_compression(self) -> str:
         output = self.output_with_format
 
-        if self.compress_output:
+        if self.compress_output and self.output_format != OutputFormat.uki:
             output += f".{self.compress_output}"
 
         return output
index 0557dab7531c9dc32684abd49f0cddc2afe42e20..23d09febf03205c786737a569791a894f0e304ab 100644 (file)
@@ -312,8 +312,10 @@ def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None:
                  "--offline=yes",
                  fname])
 
-        if firmware == QemuFirmware.direct or config.output_format == OutputFormat.cpio:
-            if config.qemu_kernel:
+        if firmware == QemuFirmware.direct or config.output_format in (OutputFormat.cpio, OutputFormat.uki):
+            if config.output_format == OutputFormat.uki:
+                kernel = fname
+            elif config.qemu_kernel:
                 kernel = config.qemu_kernel
             elif "-kernel" not in args.cmdline:
                 kernel = config.output_dir / config.output_split_kernel
@@ -338,7 +340,7 @@ def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None:
 
         if config.output_format == OutputFormat.cpio:
             cmdline += ["-initrd", fname]
-        else:
+        elif config.output_format == OutputFormat.disk:
             if firmware == QemuFirmware.direct:
                 cmdline += ["-initrd", config.output_dir / config.output_split_initrd]
 
index b3cf92d4046daecdf3ec9ddd5213245b8a7d8029..dfe048207870e06d18940f83609454cf701aac77 100644 (file)
@@ -416,12 +416,12 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 `Format=`, `--format=`, `-t`
 
-: The image format type to generate. One of `directory` (for generating OS
-  images inside a local directory), `tar` (similar, but a tarball of the
-  image is generated), `cpio` (similar, but a cpio archive is generated),
-  `disk` (a block device image with a GPT partition table) or `none`
-  (the image is solely intended as a build image to produce another
-  artifact).
+: The image format type to generate. One of `directory` (for generating
+  OS images inside a local directory), `tar` (similar, but a tarball of
+  the image is generated), `cpio` (similar, but a cpio archive is
+  generated), `disk` (a block device image with a GPT partition table),
+  `uki` (a unified kernel image) or `none` (the image is solely intended
+  as a build image to produce another artifact).
 
 `ManifestFormat=`, `--manifest-format=`