Repository: systemd/mkosi

Issue:
`KernelModulesIncludeHost=` removes microcode files
Host OS: Arch Linux
`uname`: Linux 6.7.9-arch1-1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
`mkosi`: v22

# Description

Using `KernelModulesIncludeHost=` causes the `/usr/lib/firmware/*-ucode/` directories to be removed since no kernel module depends on them, which unfortunately happens before `mkosi` includes the microcode for UKI/CPIO outputs.


Discussion:

Resolving patch:
diff --git a/mkosi/__init__.py b/mkosi/__init__.py
--- a/mkosi/__init__.py
+++ b/mkosi/__init__.py
@@ -2252,8 +2252,7 @@ def install_kernel(context: Context, partitions: Sequence[Partition]) -> None:
             break
 
 
-def make_uki(context: Context, stub: Path, kver: str, kimg: Path, output: Path) -> None:
-    microcode = build_microcode_initrd(context)
+def make_uki(context: Context, stub: Path, kver: str, kimg: Path, microcode: Optional[Path], output: Path) -> None:
     make_cpio(context.root, context.workspace / "initrd", tools=context.config.tools(), sandbox=context.sandbox)
     maybe_compress(context, context.config.compress_output, context.workspace / "initrd", context.workspace / "initrd")
 
@@ -2974,9 +2973,9 @@ def reuse_cache(context: Context) -> bool:
     return True
 
 
-def save_uki_components(context: Context) -> tuple[Optional[Path], Optional[str], Optional[Path]]:
+def save_uki_components(context: Context) -> tuple[Optional[Path], Optional[str], Optional[Path], Optional[Path]]:
     if context.config.output_format not in (OutputFormat.uki, OutputFormat.esp):
-        return None, None, None
+        return None, None, None, None
 
     try:
         kver, kimg = next(gen_kernel_images(context))
@@ -2993,8 +2992,9 @@ def save_uki_components(context: Context) -> tuple[Optional[Path], Optional[str]
         die(f"sd-stub not found at /{stub.relative_to(context.root)} in the image")
 
     stub = shutil.copy2(stub, context.workspace)
+    microcode = build_microcode_initrd(context)
 
-    return stub, kver, kimg
+    return stub, kver, kimg, microcode
 
 
 def make_image(
@@ -3419,9 +3419,8 @@ def build_image(context: Context) -> None:
         run_firstboot(context)
         run_hwdb(context)
 
-        # These might be removed by the next steps,
-        # so let's save them for later if needed.
-        stub, kver, kimg = save_uki_components(context)
+        # These might be removed by the next steps, so let's save them for later if needed.
+        stub, kver, kimg, microcode = save_uki_components(context)
 
         remove_packages(context)
 
@@ -3463,10 +3462,10 @@ def build_image(context: Context) -> None:
         )
     elif context.config.output_format == OutputFormat.uki:
         assert stub and kver and kimg
-        make_uki(context, stub, kver, kimg, context.staging / context.config.output_with_format)
+        make_uki(context, stub, kver, kimg, microcode, context.staging / context.config.output_with_format)
     elif context.config.output_format == OutputFormat.esp:
         assert stub and kver and kimg
-        make_uki(context, stub, kver, kimg, context.staging / context.config.output_split_uki)
+        make_uki(context, stub, kver, kimg, microcode, context.staging / context.config.output_split_uki)
         make_esp(context, context.staging / context.config.output_split_uki)
     elif context.config.output_format.is_extension_image():
         make_extension_image(context, context.staging / context.config.output_with_format)
diff --git a/mkosi/kmod.py b/mkosi/kmod.py
--- a/mkosi/kmod.py
+++ b/mkosi/kmod.py
@@ -220,11 +220,14 @@ def process_kernel_modules(
                 continue
 
             logging.debug(f"Removing module {m}")
-            (root / m).unlink()
+            m.unlink()
 
         for fw in (m for m in (root / "usr/lib/firmware").rglob("*") if not m.is_dir()):
             if fw in required:
                 continue
 
+            if any(fw.is_relative_to(root / "usr/lib/firmware" / d) for d in ("amd-ucode", "intel-ucode")):
+                continue
+
             logging.debug(f"Removing firmware {fw}")
-            (root / fw).unlink()
+            fw.unlink()


Test patch: