format_bytes,
parse_boolean,
parse_config,
- parse_ini,
summary,
systemd_tool_version,
want_selinux_relabel,
stub: Path,
output: Path,
*,
- cmdline: str = "",
+ cmdline: Sequence[str] = (),
arguments: Sequence[PathString] = (),
options: Sequence[PathString] = (),
sign: bool = True,
if not (arch := context.config.architecture.to_efi()):
die(f"Architecture {context.config.architecture} does not support UEFI")
- cmdline = cmdline.strip()
-
# 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.
- (context.workspace / "cmdline").write_text(f"{cmdline}\x00")
+ (context.workspace / "cmdline").write_text(f"{' '.join(cmdline)}\x00")
cmd = [
python_binary(context.config, binary=ukify),
options += ["--ro-bind", initrd, workdir(initrd)]
with complete_step(f"Generating unified kernel image for kernel version {kver}"):
- run_ukify(context, stub, output, cmdline=" ".join(cmdline), arguments=arguments, options=options)
+ run_ukify(context, stub, output, cmdline=cmdline, arguments=arguments, options=options)
def systemd_stub_binary(context: Context) -> Path:
addon_dir.mkdir(parents=True, exist_ok=True)
for addon in context.config.pe_addons:
- output = addon_dir / addon.with_suffix(".addon.efi").name
+ output = addon_dir / f"{addon.output}.addon.efi"
with complete_step(f"Generating PE addon /{output.relative_to(context.root)}"):
run_ukify(
context,
stub,
output,
- arguments=["--config", workdir(addon)],
- options=["--ro-bind", addon, workdir(addon)],
+ cmdline=addon.cmdline,
)
profiles = []
for profile in context.config.unified_kernel_image_profiles:
- output = context.workspace / "uki-profiles" / profile.with_suffix(".efi").name
-
- # We want to append the cmdline from the ukify config file to the base kernel command line so parse
- # it from the ukify config file and append it to our own kernel command line.
+ id = profile.profile["ID"]
+ output = context.workspace / f"uki-profiles/{id}.efi"
- profile_cmdline = ""
+ profile_section = context.workspace / f"uki-profiles/{id}.profile"
- for section, k, v in parse_ini(profile):
- if section == "UKI" and k == "Cmdline":
- profile_cmdline = v.replace("\n", " ")
+ with profile_section.open("w") as f:
+ for k, v in profile.profile.items():
+ f.write(f"{k}={v}\n")
- with complete_step(f"Generating UKI profile '{profile.stem}'"):
+ with complete_step(f"Generating UKI profile '{id}'"):
run_ukify(
context,
stub,
output,
- cmdline=f"{' '.join(cmdline)} {profile_cmdline}",
- arguments=["--config", workdir(profile)],
- options=["--ro-bind", profile, workdir(profile)],
+ cmdline=[*cmdline, *profile.cmdline],
+ arguments=["--profile", f"@{profile_section}"],
+ options=["--ro-bind", profile_section, profile_section],
sign=False,
)
if config.secure_boot_key_source != config.sign_expected_pcr_key_source:
die("Secure boot key source and expected PCR signatures key source have to be the same")
+ for addon in config.pe_addons:
+ if not addon.output:
+ die(
+ "PE addon configured without output filename",
+ hint="Use Output= to configure the output filename",
+ )
+
+ for profile in config.unified_kernel_image_profiles:
+ if "ID" not in profile.profile:
+ die(
+ "UKI Profile is missing ID key in its .profile section",
+ hint="Use Profile= to configure the profile ID",
+ )
+
def check_tool(config: Config, *tools: PathString, reason: str, hint: Optional[str] = None) -> Path:
tool = config.find_binary(*tools)
def config_make_list_parser(
- delimiter: str,
*,
+ delimiter: Optional[str] = None,
parse: Callable[[str], Any] = str,
unescape: bool = False,
reset: bool = True,
if unescape:
lex = shlex.shlex(value, posix=True)
lex.whitespace_split = True
- lex.whitespace = f"\n{delimiter}"
+ lex.whitespace = f"\n{delimiter or ''}"
lex.commenters = ""
values = list(lex)
if reset and not values:
return None
else:
- values = value.replace(delimiter, "\n").split("\n")
+ if delimiter:
+ value = value.replace(delimiter, "\n")
+ values = value.split("\n")
if reset and len(values) == 1 and values[0] == "":
return None
def config_make_dict_parser(
- delimiter: str,
*,
+ delimiter: Optional[str] = None,
parse: Callable[[str], tuple[str, Any]],
unescape: bool = False,
allow_paths: bool = False,
if unescape:
lex = shlex.shlex(value, posix=True)
lex.whitespace_split = True
- lex.whitespace = f"\n{delimiter}"
+ lex.whitespace = f"\n{delimiter or ''}"
lex.commenters = ""
values = list(lex)
if reset and not values:
return None
else:
- values = value.replace(delimiter, "\n").split("\n")
+ if delimiter:
+ value = value.replace(delimiter, "\n")
+ values = value.split("\n")
if reset and len(values) == 1 and values[0] == "":
return None
return (key, value)
-def parse_credential(value: str) -> tuple[str, str]:
+def parse_key_value(value: str) -> tuple[str, str]:
key, _, value = value.partition("=")
key, value = key.strip(), value.strip()
return (key, value)
)
+@dataclasses.dataclass(frozen=True)
+class PEAddon:
+ output: str
+ cmdline: list[str]
+
+
+@dataclasses.dataclass(frozen=True)
+class UKIProfile:
+ profile: dict[str, str]
+ cmdline: list[str]
+
+
+def make_simple_config_parser(settings: Sequence[ConfigSetting], type: type[Any]) -> Callable[[str], Any]:
+ lookup = {s.name: s for s in settings}
+
+ def parse_simple_config(value: str) -> Any:
+ path = parse_path(value)
+ config = argparse.Namespace()
+
+ for section, name, value in parse_ini(path, only_sections=[s.section for s in settings]):
+ if not name and not value:
+ continue
+
+ if not (s := lookup.get(name)):
+ die(f"Unknown setting {name}")
+
+ if section != s.section:
+ logging.warning(f"Setting {name} should be configured in [{s.section}], not [{section}].")
+
+ if name != s.name:
+ logging.warning(f"Setting {name} is deprecated, please use {s.name} instead.")
+
+ setattr(config, s.dest, s.parse(value, getattr(config, s.dest, None)))
+
+ return type(**{k: v for k, v in vars(config).items() if k in inspect.signature(type).parameters})
+
+ return parse_simple_config
+
+
@dataclasses.dataclass(frozen=True)
class Config:
"""Type-hinted storage for command line arguments.
shim_bootloader: ShimBootloader
unified_kernel_images: ConfigFeature
unified_kernel_image_format: str
- unified_kernel_image_profiles: list[Path]
+ unified_kernel_image_profiles: list[UKIProfile]
initrds: list[Path]
initrd_packages: list[str]
initrd_volatile_packages: list[str]
kernel_modules_include: list[str]
kernel_modules_exclude: list[str]
kernel_modules_include_host: bool
- pe_addons: list[Path]
+ pe_addons: list[PEAddon]
kernel_modules_initrd: bool
kernel_modules_initrd_include: list[str]
yield section, "", ""
+PE_ADDON_SETTINGS = (
+ ConfigSetting(
+ dest="output",
+ section="PEAddon",
+ parse=config_make_filename_parser("Output= requires a filename with no path components."),
+ default="",
+ ),
+ ConfigSetting(
+ dest="cmdline",
+ section="PEAddon",
+ parse=config_make_list_parser(delimiter=" "),
+ ),
+)
+
+
+UKI_PROFILE_SETTINGS = (
+ ConfigSetting(
+ dest="profile",
+ section="UKIProfile",
+ parse=config_make_dict_parser(parse=parse_key_value),
+ ),
+ ConfigSetting(
+ dest="cmdline",
+ section="UKIProfile",
+ parse=config_make_list_parser(delimiter=" "),
+ ),
+)
+
+
SETTINGS = (
# Include section
ConfigSetting(
long="--uki-profile",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(
+ delimiter=",",
+ parse=make_simple_config_parser(UKI_PROFILE_SETTINGS, UKIProfile),
+ ),
recursive_paths=("mkosi.uki-profiles/",),
help="Configuration files to generate UKI profiles",
),
long="--pe-addon",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(
+ delimiter=",",
+ parse=make_simple_config_parser(PE_ADDON_SETTINGS, PEAddon),
+ ),
recursive_paths=("mkosi.pe-addons/",),
help="Configuration files to generate PE addons",
),
long="--credential",
metavar="NAME=VALUE",
section="Host",
- parse=config_make_dict_parser(
- delimiter=" ", parse=parse_credential, allow_paths=True, unescape=True
- ),
+ parse=config_make_dict_parser(delimiter=" ", parse=parse_key_value, allow_paths=True, unescape=True),
help="Pass a systemd credential to systemd-nspawn or qemu",
paths=("mkosi.credentials",),
),
assert "Type" in keysource
return KeySource(type=KeySourceType(keysource["Type"]), source=keysource.get("Source", ""))
+ def pe_addon_transformer(addons: list[dict[str, Any]], fieldtype: type[PEAddon]) -> list[PEAddon]:
+ return [PEAddon(output=addon["Output"], cmdline=addon["Cmdline"]) for addon in addons]
+
+ def uki_profile_transformer(
+ profiles: list[dict[str, Any]],
+ fieldtype: type[UKIProfile],
+ ) -> list[UKIProfile]:
+ return [UKIProfile(profile=profile["Profile"], cmdline=profile["Cmdline"]) for profile in profiles]
+
# The type of this should be
# dict[
# type,
Network: enum_transformer,
KeySource: key_source_transformer,
Vmm: enum_transformer,
+ list[PEAddon]: pe_addon_transformer,
+ list[UKIProfile]: uki_profile_transformer,
}
def json_transformer(key: str, val: Any) -> Any:
`UnifiedKernelImageProfiles=`, `--uki-profile=`
: Build additional UKI profiles. Takes a comma separated list of paths
- to `ukify` config files. This option may be used multiple times in
+ to UKI profile config files. This option may be used multiple times in
which case each config gets built into a corresponding UKI profile.
Config files in the `mkosi.uki-profiles/` directory are
automatically picked up. All configured UKI profiles are added as
additional UKI profiles to each UKI built by mkosi.
+ See the documentation for the `UKIProfile` section for information
+ on which settings can be configured in UKI profile config files.
+
`PeAddons=`, `--pe-addon`
: Build additional PE addons. Takes a comma separated list of paths to
- `ukify` config files. This option may be used multiple times in which case
+ PE addon config files. This option may be used multiple times in which case
each config gets built into a corresponding addon. Each addon has the name
of the config file, with the extension replaced with `.addon.efi`.
Config files in the `mkosi.pe-addons/` directory are automatically picked
up.
+ See the documentation for the `PEAddon` section for information on
+ which settings can be configured in PE addon config files.
+
`Initrds=`, `--initrd`
: Use user-provided initrd(s). Takes a comma separated list of paths to initrd
files. This option may be used multiple times in which case the initrd lists
each individual subimage as if they were "universal" settings. See
the **Building multiple images** section for more information.
+### [PEAddon] Section
+
+The `PEAddon` section can be used in UKI profile config files which are
+passed to the `PEAddons=` setting. The following settings can be
+specified in the `PEAddon` section:
+
+`Output=`
+: The name the addon should have in the addons directory in the ESP.
+ The final name is the name specified here suffixed with
+ `.addon.efi`.
+
+`Cmdline=`
+: The kernel command line arguments to store in the `.cmdline` section
+ of the addon. Takes a space delimited list of extra kernel command
+ line arguments.
+
+### [UKIProfile] Section
+
+The `UKIProfile` section can be used in UKI profile config files which
+are passed to the `UnifiedKernelImageProfiles=` setting. The following
+settings can be specified in the `UKIProfile` section:
+
+`Profile=`
+: The contents of the `.profile` section of the UKI profile. Takes a
+ list of key/value pairs separated by `=`. The `ID=` key must be
+ specified. See the UKI [specification](https://uapi-group.org/specifications/specs/unified_kernel_image/#multi-profile-ukis)
+ for a full list of possible keys.
+
+`Cmdline=`
+: Extra kernel command line options for the UKI profile. Takes a space
+ delimited list of extra kernel command line arguments. Note that
+ the final `.cmdline` section will the combination of the base
+ `.cmdline` section and the extra kernel command line arguments
+ specified with this setting.
+
## Specifiers
The current value of various settings can be accessed when parsing
ManifestFormat,
Network,
OutputFormat,
+ PEAddon,
QemuDrive,
QemuFirmware,
QemuVsockCID,
SecureBootSignTool,
ShimBootloader,
+ UKIProfile,
Verb,
Vmm,
)
],
"Passphrase": null,
"PeAddons": [
- "/my-addon.conf"
+ {
+ "Cmdline": [
+ "key=value"
+ ],
+ "Output": "abc"
+ }
],
"PostInstallationScripts": [
"/bar/qux"
],
"UnifiedKernelImageFormat": "myuki",
"UnifiedKernelImageProfiles": [
- "/profile"
+ {
+ "Cmdline": [
+ "key=value"
+ ],
+ "Profile": {
+ "key": "value"
+ }
+ }
],
"UnifiedKernelImages": "auto",
"UnitProperties": [
packages=[],
pass_environment=["abc"],
passphrase=None,
- pe_addons=[Path("/my-addon.conf")],
+ pe_addons=[PEAddon(output="abc", cmdline=["key=value"])],
postinst_scripts=[Path("/bar/qux")],
postoutput_scripts=[Path("/foo/src")],
prepare_scripts=[Path("/run/foo")],
tools_tree_release=None,
tools_tree_repositories=["abc"],
unified_kernel_image_format="myuki",
- unified_kernel_image_profiles=[Path("/profile")],
+ unified_kernel_image_profiles=[UKIProfile(profile={"key": "value"}, cmdline=["key=value"])],
unified_kernel_images=ConfigFeature.auto,
unit_properties=["PROPERTY=VALUE"],
use_subvolumes=ConfigFeature.auto,