Skip to content

Commit

Permalink
Add mkosi-addon and kernel-install plugin
Browse files Browse the repository at this point in the history
Change mkosi-initrd to avoid adding local customizations
(crypttab, kmods, fw) to the UKI by default, with an --host-only
override.
Add new mkosi-addon and kernel-install plugin to build local
customizations into an EFI addon instead.

This allows us to move closer to the desired goal of having
universal UKIs, built by vendors, used together with locally
built enhancements.
  • Loading branch information
bluca committed Dec 7, 2024
1 parent d310436 commit 7319fcc
Show file tree
Hide file tree
Showing 14 changed files with 527 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.cache-pre-inst
.cache
.mkosi.1
.mkosi-addon.1
.mkosi-initrd.1
.mkosi-sandbox.1
.mypy_cache/
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,16 @@ mkosi binary and is not to be considered a public API.

## kernel-install plugin

mkosi can also be used as a kernel-install plugin to build initrds. To
enable this feature, install `kernel-install/50-mkosi.install`
into `/usr/lib/kernel/install.d`. Extra distro configuration for the
initrd can be configured in `/usr/lib/mkosi-initrd`. Users can add their
own customizations in `/etc/mkosi-initrd`.
mkosi can also be used as a kernel-install plugin to build initrds and addons.
To enable these features, install `kernel-install/50-mkosi.install` and
`kernel-install/51-mkosi-addon.install` into `/usr/lib/kernel/install.d`.
Extra distro configuration for the initrd can be configured in
`/usr/lib/mkosi-initrd`. Users can add their own customizations in
`/etc/mkosi-initrd` and `/etc/mkosi-addon`.

Once installed, the mkosi plugin can be enabled by writing
`initrd_generator=mkosi-initrd` to `/usr/lib/kernel/install.conf` or to
`/etc/kernel/install.conf`.
`initrd_generator=mkosi-initrd` and `layout=uki` to `/usr/lib/kernel/install.conf`
or to `/etc/kernel/install.conf`.

# Hacking on mkosi

Expand Down
1 change: 1 addition & 0 deletions bin/mkosi-addon
111 changes: 111 additions & 0 deletions kernel-install/51-mkosi-addon.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later

import argparse
import dataclasses
import logging
import os
import sys
import tempfile
from pathlib import Path
from typing import Optional

from mkosi import identify_cpu
from mkosi.archive import make_cpio
from mkosi.config import OutputFormat
from mkosi.log import die, log_setup
from mkosi.run import run, uncaught_exception_handler
from mkosi.sandbox import __version__, umask
from mkosi.types import PathString


@dataclasses.dataclass(frozen=True)
class Context:
command: str
kernel_version: str
entry_dir: Path
kernel_image: Path
staging_area: Path
layout: str
image_type: str
verbose: bool


def we_are_wanted(context: Context) -> bool:
return context.layout == "uki"


def mandatory_variable(name: str) -> str:
try:
return os.environ[name]
except KeyError:
die(f"${name} must be set in the environment")


@uncaught_exception_handler()
def main() -> None:
log_setup()

parser = argparse.ArgumentParser(
description="kernel-install plugin to build local addon for initrd/cmdline/ucode",
allow_abbrev=False,
usage="51-mkosi-addon.install COMMAND KERNEL_VERSION ENTRY_DIR KERNEL_IMAGE…",
)

parser.add_argument(
"command",
metavar="COMMAND",
help="The action to perform. Only 'add' is supported.",
)
parser.add_argument(
"kernel_version",
metavar="KERNEL_VERSION",
help="Kernel version string",
)
parser.add_argument(
"entry_dir",
metavar="ENTRY_DIR",
type=Path,
nargs="?",
help="Type#1 entry directory (ignored)",
)
parser.add_argument(
"kernel_image",
metavar="KERNEL_IMAGE",
type=Path,
nargs="?",
help="Kernel image",
)
parser.add_argument(
"--version",
action="version",
version=f"mkosi {__version__}",
)

context = Context(
**vars(parser.parse_args()),
staging_area=Path(mandatory_variable("KERNEL_INSTALL_STAGING_AREA")),
layout=mandatory_variable("KERNEL_INSTALL_LAYOUT"),
image_type=mandatory_variable("KERNEL_INSTALL_IMAGE_TYPE"),
verbose=int(os.getenv("KERNEL_INSTALL_VERBOSE", 0)) > 0,
)

if context.command != "add" or not context.layout == "uki":
return

cmdline: list[PathString] = [
"mkosi-addon",
"--output", "mkosi-local.addon.efi",
"--output-dir", context.staging_area / "uki.efi.extra.d",
] # fmt: skip

if context.verbose:
cmdline += ["--debug"]

logging.info(f"Building mkosi-local.addon.efi")

run(cmdline, stdin=sys.stdin, stdout=sys.stdout)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions mkosi-addon
102 changes: 89 additions & 13 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def remove_files(context: Context) -> None:


def install_distribution(context: Context) -> None:
if context.config.base_trees:
if context.config.base_trees or context.config.output_format == OutputFormat.addon:
if not context.config.packages:
return

Expand Down Expand Up @@ -288,7 +288,7 @@ def remove_packages(context: Context) -> None:


def check_root_populated(context: Context) -> None:
if context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.output_format in (OutputFormat.sysext, OutputFormat.confext, OutputFormat.addon):
return

"""Check that the root was populated by looking for a os-release file."""
Expand All @@ -308,7 +308,11 @@ def configure_os_release(context: Context) -> None:
if not (context.config.image_id or context.config.image_version or context.config.hostname):
return

if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.overlay or context.config.output_format in (
OutputFormat.sysext,
OutputFormat.confext,
OutputFormat.addon,
):
return

for candidate in ["usr/lib/os-release", "usr/lib/initrd-release", "etc/os-release"]:
Expand Down Expand Up @@ -2084,7 +2088,7 @@ def install_kernel(context: Context, partitions: Sequence[Partition]) -> None:
# single-file images have the benefit that they can be signed like normal EFI binaries, and can
# encode everything necessary to boot a specific root device, including the root hash.

if context.config.output_format in (OutputFormat.uki, OutputFormat.esp):
if context.config.output_format in (OutputFormat.uki, OutputFormat.esp, OutputFormat.addon):
return

if context.config.bootable == ConfigFeature.disabled:
Expand Down Expand Up @@ -2149,6 +2153,51 @@ def make_uki(
extract_pe_section(context, output, ".initrd", context.staging / context.config.output_split_initrd)


def make_addon(context: Context, microcodes: list[Path], output: Path) -> None:
make_cpio(context.root, context.workspace / "initrd", sandbox=context.sandbox)
maybe_compress(
context, context.config.compress_output, context.workspace / "initrd", context.workspace / "initrd"
)

arch = context.config.architecture.to_efi()
stub = Path(f"/usr/lib/systemd/boot/efi/addon{arch}.efi.stub")
if not stub.exists():
die("sd-stub not found")

arguments: list[PathString] = [
"--initrd",
workdir(context.workspace / "initrd"),
]
options: list[PathString] = [
"--ro-bind",
context.workspace / "initrd",
workdir(context.workspace / "initrd"),
]

if microcodes:
# new .ucode section support?
check_ukify(
context.config,
version="256~devel",
reason="build addon with .ucode section support",
hint=("Use ToolsTree=default to download most required tools including ukify " "automatically"),
)

for microcode in microcodes:
arguments += ["--microcode", workdir(microcode)]
options += ["--ro-bind", microcode, workdir(microcode)]

with complete_step(f"Generating PE addon {output}"):
run_ukify(
context,
stub,
output,
cmdline=context.config.kernel_command_line,
arguments=arguments,
options=options,
)


def compressor_command(context: Context, compression: Compression) -> list[PathString]:
"""Returns a command suitable for compressing archives."""

Expand Down Expand Up @@ -2590,7 +2639,7 @@ def check_tools(config: Config, verb: Verb) -> None:
check_tool(config, "depmod", reason="generate kernel module dependencies")

if want_efi(config):
if config.unified_kernel_image_profiles:
if config.unified_kernel_image_profiles or config.output_format == OutputFormat.addon:
check_ukify(
config,
version="257~devel",
Expand Down Expand Up @@ -2726,7 +2775,11 @@ def configure_ssh(context: Context) -> None:


def configure_initrd(context: Context) -> None:
if context.config.overlay or context.config.output_format.is_extension_image():
if (
context.config.overlay
or context.config.output_format.is_extension_image()
or context.config.output_format == OutputFormat.addon
):
return

if (
Expand All @@ -2747,7 +2800,11 @@ def configure_initrd(context: Context) -> None:


def configure_clock(context: Context) -> None:
if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.overlay or context.config.output_format in (
OutputFormat.sysext,
OutputFormat.confext,
OutputFormat.addon,
):
return

with umask(~0o644):
Expand Down Expand Up @@ -2794,7 +2851,11 @@ def run_depmod(context: Context, *, cache: bool = False) -> None:


def run_sysusers(context: Context) -> None:
if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.overlay or context.config.output_format in (
OutputFormat.sysext,
OutputFormat.confext,
OutputFormat.addon,
):
return

if not context.config.find_binary("systemd-sysusers"):
Expand All @@ -2811,7 +2872,11 @@ def run_sysusers(context: Context) -> None:


def run_tmpfiles(context: Context) -> None:
if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.overlay or context.config.output_format in (
OutputFormat.sysext,
OutputFormat.confext,
OutputFormat.addon,
):
return

if not context.config.find_binary("systemd-tmpfiles"):
Expand Down Expand Up @@ -2853,7 +2918,11 @@ def run_tmpfiles(context: Context) -> None:


def run_preset(context: Context) -> None:
if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.overlay or context.config.output_format in (
OutputFormat.sysext,
OutputFormat.confext,
OutputFormat.addon,
):
return

if not context.config.find_binary("systemctl"):
Expand All @@ -2872,7 +2941,11 @@ def run_preset(context: Context) -> None:


def run_hwdb(context: Context) -> None:
if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
if context.config.overlay or context.config.output_format in (
OutputFormat.sysext,
OutputFormat.confext,
OutputFormat.addon,
):
return

if not context.config.find_binary("systemd-hwdb"):
Expand Down Expand Up @@ -3732,6 +3805,8 @@ def build_image(context: Context) -> None:
)
elif context.config.output_format == OutputFormat.cpio:
make_cpio(context.root, context.staging / context.config.output_with_format, sandbox=context.sandbox)
elif context.config.output_format == OutputFormat.addon:
make_addon(context, microcode, context.staging / context.config.output_with_format)
elif context.config.output_format == OutputFormat.uki:
assert stub and kver and kimg
make_uki(context, stub, kver, kimg, microcode, context.staging / context.config.output_with_format)
Expand All @@ -3744,7 +3819,7 @@ def build_image(context: Context) -> None:
elif context.config.output_format == OutputFormat.directory:
context.root.rename(context.staging / context.config.output_with_format)

if context.config.output_format not in (OutputFormat.uki, OutputFormat.esp):
if context.config.output_format not in (OutputFormat.uki, OutputFormat.esp, OutputFormat.addon):
maybe_compress(
context,
context.config.compress_output,
Expand Down Expand Up @@ -3795,7 +3870,7 @@ def run_sandbox(args: Args, config: Config) -> None:

def run_shell(args: Args, config: Config) -> None:
opname = "acquire shell in" if args.verb == Verb.shell else "boot"
if config.output_format in (OutputFormat.tar, OutputFormat.cpio):
if config.output_format in (OutputFormat.tar, OutputFormat.cpio, OutputFormat.addon):
die(f"Sorry, can't {opname} a {config.output_format} archive.")
if config.output_format.use_outer_compression() and config.compress_output:
die(f"Sorry, can't {opname} a compressed image.")
Expand Down Expand Up @@ -4552,6 +4627,7 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
if args.verb == Verb.documentation:
if args.cmdline:
manual = {
"addon": "mkosi-addon",
"initrd": "mkosi-initrd",
"sandbox": "mkosi-sandbox",
"news": "mkosi.news",
Expand Down
Loading

0 comments on commit 7319fcc

Please sign in to comment.