Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mkosi-addon and kernel-install plugin #3263

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading