Skip to content
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
64 changes: 56 additions & 8 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2136,7 +2136,6 @@ def install_uki(
kimg: Path,
token: str,
partitions: Sequence[Partition],
profiles: Sequence[Path],
cmdline: list[str],
) -> dict[str, Any]:
boot_binary = context.root / finalize_uki_path(
Expand Down Expand Up @@ -2169,6 +2168,10 @@ def install_uki(
if context.config.kernel_modules_initrd:
initrds += [build_kernel_modules_initrd(context, kver)]

# UKI profiles sections are replacements, so an initrd section needs to have
# a full copy of the main initrd (plus profile-specific packages)
profiles = build_uki_profiles(context, cmdline, microcodes + initrds)

pcrs = build_uki(
context,
systemd_stub_binary(context),
Expand Down Expand Up @@ -2211,22 +2214,65 @@ def systemd_addon_stub_binary(context: Context) -> Path:
return stub


def build_uki_profiles(context: Context, cmdline: Sequence[str]) -> list[Path]:
def build_uki_profile_initrd(
context: Context,
profile_id: str,
packages: Sequence[str],
base_initrds: Sequence[Path],
) -> Path:
initrd = context.workspace / f"uki-profiles/{profile_id}.initrd"
delta = context.workspace / f"uki-profiles/{profile_id}.delta"

with complete_step(f"Building initrd for UKI profile '{profile_id}'"):
with setup_build_overlay(context, volatile=True):
context.config.distribution.installer.install_packages(context, packages)
assert context.upperdir
make_cpio(Path(context.upperdir), delta, sandbox=context.sandbox)

# Join the base initrds with the delta from the profile, as each profile's initrd
# is a replacement of the base initrd. The delta comes last so its files take precedence.
join_initrds([*base_initrds, delta], initrd)
delta.unlink()

maybe_compress(
context,
context.config.compress_output,
initrd,
initrd,
)

return initrd


def build_uki_profiles(
context: Context,
cmdline: Sequence[str],
base_initrds: Sequence[Path],
) -> list[Path]:
if 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()
(context.workspace / "uki-profiles").mkdir(exist_ok=True)

profiles = []

for profile in context.config.unified_kernel_image_profiles:
id = profile.profile["ID"]
output = context.workspace / f"uki-profiles/{id}.efi"

arguments: list[PathString] = []
options: list[PathString] = []

# Build an initrd from packages if specified for this profile
if profile.packages:
initrd = build_uki_profile_initrd(context, id, profile.packages, base_initrds)
arguments += ["--initrd", workdir(initrd)]
options += ["--ro-bind", initrd, workdir(initrd)]

profile_section = context.workspace / f"uki-profiles/{id}.profile"

with profile_section.open("w") as f:
Expand All @@ -2242,8 +2288,8 @@ def build_uki_profiles(context: Context, cmdline: Sequence[str]) -> list[Path]:
stub,
output,
cmdline=[*cmdline, *profile.cmdline],
arguments=["--profile", f"@{profile_section}"],
options=["--ro-bind", profile_section, profile_section],
arguments=[*arguments, "--profile", f"@{profile_section}"],
options=[*options, "--ro-bind", profile_section, profile_section],
sign=False,
)

Expand Down Expand Up @@ -2274,15 +2320,14 @@ def install_kernel(context: Context, partitions: Sequence[Partition]) -> None:

token = find_entry_token(context)
cmdline = finalize_cmdline(context, partitions, finalize_roothash(partitions))
profiles = build_uki_profiles(context, cmdline) if want_uki(context) else []
# The first processed UKI is the one that will be used as split artifact, so take pcrs from
# it and ignore the rest
# TODO: we should probably support signing pcrs for all built UKIs
pcrs: dict[str, Any] = {}

for kver, kimg in gen_kernel_images(context):
if want_uki(context):
pcrs = pcrs or install_uki(context, kver, kimg, token, partitions, profiles, cmdline)
pcrs = pcrs or install_uki(context, kver, kimg, token, partitions, cmdline)
if not want_uki(context) or want_grub_bios(context, partitions):
install_type1(context, kver, kimg, token, partitions, cmdline)

Expand Down Expand Up @@ -2310,6 +2355,9 @@ def make_uki(
)

initrds = [context.workspace / "initrd"]
# UKI profiles sections are replacements, so an initrd section needs to have
# a full copy of the main initrd (plus profile-specific packages)
profiles = build_uki_profiles(context, context.config.kernel_command_line, microcode + initrds)
pcrs = build_uki(
context,
stub,
Expand All @@ -2318,7 +2366,7 @@ def make_uki(
microcode,
initrds,
context.config.kernel_command_line,
build_uki_profiles(context, context.config.kernel_command_line),
profiles,
output,
)

Expand Down
9 changes: 9 additions & 0 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,7 @@ class UKIProfile:
profile: dict[str, str]
cmdline: list[str]
sign_expected_pcr: bool
packages: list[str]


def make_simple_config_parser(
Expand Down Expand Up @@ -2637,6 +2638,13 @@ def parse_kernel_module_filter_regexp(p: str) -> str:
parse=config_parse_boolean,
default=True,
),
ConfigSetting(
dest="packages",
metavar="PACKAGE",
section="UKIProfile",
parse=config_make_list_parser(delimiter=",", key=package_sort_key),
help="Add an additional package to the UKI Profile's initrd",
),
]


Expand Down Expand Up @@ -5948,6 +5956,7 @@ def uki_profile_transformer(
UKIProfile(
profile=profile["Profile"],
cmdline=profile["Cmdline"],
packages=profile["Packages"],
sign_expected_pcr=profile["SignExpectedPcr"],
)
for profile in profiles
Expand Down
7 changes: 7 additions & 0 deletions mkosi/resources/man/mkosi.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -2266,6 +2266,13 @@ settings can be specified in the `UKIProfile` section:
`.cmdline` section and the extra kernel command line arguments
specified with this setting.

`Packages=`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So while this does end in it's own structure in the config, I think calling this Packages= has the (only very slight!) potential for hard to debug errors, since during config parsing we do pass around dictionaries.

More importantly, though, we already have InitrdPackages= which would make this a bit confusing, since that's for the packages in the initrd.

How about ExtraInitrdPackages= to clearly communicate that this is different and that this is additive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather leave it as-is, given it's a different section and it has the same syntax as the other it seems clearer. More importantly OBS won't parse a new setting for dependency resolution, so that would be a massive pain to change

: Install the specified distribution packages (i.e. RPM, deb, …) in the
initrd, on top of the base profile's initrd. Takes a comma-separated list
of package specifications. This option may be used multiple times in which
case the specified package lists are combined. The types and syntax is the
same as the `Content` section's `Packages=` setting.

`SignExpectedPcr=`
: Sign expected PCR measurements for this UKI profile. Takes a boolean.
Enabled by default.
Expand Down
4 changes: 4 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ def test_config() -> None:
"Cmdline": [
"key=value"
],
"Packages": [
"mypackage"
],
"Profile": {
"key": "value"
},
Expand Down Expand Up @@ -596,6 +599,7 @@ def test_config() -> None:
UKIProfile(
profile={"key": "value"},
cmdline=["key=value"],
packages=["mypackage"],
sign_expected_pcr=True,
)
],
Expand Down