from mkosi.bubblewrap import bwrap, chroot_cmd
from mkosi.burn import run_burn
from mkosi.config import (
+ Args,
BiosBootloader,
Bootloader,
Compression,
+ Config,
ConfigFeature,
DocFormat,
+ JsonEncoder,
ManifestFormat,
- MkosiArgs,
- MkosiConfig,
- MkosiJsonEncoder,
OutputFormat,
SecureBootSignTool,
ShimBootloader,
summary,
yes_no,
)
+from mkosi.context import Context
from mkosi.distributions import Distribution
from mkosi.installer import clean_package_manager_metadata, package_manager_scripts
from mkosi.kmod import gen_required_kernel_modules, process_kernel_modules
from mkosi.partition import Partition, finalize_root, finalize_roothash
from mkosi.qemu import KernelType, QemuDeviceNode, copy_ephemeral, run_qemu, run_ssh
from mkosi.run import become_root, find_binary, fork_and_wait, init_mount_namespace, run
-from mkosi.state import MkosiState
from mkosi.tree import copy_tree, move_tree, rmtree
from mkosi.types import PathString
from mkosi.util import (
)
@contextlib.contextmanager
-def mount_base_trees(state: MkosiState) -> Iterator[None]:
- if not state.config.base_trees or not state.config.overlay:
+def mount_base_trees(context: Context) -> Iterator[None]:
+ if not context.config.base_trees or not context.config.overlay:
yield
return
with complete_step("Mounting base trees…"), contextlib.ExitStack() as stack:
bases = []
- (state.workspace / "bases").mkdir(exist_ok=True)
+ (context.workspace / "bases").mkdir(exist_ok=True)
- for path in state.config.base_trees:
- d = state.workspace / f"bases/{path.name}-{uuid.uuid4().hex}"
+ for path in context.config.base_trees:
+ d = context.workspace / f"bases/{path.name}-{uuid.uuid4().hex}"
if path.is_dir():
bases += [path]
elif path.suffix == ".tar":
- extract_tar(state, path, d)
+ extract_tar(context, path, d)
bases += [d]
elif path.suffix == ".raw":
- bwrap(state, ["systemd-dissect", "-M", path, d])
- stack.callback(lambda: bwrap(state, ["systemd-dissect", "-U", d]))
+ bwrap(context, ["systemd-dissect", "-M", path, d])
+ stack.callback(lambda: bwrap(context, ["systemd-dissect", "-U", d]))
bases += [d]
else:
die(f"Unsupported base tree source {path}")
- stack.enter_context(mount_overlay(bases, state.root, state.root))
+ stack.enter_context(mount_overlay(bases, context.root, context.root))
yield
-def remove_files(state: MkosiState) -> None:
+def remove_files(context: Context) -> None:
"""Remove files based on user-specified patterns"""
- if not state.config.remove_files:
+ if not context.config.remove_files:
return
with complete_step("Removing files…"):
- for pattern in state.config.remove_files:
- for p in state.root.glob(pattern.lstrip("/")):
+ for pattern in context.config.remove_files:
+ for p in context.root.glob(pattern.lstrip("/")):
rmtree(p)
-def install_distribution(state: MkosiState) -> None:
- if state.config.base_trees:
- if not state.config.packages:
+def install_distribution(context: Context) -> None:
+ if context.config.base_trees:
+ if not context.config.packages:
return
- with complete_step(f"Installing extra packages for {str(state.config.distribution).capitalize()}"):
- state.config.distribution.install_packages(state, state.config.packages)
+ with complete_step(f"Installing extra packages for {str(context.config.distribution).capitalize()}"):
+ context.config.distribution.install_packages(context, context.config.packages)
else:
- with complete_step(f"Installing {str(state.config.distribution).capitalize()}"):
- state.config.distribution.install(state)
+ with complete_step(f"Installing {str(context.config.distribution).capitalize()}"):
+ context.config.distribution.install(context)
- if not state.config.overlay:
- if not (state.root / "etc/machine-id").exists():
+ if not context.config.overlay:
+ if not (context.root / "etc/machine-id").exists():
# Uninitialized means we want it to get initialized on first boot.
with umask(~0o444):
- (state.root / "etc/machine-id").write_text("uninitialized\n")
+ (context.root / "etc/machine-id").write_text("uninitialized\n")
# Ensure /efi exists so that the ESP is mounted there, as recommended by
# https://0pointer.net/blog/linux-boot-partitions.html. Use the most restrictive access mode we
# can without tripping up mkfs tools since this directory is only meant to be overmounted and
# should not be read from or written to.
with umask(~0o500):
- (state.root / "efi").mkdir(exist_ok=True)
+ (context.root / "efi").mkdir(exist_ok=True)
- if state.config.packages:
- state.config.distribution.install_packages(state, state.config.packages)
+ if context.config.packages:
+ context.config.distribution.install_packages(context, context.config.packages)
for f in ("var/lib/systemd/random-seed",
"var/lib/systemd/credential.secret",
"var/lib/dbus/machine-id"):
# Using missing_ok=True still causes an OSError if the mount is read-only even if the
# file doesn't exist so do an explicit exists() check first.
- if (state.root / f).exists():
- (state.root / f).unlink()
+ if (context.root / f).exists():
+ (context.root / f).unlink()
-def install_build_packages(state: MkosiState) -> None:
- if not state.config.build_scripts or not state.config.build_packages:
+def install_build_packages(context: Context) -> None:
+ if not context.config.build_scripts or not context.config.build_packages:
return
# TODO: move to parenthesised context managers once on 3.10
- pd = str(state.config.distribution).capitalize()
- with complete_step(f"Installing build packages for {pd}"), mount_build_overlay(state):
- state.config.distribution.install_packages(state, state.config.build_packages)
+ pd = str(context.config.distribution).capitalize()
+ with complete_step(f"Installing build packages for {pd}"), mount_build_overlay(context):
+ context.config.distribution.install_packages(context, context.config.build_packages)
-def remove_packages(state: MkosiState) -> None:
+def remove_packages(context: Context) -> None:
"""Remove packages listed in config.remove_packages"""
- if not state.config.remove_packages:
+ if not context.config.remove_packages:
return
- with complete_step(f"Removing {len(state.config.remove_packages)} packages…"):
+ with complete_step(f"Removing {len(context.config.remove_packages)} packages…"):
try:
- state.config.distribution.remove_packages(state, state.config.remove_packages)
+ context.config.distribution.remove_packages(context, context.config.remove_packages)
except NotImplementedError:
- die(f"Removing packages is not supported for {state.config.distribution}")
+ die(f"Removing packages is not supported for {context.config.distribution}")
-def check_root_populated(state: MkosiState) -> None:
+def check_root_populated(context: Context) -> None:
"""Check that the root was populated by looking for a os-release file."""
- osrelease = state.root / "usr/lib/os-release"
+ osrelease = context.root / "usr/lib/os-release"
if not osrelease.exists():
die(
f"{osrelease} not found.",
)
-def configure_os_release(state: MkosiState) -> None:
+def configure_os_release(context: Context) -> None:
"""Write IMAGE_ID and IMAGE_VERSION to /usr/lib/os-release in the image."""
- if not state.config.image_id and not state.config.image_version:
+ if not context.config.image_id and not context.config.image_version:
return
- if state.config.overlay or state.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
+ if context.config.overlay or context.config.output_format in (OutputFormat.sysext, OutputFormat.confext):
return
for candidate in ["usr/lib/os-release", "etc/os-release", "usr/lib/initrd-release", "etc/initrd-release"]:
- osrelease = state.root / candidate
+ osrelease = context.root / candidate
# at this point we know we will either change or add to the file
newosrelease = osrelease.with_suffix(".new")
with osrelease.open("r") as old, newosrelease.open("w") as new:
# fix existing values
for line in old.readlines():
- if state.config.image_id and line.startswith("IMAGE_ID="):
- new.write(f'IMAGE_ID="{state.config.image_id}"\n')
+ if context.config.image_id and line.startswith("IMAGE_ID="):
+ new.write(f'IMAGE_ID="{context.config.image_id}"\n')
image_id_written = True
- elif state.config.image_version and line.startswith("IMAGE_VERSION="):
- new.write(f'IMAGE_VERSION="{state.config.image_version}"\n')
+ elif context.config.image_version and line.startswith("IMAGE_VERSION="):
+ new.write(f'IMAGE_VERSION="{context.config.image_version}"\n')
image_version_written = True
else:
new.write(line)
# append if they were missing
- if state.config.image_id and not image_id_written:
- new.write(f'IMAGE_ID="{state.config.image_id}"\n')
- if state.config.image_version and not image_version_written:
- new.write(f'IMAGE_VERSION="{state.config.image_version}"\n')
+ if context.config.image_id and not image_id_written:
+ new.write(f'IMAGE_ID="{context.config.image_id}"\n')
+ if context.config.image_version and not image_version_written:
+ new.write(f'IMAGE_VERSION="{context.config.image_version}"\n')
newosrelease.rename(osrelease)
-def configure_extension_release(state: MkosiState) -> None:
- if state.config.output_format not in (OutputFormat.sysext, OutputFormat.confext):
+def configure_extension_release(context: Context) -> None:
+ if context.config.output_format not in (OutputFormat.sysext, OutputFormat.confext):
return
- prefix = "SYSEXT" if state.config.output_format == OutputFormat.sysext else "CONFEXT"
- d = "usr/lib" if state.config.output_format == OutputFormat.sysext else "etc"
- p = state.root / d / f"extension-release.d/extension-release.{state.config.output}"
+ prefix = "SYSEXT" if context.config.output_format == OutputFormat.sysext else "CONFEXT"
+ d = "usr/lib" if context.config.output_format == OutputFormat.sysext else "etc"
+ p = context.root / d / f"extension-release.d/extension-release.{context.config.output}"
p.parent.mkdir(parents=True, exist_ok=True)
- osrelease = read_os_release(state.root)
+ osrelease = read_os_release(context.root)
extrelease = read_env_file(p) if p.exists() else {}
new = p.with_suffix(".new")
if "VERSION_ID" not in extrelease and (version := osrelease.get("VERSION_ID")):
f.write(f"VERSION_ID={version}\n")
- if f"{prefix}_ID" not in extrelease and state.config.image_id:
- f.write(f"{prefix}_ID={state.config.image_id}\n")
+ if f"{prefix}_ID" not in extrelease and context.config.image_id:
+ f.write(f"{prefix}_ID={context.config.image_id}\n")
- if f"{prefix}_VERSION_ID" not in extrelease and state.config.image_version:
- f.write(f"{prefix}_VERSION_ID={state.config.image_version}\n")
+ if f"{prefix}_VERSION_ID" not in extrelease and context.config.image_version:
+ f.write(f"{prefix}_VERSION_ID={context.config.image_version}\n")
if f"{prefix}_SCOPE" not in extrelease:
f.write(f"{prefix}_SCOPE=initrd system portable\n")
if "ARCHITECTURE" not in extrelease:
- f.write(f"ARCHITECTURE={state.config.architecture}\n")
+ f.write(f"ARCHITECTURE={context.config.architecture}\n")
new.rename(p)
-def configure_autologin_service(state: MkosiState, service: str, extra: str) -> None:
- dropin = state.root / f"usr/lib/systemd/system/{service}.d/autologin.conf"
+def configure_autologin_service(context: Context, service: str, extra: str) -> None:
+ dropin = context.root / f"usr/lib/systemd/system/{service}.d/autologin.conf"
with umask(~0o755):
dropin.parent.mkdir(parents=True, exist_ok=True)
with umask(~0o644):
)
-def configure_autologin(state: MkosiState) -> None:
- if not state.config.autologin:
+def configure_autologin(context: Context) -> None:
+ if not context.config.autologin:
return
with complete_step("Setting up autologin…"):
- configure_autologin_service(state, "console-getty.service",
+ configure_autologin_service(context, "console-getty.service",
"--noclear --keep-baud console 115200,38400,9600")
- configure_autologin_service(state, "getty@tty1.service",
+ configure_autologin_service(context, "getty@tty1.service",
"--noclear -")
- configure_autologin_service(state, "serial-getty@ttyS0.service",
+ configure_autologin_service(context, "serial-getty@ttyS0.service",
"--keep-baud 115200,57600,38400,9600 -")
- if state.config.architecture.default_serial_tty() != "ttyS0":
- configure_autologin_service(state,
- f"serial-getty@{state.config.architecture.default_serial_tty()}.service",
+ if context.config.architecture.default_serial_tty() != "ttyS0":
+ configure_autologin_service(context,
+ f"serial-getty@{context.config.architecture.default_serial_tty()}.service",
"--keep-baud 115200,57600,38400,9600 -")
@contextlib.contextmanager
-def mount_cache_overlay(state: MkosiState) -> Iterator[None]:
- if not state.config.incremental or not any(state.root.iterdir()):
+def mount_cache_overlay(context: Context) -> Iterator[None]:
+ if not context.config.incremental or not any(context.root.iterdir()):
yield
return
- d = state.workspace / "cache-overlay"
+ d = context.workspace / "cache-overlay"
with umask(~0o755):
d.mkdir(exist_ok=True)
- with mount_overlay([state.root], d, state.root):
+ with mount_overlay([context.root], d, context.root):
yield
@contextlib.contextmanager
-def mount_build_overlay(state: MkosiState, volatile: bool = False) -> Iterator[Path]:
- d = state.workspace / "build-overlay"
+def mount_build_overlay(context: Context, volatile: bool = False) -> Iterator[Path]:
+ d = context.workspace / "build-overlay"
if not d.is_symlink():
with umask(~0o755):
d.mkdir(exist_ok=True)
with contextlib.ExitStack() as stack:
- lower = [state.root]
+ lower = [context.root]
if volatile:
lower += [d]
else:
upper = d
- stack.enter_context(mount_overlay(lower, upper, state.root))
+ stack.enter_context(mount_overlay(lower, upper, context.root))
- yield state.root
+ yield context.root
@contextlib.contextmanager
-def finalize_source_mounts(config: MkosiConfig) -> Iterator[list[PathString]]:
+def finalize_source_mounts(config: Config) -> Iterator[list[PathString]]:
with contextlib.ExitStack() as stack:
mounts = [
(stack.enter_context(mount_overlay([source])) if config.build_sources_ephemeral else source, target)
def finalize_host_scripts(
- state: MkosiState,
+ context: Context,
helpers: dict[str, Sequence[PathString]], # FIXME: change dict to Mapping when PyRight is fixed
) -> contextlib.AbstractContextManager[Path]:
scripts: dict[str, Sequence[PathString]] = {}
if find_binary("git"):
scripts["git"] = ("git", "-c", "safe.directory=*")
if find_binary("useradd"):
- scripts["useradd"] = ("useradd", "--root", state.root)
- return finalize_scripts(scripts | helpers | package_manager_scripts(state))
+ scripts["useradd"] = ("useradd", "--root", context.root)
+ return finalize_scripts(scripts | helpers | package_manager_scripts(context))
-def finalize_chroot_scripts(state: MkosiState) -> contextlib.AbstractContextManager[Path]:
- git = {"git": ("git", "-c", "safe.directory=*")} if find_binary("git", root=state.root) else {}
+def finalize_chroot_scripts(context: Context) -> contextlib.AbstractContextManager[Path]:
+ git = {"git": ("git", "-c", "safe.directory=*")} if find_binary("git", root=context.root) else {}
return finalize_scripts(git)
-def run_prepare_scripts(state: MkosiState, build: bool) -> None:
- if not state.config.prepare_scripts:
+def run_prepare_scripts(context: Context, build: bool) -> None:
+ if not context.config.prepare_scripts:
return
- if build and not state.config.build_scripts:
+ if build and not context.config.build_scripts:
return
env = dict(
- ARCHITECTURE=str(state.config.architecture),
- BUILDROOT=str(state.root),
+ ARCHITECTURE=str(context.config.architecture),
+ BUILDROOT=str(context.root),
CHROOT_SCRIPT="/work/prepare",
CHROOT_SRCDIR="/work/src",
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
SCRIPT="/work/prepare",
SRCDIR=str(Path.cwd()),
- WITH_DOCS=one_zero(state.config.with_docs),
- WITH_NETWORK=one_zero(state.config.with_network),
- WITH_TESTS=one_zero(state.config.with_tests),
+ WITH_DOCS=one_zero(context.config.with_docs),
+ WITH_NETWORK=one_zero(context.config.with_network),
+ WITH_TESTS=one_zero(context.config.with_tests),
)
with contextlib.ExitStack() as stack:
if build:
- stack.enter_context(mount_build_overlay(state))
+ stack.enter_context(mount_build_overlay(context))
step_msg = "Running prepare script {} in build overlay…"
arg = "build"
else:
step_msg = "Running prepare script {}…"
arg = "final"
- sources = stack.enter_context(finalize_source_mounts(state.config))
- cd = stack.enter_context(finalize_chroot_scripts(state))
+ sources = stack.enter_context(finalize_source_mounts(context.config))
+ cd = stack.enter_context(finalize_chroot_scripts(context))
- for script in state.config.prepare_scripts:
+ for script in context.config.prepare_scripts:
helpers = {
"mkosi-chroot": chroot_cmd(
- state.root,
+ context.root,
resolve=True,
options=[
"--bind", script, "/work/prepare",
"mkosi-as-caller" : MKOSI_AS_CALLER,
}
- hd = stack.enter_context(finalize_host_scripts(state, helpers))
+ hd = stack.enter_context(finalize_host_scripts(context, helpers))
with complete_step(step_msg.format(script)):
bwrap(
- state,
+ context,
script_maybe_chroot(script, "/work/prepare") + [arg],
network=True,
options=sources + ["--ro-bind", script, script],
scripts=hd,
- env=env | state.config.environment,
+ env=env | context.config.environment,
stdin=sys.stdin,
)
-def run_build_scripts(state: MkosiState) -> None:
- if not state.config.build_scripts:
+def run_build_scripts(context: Context) -> None:
+ if not context.config.build_scripts:
return
env = dict(
- ARCHITECTURE=str(state.config.architecture),
- BUILDROOT=str(state.root),
+ ARCHITECTURE=str(context.config.architecture),
+ BUILDROOT=str(context.root),
CHROOT_DESTDIR="/work/dest",
CHROOT_OUTPUTDIR="/work/out",
CHROOT_SCRIPT="/work/build-script",
CHROOT_SRCDIR="/work/src",
- DESTDIR=str(state.install_dir),
+ DESTDIR=str(context.install_dir),
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
- OUTPUTDIR=str(state.staging),
+ OUTPUTDIR=str(context.staging),
SCRIPT="/work/build-script",
SRCDIR=str(Path.cwd()),
- WITH_DOCS=one_zero(state.config.with_docs),
- WITH_NETWORK=one_zero(state.config.with_network),
- WITH_TESTS=one_zero(state.config.with_tests),
+ WITH_DOCS=one_zero(context.config.with_docs),
+ WITH_NETWORK=one_zero(context.config.with_network),
+ WITH_TESTS=one_zero(context.config.with_tests),
)
- if state.config.build_dir is not None:
+ if context.config.build_dir is not None:
env |= dict(
- BUILDDIR=str(state.config.build_dir),
+ BUILDDIR=str(context.config.build_dir),
CHROOT_BUILDDIR="/work/build",
)
with (
- mount_build_overlay(state, volatile=True),
- finalize_chroot_scripts(state) as cd,
- finalize_source_mounts(state.config) as sources,
+ mount_build_overlay(context, volatile=True),
+ finalize_chroot_scripts(context) as cd,
+ finalize_source_mounts(context.config) as sources,
):
- for script in state.config.build_scripts:
+ for script in context.config.build_scripts:
helpers = {
"mkosi-chroot": chroot_cmd(
- state.root,
- resolve=state.config.with_network,
+ context.root,
+ resolve=context.config.with_network,
options=[
"--bind", script, "/work/build-script",
- "--bind", state.install_dir, "/work/dest",
- "--bind", state.staging, "/work/out",
+ "--bind", context.install_dir, "/work/dest",
+ "--bind", context.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
- *(["--bind", str(state.config.build_dir), "/work/build"] if state.config.build_dir else []),
+ *([
+ "--bind", os.fspath(context.config.build_dir), "/work/build"]
+ if context.config.build_dir
+ else []
+ ),
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
"--setenv", "DESTDIR", "/work/dest",
"--setenv", "OUTPUTDIR", "/work/out",
"--setenv", "BUILDROOT", "/",
- *(["--setenv", "BUILDDIR", "/work/build"] if state.config.build_dir else []),
+ *(["--setenv", "BUILDDIR", "/work/build"] if context.config.build_dir else []),
],
),
"mkosi-as-caller" : MKOSI_AS_CALLER,
}
- cmdline = state.args.cmdline if state.args.verb == Verb.build else []
+ cmdline = context.args.cmdline if context.args.verb == Verb.build else []
with (
- finalize_host_scripts(state, helpers) as hd,
+ finalize_host_scripts(context, helpers) as hd,
complete_step(f"Running build script {script}…"),
):
bwrap(
- state,
+ context,
script_maybe_chroot(script, "/work/build-script") + cmdline,
- network=state.config.with_network,
+ network=context.config.with_network,
options=sources + ["--ro-bind", script, script],
scripts=hd,
- env=env | state.config.environment,
+ env=env | context.config.environment,
stdin=sys.stdin,
)
-def run_postinst_scripts(state: MkosiState) -> None:
- if not state.config.postinst_scripts:
+def run_postinst_scripts(context: Context) -> None:
+ if not context.config.postinst_scripts:
return
env = dict(
- ARCHITECTURE=str(state.config.architecture),
- BUILDROOT=str(state.root),
+ ARCHITECTURE=str(context.config.architecture),
+ BUILDROOT=str(context.root),
CHROOT_OUTPUTDIR="/work/out",
CHROOT_SCRIPT="/work/postinst",
CHROOT_SRCDIR="/work/src",
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
- OUTPUTDIR=str(state.staging),
+ OUTPUTDIR=str(context.staging),
SCRIPT="/work/postinst",
SRCDIR=str(Path.cwd()),
)
with (
- finalize_chroot_scripts(state) as cd,
- finalize_source_mounts(state.config) as sources,
+ finalize_chroot_scripts(context) as cd,
+ finalize_source_mounts(context.config) as sources,
):
- for script in state.config.postinst_scripts:
+ for script in context.config.postinst_scripts:
helpers = {
"mkosi-chroot": chroot_cmd(
- state.root,
- resolve=state.config.with_network,
+ context.root,
+ resolve=context.config.with_network,
options=[
"--bind", script, "/work/postinst",
- "--bind", state.staging, "/work/out",
+ "--bind", context.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
"--chdir", "/work/src",
}
with (
- finalize_host_scripts(state, helpers) as hd,
+ finalize_host_scripts(context, helpers) as hd,
complete_step(f"Running postinstall script {script}…"),
):
bwrap(
- state,
+ context,
script_maybe_chroot(script, "/work/postinst") + ["final"],
- network=state.config.with_network,
+ network=context.config.with_network,
options=sources + ["--ro-bind", script, script],
scripts=hd,
- env=env | state.config.environment,
+ env=env | context.config.environment,
stdin=sys.stdin,
)
-def run_finalize_scripts(state: MkosiState) -> None:
- if not state.config.finalize_scripts:
+def run_finalize_scripts(context: Context) -> None:
+ if not context.config.finalize_scripts:
return
env = dict(
- ARCHITECTURE=str(state.config.architecture),
- BUILDROOT=str(state.root),
+ ARCHITECTURE=str(context.config.architecture),
+ BUILDROOT=str(context.root),
CHROOT_OUTPUTDIR="/work/out",
CHROOT_SCRIPT="/work/finalize",
CHROOT_SRCDIR="/work/src",
MKOSI_UID=str(INVOKING_USER.uid),
MKOSI_GID=str(INVOKING_USER.gid),
- OUTPUTDIR=str(state.staging),
+ OUTPUTDIR=str(context.staging),
SCRIPT="/work/finalize",
SRCDIR=str(Path.cwd()),
)
with (
- finalize_chroot_scripts(state) as cd,
- finalize_source_mounts(state.config) as sources,
+ finalize_chroot_scripts(context) as cd,
+ finalize_source_mounts(context.config) as sources,
):
- for script in state.config.finalize_scripts:
+ for script in context.config.finalize_scripts:
helpers = {
"mkosi-chroot": chroot_cmd(
- state.root,
- resolve=state.config.with_network,
+ context.root,
+ resolve=context.config.with_network,
options=[
"--bind", script, "/work/finalize",
- "--bind", state.staging, "/work/out",
+ "--bind", context.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
"--bind", cd, "/work/scripts",
"--chdir", "/work/src",
}
with (
- finalize_host_scripts(state, helpers) as hd,
+ finalize_host_scripts(context, helpers) as hd,
complete_step(f"Running finalize script {script}…"),
):
bwrap(
- state,
+ context,
script_maybe_chroot(script, "/work/finalize"),
- network=state.config.with_network,
+ network=context.config.with_network,
options=sources + ["--ro-bind", script, script],
scripts=hd,
- env=env | state.config.environment,
+ env=env | context.config.environment,
stdin=sys.stdin,
)
-def certificate_common_name(state: MkosiState, certificate: Path) -> str:
+def certificate_common_name(context: Context, certificate: Path) -> str:
output = bwrap(
- state,
+ context,
[
"openssl",
"x509",
die(f"Certificate {certificate} is missing Common Name")
-def pesign_prepare(state: MkosiState) -> None:
- assert state.config.secure_boot_key
- assert state.config.secure_boot_certificate
+def pesign_prepare(context: Context) -> None:
+ assert context.config.secure_boot_key
+ assert context.config.secure_boot_certificate
- if (state.workspace / "pesign").exists():
+ if (context.workspace / "pesign").exists():
return
- (state.workspace / "pesign").mkdir()
+ (context.workspace / "pesign").mkdir()
# pesign takes a certificate directory and a certificate common name as input arguments, so we have
# to transform our input key and cert into that format. Adapted from
# https://www.mankier.com/1/pesign#Examples-Signing_with_the_certificate_and_private_key_in_individual_files
bwrap(
- state,
+ context,
[
"openssl",
"pkcs12",
"-certpbe", "NONE",
"-nomaciter",
"-passout", "pass:",
- "-out", state.workspace / "secure-boot.p12",
- "-inkey", state.config.secure_boot_key,
- "-in", state.config.secure_boot_certificate,
+ "-out", context.workspace / "secure-boot.p12",
+ "-inkey", context.config.secure_boot_key,
+ "-in", context.config.secure_boot_certificate,
],
)
bwrap(
- state,
+ context,
[
"pk12util",
"-K", "",
"-W", "",
- "-i", state.workspace / "secure-boot.p12",
- "-d", state.workspace / "pesign",
+ "-i", context.workspace / "secure-boot.p12",
+ "-d", context.workspace / "pesign",
],
)
-def efi_boot_binary(state: MkosiState) -> Path:
- arch = state.config.architecture.to_efi()
+def efi_boot_binary(context: Context) -> Path:
+ arch = context.config.architecture.to_efi()
assert arch
return Path(f"efi/EFI/BOOT/BOOT{arch.upper()}.EFI")
-def shim_second_stage_binary(state: MkosiState) -> Path:
- arch = state.config.architecture.to_efi()
+def shim_second_stage_binary(context: Context) -> Path:
+ arch = context.config.architecture.to_efi()
assert arch
- if state.config.distribution == Distribution.opensuse:
+ if context.config.distribution == Distribution.opensuse:
return Path("efi/EFI/BOOT/grub.EFI")
else:
return Path(f"efi/EFI/BOOT/grub{arch}.EFI")
-def sign_efi_binary(state: MkosiState, input: Path, output: Path) -> None:
- assert state.config.secure_boot_key
- assert state.config.secure_boot_certificate
+def sign_efi_binary(context: Context, input: Path, output: Path) -> None:
+ assert context.config.secure_boot_key
+ assert context.config.secure_boot_certificate
if (
- state.config.secure_boot_sign_tool == SecureBootSignTool.sbsign or
- state.config.secure_boot_sign_tool == SecureBootSignTool.auto and
+ context.config.secure_boot_sign_tool == SecureBootSignTool.sbsign or
+ context.config.secure_boot_sign_tool == SecureBootSignTool.auto and
shutil.which("sbsign") is not None
):
bwrap(
- state,
+ context,
[
"sbsign",
- "--key", state.config.secure_boot_key,
- "--cert", state.config.secure_boot_certificate,
+ "--key", context.config.secure_boot_key,
+ "--cert", context.config.secure_boot_certificate,
"--output", output,
input,
],
)
elif (
- state.config.secure_boot_sign_tool == SecureBootSignTool.pesign or
- state.config.secure_boot_sign_tool == SecureBootSignTool.auto and
+ context.config.secure_boot_sign_tool == SecureBootSignTool.pesign or
+ context.config.secure_boot_sign_tool == SecureBootSignTool.auto and
shutil.which("pesign") is not None
):
- pesign_prepare(state)
+ pesign_prepare(context)
bwrap(
- state,
+ context,
[
"pesign",
- "--certdir", state.workspace / "pesign",
- "--certificate", certificate_common_name(state, state.config.secure_boot_certificate),
+ "--certdir", context.workspace / "pesign",
+ "--certificate", certificate_common_name(context, context.config.secure_boot_certificate),
"--sign",
"--force",
"--in", input,
die("One of sbsign or pesign is required to use SecureBoot=")
-def install_systemd_boot(state: MkosiState) -> None:
- if not want_efi(state.config):
+def install_systemd_boot(context: Context) -> None:
+ if not want_efi(context.config):
return
- if state.config.bootloader != Bootloader.systemd_boot:
+ if context.config.bootloader != Bootloader.systemd_boot:
return
- if not any(gen_kernel_images(state)) and state.config.bootable == ConfigFeature.auto:
+ if not any(gen_kernel_images(context)) and context.config.bootable == ConfigFeature.auto:
return
if not shutil.which("bootctl"):
- if state.config.bootable == ConfigFeature.enabled:
+ if context.config.bootable == ConfigFeature.enabled:
die("An EFI bootable image with systemd-boot was requested but bootctl was not found")
return
- directory = state.root / "usr/lib/systemd/boot/efi"
+ directory = context.root / "usr/lib/systemd/boot/efi"
if not directory.exists() or not any(directory.iterdir()):
- if state.config.bootable == ConfigFeature.enabled:
+ if context.config.bootable == ConfigFeature.enabled:
die("A EFI bootable image with systemd-boot was requested but systemd-boot was not found at "
- f"{directory.relative_to(state.root)}")
+ f"{directory.relative_to(context.root)}")
return
- if state.config.secure_boot:
+ if context.config.secure_boot:
with complete_step("Signing systemd-boot binaries…"):
for input in itertools.chain(directory.glob('*.efi'), directory.glob('*.EFI')):
output = directory / f"{input}.signed"
- sign_efi_binary(state, input, output)
+ sign_efi_binary(context, input, output)
with complete_step("Installing systemd-boot…"):
bwrap(
- state,
- ["bootctl", "install", "--root", state.root, "--all-architectures", "--no-variables"],
+ context,
+ ["bootctl", "install", "--root", context.root, "--all-architectures", "--no-variables"],
env={"SYSTEMD_ESP_PATH": "/efi"},
)
- if state.config.shim_bootloader != ShimBootloader.none:
+ if context.config.shim_bootloader != ShimBootloader.none:
shutil.copy2(
- state.root / f"efi/EFI/systemd/systemd-boot{state.config.architecture.to_efi()}.efi",
- state.root / shim_second_stage_binary(state),
+ context.root / f"efi/EFI/systemd/systemd-boot{context.config.architecture.to_efi()}.efi",
+ context.root / shim_second_stage_binary(context),
)
- if state.config.secure_boot and state.config.secure_boot_auto_enroll:
- assert state.config.secure_boot_key
- assert state.config.secure_boot_certificate
+ if context.config.secure_boot and context.config.secure_boot_auto_enroll:
+ assert context.config.secure_boot_key
+ assert context.config.secure_boot_certificate
with complete_step("Setting up secure boot auto-enrollment…"):
- keys = state.root / "efi/loader/keys/auto"
+ keys = context.root / "efi/loader/keys/auto"
with umask(~0o700):
keys.mkdir(parents=True, exist_ok=True)
# sbsiglist expects a DER certificate.
bwrap(
- state,
+ context,
[
"openssl",
"x509",
"-outform", "DER",
- "-in", state.config.secure_boot_certificate,
- "-out", state.workspace / "mkosi.der",
+ "-in", context.config.secure_boot_certificate,
+ "-out", context.workspace / "mkosi.der",
],
)
bwrap(
- state,
+ context,
[
"sbsiglist",
"--owner", str(uuid.uuid4()),
"--type", "x509",
- "--output", state.workspace / "mkosi.esl",
- state.workspace / "mkosi.der",
+ "--output", context.workspace / "mkosi.esl",
+ context.workspace / "mkosi.der",
],
)
# We reuse the key for all secure boot databases to keep things simple.
for db in ["PK", "KEK", "db"]:
bwrap(
- state,
+ context,
[
"sbvarsign",
"--attr",
"NON_VOLATILE,BOOTSERVICE_ACCESS,RUNTIME_ACCESS,TIME_BASED_AUTHENTICATED_WRITE_ACCESS",
- "--key", state.config.secure_boot_key,
- "--cert", state.config.secure_boot_certificate,
+ "--key", context.config.secure_boot_key,
+ "--cert", context.config.secure_boot_certificate,
"--output", keys / f"{db}.auth",
db,
- state.workspace / "mkosi.esl",
+ context.workspace / "mkosi.esl",
],
)
def find_and_install_shim_binary(
- state: MkosiState,
+ context: Context,
name: str,
signed: Sequence[str],
unsigned: Sequence[str],
output: Path,
) -> None:
- if state.config.shim_bootloader == ShimBootloader.signed:
+ if context.config.shim_bootloader == ShimBootloader.signed:
for pattern in signed:
- for p in state.root.glob(pattern):
+ for p in context.root.glob(pattern):
if p.is_symlink() and p.readlink().is_absolute():
logging.warning(f"Ignoring signed {name} EFI binary which is an absolute path to {p.readlink()}")
continue
- rel = p.relative_to(state.root)
+ rel = p.relative_to(context.root)
log_step(f"Installing signed {name} EFI binary from /{rel} to /{output}")
- shutil.copy2(p, state.root / output)
+ shutil.copy2(p, context.root / output)
return
- if state.config.bootable == ConfigFeature.enabled:
+ if context.config.bootable == ConfigFeature.enabled:
die(f"Couldn't find signed {name} EFI binary installed in the image")
else:
for pattern in unsigned:
- for p in state.root.glob(pattern):
+ for p in context.root.glob(pattern):
if p.is_symlink() and p.readlink().is_absolute():
logging.warning(f"Ignoring unsigned {name} EFI binary which is an absolute path to {p.readlink()}")
continue
- rel = p.relative_to(state.root)
- if state.config.secure_boot:
+ rel = p.relative_to(context.root)
+ if context.config.secure_boot:
log_step(f"Signing and installing unsigned {name} EFI binary from /{rel} to /{output}")
- sign_efi_binary(state, p, state.root / output)
+ sign_efi_binary(context, p, context.root / output)
else:
log_step(f"Installing unsigned {name} EFI binary /{rel} to /{output}")
- shutil.copy2(p, state.root / output)
+ shutil.copy2(p, context.root / output)
return
- if state.config.bootable == ConfigFeature.enabled:
+ if context.config.bootable == ConfigFeature.enabled:
die(f"Couldn't find unsigned {name} EFI binary installed in the image")
-def install_shim(state: MkosiState) -> None:
- if not want_efi(state.config):
+def install_shim(context: Context) -> None:
+ if not want_efi(context.config):
return
- if state.config.shim_bootloader == ShimBootloader.none:
+ if context.config.shim_bootloader == ShimBootloader.none:
return
- if not any(gen_kernel_images(state)) and state.config.bootable == ConfigFeature.auto:
+ if not any(gen_kernel_images(context)) and context.config.bootable == ConfigFeature.auto:
return
- dst = efi_boot_binary(state)
+ dst = efi_boot_binary(context)
with umask(~0o700):
- (state.root / dst).parent.mkdir(parents=True, exist_ok=True)
+ (context.root / dst).parent.mkdir(parents=True, exist_ok=True)
- arch = state.config.architecture.to_efi()
+ arch = context.config.architecture.to_efi()
signed = [
f"usr/lib/shim/shim{arch}.efi.signed.latest", # Ubuntu
f"usr/share/shim/shim{arch}.efi", # Arch
]
- find_and_install_shim_binary(state, "shim", signed, unsigned, dst)
+ find_and_install_shim_binary(context, "shim", signed, unsigned, dst)
signed = [
f"usr/lib/shim/mm{arch}.efi.signed", # Debian
f"usr/share/shim/mm{arch}.efi", # Arch
]
- find_and_install_shim_binary(state, "mok", signed, unsigned, dst.parent)
+ find_and_install_shim_binary(context, "mok", signed, unsigned, dst.parent)
-def find_grub_bios_directory(state: MkosiState) -> Optional[Path]:
+def find_grub_bios_directory(context: Context) -> Optional[Path]:
for d in ("usr/lib/grub/i386-pc", "usr/share/grub2/i386-pc"):
- if (p := state.root / d).exists() and any(p.iterdir()):
+ if (p := context.root / d).exists() and any(p.iterdir()):
return p
return None
-def find_grub_binary(state: MkosiState, binary: str) -> Optional[Path]:
+def find_grub_binary(context: Context, binary: str) -> Optional[Path]:
assert "grub" in binary and "grub2" not in binary
- return find_binary(binary, root=state.root) or find_binary(binary.replace("grub", "grub2"), root=state.root)
+ return find_binary(binary, root=context.root) or find_binary(binary.replace("grub", "grub2"), root=context.root)
-def find_grub_prefix(state: MkosiState) -> Optional[str]:
- path = find_grub_binary(state, "grub-mkimage")
+def find_grub_prefix(context: Context) -> Optional[str]:
+ path = find_grub_binary(context, "grub-mkimage")
if path is None:
return None
return "grub2" if "grub2" in os.fspath(path) else "grub"
-def want_grub_efi(state: MkosiState) -> bool:
- if state.config.bootable == ConfigFeature.disabled:
+def want_grub_efi(context: Context) -> bool:
+ if context.config.bootable == ConfigFeature.disabled:
return False
- if state.config.bootloader != Bootloader.grub:
+ if context.config.bootloader != Bootloader.grub:
return False
- if state.config.overlay or state.config.output_format.is_extension_image():
+ if context.config.overlay or context.config.output_format.is_extension_image():
return False
- if not any((state.root / "efi").rglob("grub*.efi")):
- if state.config.bootable == ConfigFeature.enabled:
+ if not any((context.root / "efi").rglob("grub*.efi")):
+ if context.config.bootable == ConfigFeature.enabled:
die("A bootable EFI image with grub was requested but grub for EFI is not installed in /efi")
return False
return True
-def want_grub_bios(state: MkosiState, partitions: Sequence[Partition] = ()) -> bool:
- if state.config.bootable == ConfigFeature.disabled:
+def want_grub_bios(context: Context, partitions: Sequence[Partition] = ()) -> bool:
+ if context.config.bootable == ConfigFeature.disabled:
return False
- if state.config.output_format != OutputFormat.disk:
+ if context.config.output_format != OutputFormat.disk:
return False
- if state.config.bios_bootloader != BiosBootloader.grub:
+ if context.config.bios_bootloader != BiosBootloader.grub:
return False
- if state.config.overlay:
+ if context.config.overlay:
return False
- have = find_grub_bios_directory(state) is not None
- if not have and state.config.bootable == ConfigFeature.enabled:
+ have = find_grub_bios_directory(context) is not None
+ if not have and context.config.bootable == ConfigFeature.enabled:
die("A BIOS bootable image with grub was requested but grub for BIOS is not installed")
bios = any(p.type == Partition.GRUB_BOOT_PARTITION_UUID for p in partitions)
- if partitions and not bios and state.config.bootable == ConfigFeature.enabled:
+ if partitions and not bios and context.config.bootable == ConfigFeature.enabled:
die("A BIOS bootable image with grub was requested but no BIOS Boot Partition was configured")
esp = any(p.type == "esp" for p in partitions)
- if partitions and not esp and state.config.bootable == ConfigFeature.enabled:
+ if partitions and not esp and context.config.bootable == ConfigFeature.enabled:
die("A BIOS bootable image with grub was requested but no ESP partition was configured")
root = any(p.type.startswith("root") or p.type.startswith("usr") for p in partitions)
- if partitions and not root and state.config.bootable == ConfigFeature.enabled:
+ if partitions and not root and context.config.bootable == ConfigFeature.enabled:
die("A BIOS bootable image with grub was requested but no root or usr partition was configured")
installed = True
for binary in ("grub-mkimage", "grub-bios-setup"):
- path = find_grub_binary(state, binary)
+ path = find_grub_binary(context, binary)
if path is not None:
continue
- if state.config.bootable == ConfigFeature.enabled:
+ if context.config.bootable == ConfigFeature.enabled:
die(f"A BIOS bootable image with grub was requested but {binary} was not found")
installed = False
return (have and bios and esp and root and installed) if partitions else have
-def prepare_grub_config(state: MkosiState) -> Optional[Path]:
- prefix = find_grub_prefix(state)
+def prepare_grub_config(context: Context) -> Optional[Path]:
+ prefix = find_grub_prefix(context)
if not prefix:
return None
- config = state.root / "efi" / prefix / "grub.cfg"
+ config = context.root / "efi" / prefix / "grub.cfg"
with umask(~0o700):
config.parent.mkdir(exist_ok=True)
return config
-def prepare_grub_efi(state: MkosiState) -> None:
- if not want_grub_efi(state):
+def prepare_grub_efi(context: Context) -> None:
+ if not want_grub_efi(context):
return
- prefix = find_grub_prefix(state)
+ prefix = find_grub_prefix(context)
assert prefix
# Signed EFI grub shipped by distributions reads its configuration from /EFI/<distribution>/grub.cfg in
# the ESP so let's put a shim there to redirect to the actual configuration file.
- earlyconfig = state.root / "efi/EFI" / state.config.distribution.name / "grub.cfg"
+ earlyconfig = context.root / "efi/EFI" / context.config.distribution.name / "grub.cfg"
with umask(~0o700):
earlyconfig.parent.mkdir(parents=True, exist_ok=True)
# Read the actual config file from the root of the ESP.
earlyconfig.write_text(f"configfile /{prefix}/grub.cfg\n")
- config = prepare_grub_config(state)
+ config = prepare_grub_config(context)
assert config
with config.open("a") as f:
f.write('if [ "${grub_platform}" == "efi" ]; then\n')
- for uki in (state.root / "boot/EFI/Linux").glob("*.efi"):
+ for uki in (context.root / "boot/EFI/Linux").glob("*.efi"):
f.write(
textwrap.dedent(
f"""\
menuentry "{uki.stem}" {{
- chainloader /{uki.relative_to(state.root / "boot")}
+ chainloader /{uki.relative_to(context.root / "boot")}
}}
"""
)
f.write("fi\n")
-def prepare_grub_bios(state: MkosiState, partitions: Sequence[Partition]) -> None:
- if not want_grub_bios(state, partitions):
+def prepare_grub_bios(context: Context, partitions: Sequence[Partition]) -> None:
+ if not want_grub_bios(context, partitions):
return
- config = prepare_grub_config(state)
+ config = prepare_grub_config(context)
assert config
root = finalize_root(partitions)
assert root
- token = find_entry_token(state)
+ token = find_entry_token(context)
- dst = state.root / "boot" / token
+ dst = context.root / "boot" / token
with umask(~0o700):
dst.mkdir(exist_ok=True)
with config.open("a") as f:
f.write('if [ "${grub_platform}" == "pc" ]; then\n')
- for kver, kimg in gen_kernel_images(state):
+ for kver, kimg in gen_kernel_images(context):
kdst = dst / kver
with umask(~0o700):
kdst.mkdir(exist_ok=True)
- microcode = build_microcode_initrd(state)
- kmods = build_kernel_modules_initrd(state, kver)
+ microcode = build_microcode_initrd(context)
+ kmods = build_kernel_modules_initrd(context, kver)
with umask(~0o600):
- kimg = Path(shutil.copy2(state.root / kimg, kdst / "vmlinuz"))
+ kimg = Path(shutil.copy2(context.root / kimg, kdst / "vmlinuz"))
initrds = [Path(shutil.copy2(microcode, kdst / "microcode"))] if microcode else []
initrds += [
Path(shutil.copy2(initrd, dst / initrd.name))
- for initrd in (state.config.initrds or [build_initrd(state)])
+ for initrd in (context.config.initrds or [build_initrd(context)])
]
initrds += [Path(shutil.copy2(kmods, kdst / "kmods"))]
- image = Path("/") / kimg.relative_to(state.root / "boot")
- cmdline = " ".join(state.config.kernel_command_line)
+ image = Path("/") / kimg.relative_to(context.root / "boot")
+ cmdline = " ".join(context.config.kernel_command_line)
initrds = " ".join(
- [os.fspath(Path("/") / initrd.relative_to(state.root / "boot")) for initrd in initrds]
+ [os.fspath(Path("/") / initrd.relative_to(context.root / "boot")) for initrd in initrds]
)
f.write(
# so we're forced to reimplement its functionality. Luckily that's pretty simple, run grub-mkimage to
# generate the required core.img and copy the relevant files to the ESP.
- mkimage = find_grub_binary(state, "grub-mkimage")
+ mkimage = find_grub_binary(context, "grub-mkimage")
assert mkimage
- directory = find_grub_bios_directory(state)
+ directory = find_grub_bios_directory(context)
assert directory
- prefix = find_grub_prefix(state)
+ prefix = find_grub_prefix(context)
assert prefix
- dst = state.root / "efi" / prefix / "i386-pc"
+ dst = context.root / "efi" / prefix / "i386-pc"
dst.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile("w", prefix="grub-early-config") as earlyconfig:
earlyconfig.flush()
bwrap(
- state,
+ context,
[
mkimage,
"--directory", directory,
"search",
"search_fs_file",
],
- options=["--bind", state.root / "usr", "/usr"],
+ options=["--bind", context.root / "usr", "/usr"],
)
for p in directory.glob("*.mod"):
shutil.copy2(directory / "modinfo.sh", dst)
shutil.copy2(directory / "boot.img", dst)
- dst = state.root / "efi" / prefix / "fonts"
+ dst = context.root / "efi" / prefix / "fonts"
with umask(~0o700):
dst.mkdir(exist_ok=True)
for prefix in ("grub", "grub2"):
- unicode = state.root / "usr/share" / prefix / "unicode.pf2"
+ unicode = context.root / "usr/share" / prefix / "unicode.pf2"
if unicode.exists():
shutil.copy2(unicode, dst)
-def install_grub_bios(state: MkosiState, partitions: Sequence[Partition]) -> None:
- if not want_grub_bios(state, partitions):
+def install_grub_bios(context: Context, partitions: Sequence[Partition]) -> None:
+ if not want_grub_bios(context, partitions):
return
- setup = find_grub_binary(state, "grub-bios-setup")
+ setup = find_grub_binary(context, "grub-bios-setup")
assert setup
- prefix = find_grub_prefix(state)
+ prefix = find_grub_prefix(context)
assert prefix
# grub-bios-setup insists on being able to open the root device that --directory is located on, which
# bios boot partition. To make installation work unprivileged, we trick grub to think that the root
# device is our image by mounting over its /proc/self/mountinfo file (where it gets its information from)
# with our own file correlating the root directory to our image file.
- mountinfo = state.workspace / "mountinfo"
- mountinfo.write_text(f"1 0 1:1 / / - fat {state.staging / state.config.output_with_format}\n")
+ mountinfo = context.workspace / "mountinfo"
+ mountinfo.write_text(f"1 0 1:1 / / - fat {context.staging / context.config.output_with_format}\n")
with complete_step("Installing grub boot loader…"):
# We don't setup the mountinfo bind mount with bwrap because we need to know the child process pid to
# be able to do the mount and we don't know the pid beforehand.
bwrap(
- state,
+ context,
[
"sh", "-c", f"mount --bind {mountinfo} /proc/$$/mountinfo && exec $0 \"$@\"",
setup,
- "--directory", state.root / "efi" / prefix / "i386-pc",
+ "--directory", context.root / "efi" / prefix / "i386-pc",
*(["--verbose"] if ARG_DEBUG.get() else []),
- state.staging / state.config.output_with_format,
+ context.staging / context.config.output_with_format,
],
- options=["--bind", state.root / "usr", "/usr"],
+ options=["--bind", context.root / "usr", "/usr"],
)
def install_tree(
- state: MkosiState,
+ context: Context,
src: Path,
dst: Path,
target: Optional[Path] = None,
t.parent.mkdir(parents=True, exist_ok=True)
if src.is_dir() or (src.is_file() and target):
- copy_tree(src, t, preserve_owner=preserve_owner, use_subvolumes=state.config.use_subvolumes)
+ copy_tree(src, t, preserve_owner=preserve_owner, use_subvolumes=context.config.use_subvolumes)
elif src.suffix == ".tar":
- extract_tar(state, src, t)
+ extract_tar(context, src, t)
elif src.suffix == ".raw":
run(["systemd-dissect", "--copy-from", src, "/", t])
else:
# If we get an unknown file without a target, we just copy it into /.
- copy_tree(src, t, preserve_owner=preserve_owner, use_subvolumes=state.config.use_subvolumes)
+ copy_tree(src, t, preserve_owner=preserve_owner, use_subvolumes=context.config.use_subvolumes)
-def install_base_trees(state: MkosiState) -> None:
- if not state.config.base_trees or state.config.overlay:
+def install_base_trees(context: Context) -> None:
+ if not context.config.base_trees or context.config.overlay:
return
with complete_step("Copying in base trees…"):
- for path in state.config.base_trees:
- install_tree(state, path, state.root)
+ for path in context.config.base_trees:
+ install_tree(context, path, context.root)
-def install_skeleton_trees(state: MkosiState) -> None:
- if not state.config.skeleton_trees:
+def install_skeleton_trees(context: Context) -> None:
+ if not context.config.skeleton_trees:
return
with complete_step("Copying in skeleton file trees…"):
- for tree in state.config.skeleton_trees:
- install_tree(state, tree.source, state.root, tree.target, preserve_owner=False)
+ for tree in context.config.skeleton_trees:
+ install_tree(context, tree.source, context.root, tree.target, preserve_owner=False)
-def install_package_manager_trees(state: MkosiState) -> None:
+def install_package_manager_trees(context: Context) -> None:
# Ensure /etc exists in the package manager tree
- (state.pkgmngr / "etc").mkdir(exist_ok=True)
+ (context.pkgmngr / "etc").mkdir(exist_ok=True)
# Required to be able to access certificates in the sandbox when running from nix.
if Path("/etc/static").is_symlink():
- (state.pkgmngr / "etc/static").symlink_to(Path("/etc/static").readlink())
+ (context.pkgmngr / "etc/static").symlink_to(Path("/etc/static").readlink())
- if not state.config.package_manager_trees:
+ if not context.config.package_manager_trees:
return
with complete_step("Copying in package manager file trees…"):
- for tree in state.config.package_manager_trees:
- install_tree(state, tree.source, state.workspace / "pkgmngr", tree.target, preserve_owner=False)
+ for tree in context.config.package_manager_trees:
+ install_tree(context, tree.source, context.workspace / "pkgmngr", tree.target, preserve_owner=False)
-def install_extra_trees(state: MkosiState) -> None:
- if not state.config.extra_trees:
+def install_extra_trees(context: Context) -> None:
+ if not context.config.extra_trees:
return
with complete_step("Copying in extra file trees…"):
- for tree in state.config.extra_trees:
- install_tree(state, tree.source, state.root, tree.target, preserve_owner=False)
+ for tree in context.config.extra_trees:
+ install_tree(context, tree.source, context.root, tree.target, preserve_owner=False)
-def install_build_dest(state: MkosiState) -> None:
- if not any(state.install_dir.iterdir()):
+def install_build_dest(context: Context) -> None:
+ if not any(context.install_dir.iterdir()):
return
with complete_step("Copying in build tree…"):
- copy_tree(state.install_dir, state.root, use_subvolumes=state.config.use_subvolumes)
+ copy_tree(context.install_dir, context.root, use_subvolumes=context.config.use_subvolumes)
def gzip_binary() -> str:
return "pigz" if shutil.which("pigz") else "gzip"
-def gen_kernel_images(state: MkosiState) -> Iterator[tuple[str, Path]]:
- if not (state.root / "usr/lib/modules").exists():
+def gen_kernel_images(context: Context) -> Iterator[tuple[str, Path]]:
+ if not (context.root / "usr/lib/modules").exists():
return
for kver in sorted(
- (k for k in (state.root / "usr/lib/modules").iterdir() if k.is_dir()),
+ (k for k in (context.root / "usr/lib/modules").iterdir() if k.is_dir()),
key=lambda k: GenericVersion(k.name),
reverse=True
):
break
-def build_initrd(state: MkosiState) -> Path:
- if state.config.distribution == Distribution.custom:
+def build_initrd(context: Context) -> Path:
+ if context.config.distribution == Distribution.custom:
die("Building a default initrd is not supported for custom distributions")
# Default values are assigned via the parser so we go via the argument parser to construct
# the config for the initrd.
- if state.config.root_password:
- password, hashed = state.config.root_password
+ if context.config.root_password:
+ password, hashed = context.config.root_password
rootpwopt = f"hashed:{password}" if hashed else password
else:
rootpwopt = None
cmdline = [
"--directory", "",
- "--distribution", str(state.config.distribution),
- "--release", state.config.release,
- "--architecture", str(state.config.architecture),
- *(["--mirror", state.config.mirror] if state.config.mirror else []),
- "--repository-key-check", str(state.config.repository_key_check),
- "--repositories", ",".join(state.config.repositories),
- "--package-manager-tree", ",".join(format_tree(t) for t in state.config.package_manager_trees),
+ "--distribution", str(context.config.distribution),
+ "--release", context.config.release,
+ "--architecture", str(context.config.architecture),
+ *(["--mirror", context.config.mirror] if context.config.mirror else []),
+ "--repository-key-check", str(context.config.repository_key_check),
+ "--repositories", ",".join(context.config.repositories),
+ "--package-manager-tree", ",".join(format_tree(t) for t in context.config.package_manager_trees),
# Note that when compress_output == Compression.none == 0 we don't pass --compress-output which means the
# default compression will get picked. This is exactly what we want so that initrds are always compressed.
- *(["--compress-output", str(state.config.compress_output)] if state.config.compress_output else []),
- "--with-network", str(state.config.with_network),
- "--cache-only", str(state.config.cache_only),
- "--output-dir", str(state.workspace / "initrd"),
- *(["--workspace-dir", str(state.config.workspace_dir)] if state.config.workspace_dir else []),
- "--cache-dir", str(state.cache_dir),
- *(["--local-mirror", str(state.config.local_mirror)] if state.config.local_mirror else []),
- "--incremental", str(state.config.incremental),
- "--acl", str(state.config.acl),
- *flatten(["--package", package] for package in state.config.initrd_packages),
- "--output", f"{state.config.output}-initrd",
- *(["--image-id", state.config.image_id] if state.config.image_id else []),
- *(["--image-version", state.config.image_version] if state.config.image_version else []),
+ *(["--compress-output", str(context.config.compress_output)] if context.config.compress_output else []),
+ "--with-network", str(context.config.with_network),
+ "--cache-only", str(context.config.cache_only),
+ "--output-dir", str(context.workspace / "initrd"),
+ *(["--workspace-dir", str(context.config.workspace_dir)] if context.config.workspace_dir else []),
+ "--cache-dir", str(context.cache_dir),
+ *(["--local-mirror", str(context.config.local_mirror)] if context.config.local_mirror else []),
+ "--incremental", str(context.config.incremental),
+ "--acl", str(context.config.acl),
+ *flatten(["--package", package] for package in context.config.initrd_packages),
+ "--output", f"{context.config.output}-initrd",
+ *(["--image-id", context.config.image_id] if context.config.image_id else []),
+ *(["--image-version", context.config.image_version] if context.config.image_version else []),
*(
- ["--source-date-epoch", str(state.config.source_date_epoch)]
- if state.config.source_date_epoch is not None else
+ ["--source-date-epoch", str(context.config.source_date_epoch)]
+ if context.config.source_date_epoch is not None else
[]
),
- *(["--locale", state.config.locale] if state.config.locale else []),
- *(["--locale-messages", state.config.locale_messages] if state.config.locale_messages else []),
- *(["--keymap", state.config.keymap] if state.config.keymap else []),
- *(["--timezone", state.config.timezone] if state.config.timezone else []),
- *(["--hostname", state.config.hostname] if state.config.hostname else []),
+ *(["--locale", context.config.locale] if context.config.locale else []),
+ *(["--locale-messages", context.config.locale_messages] if context.config.locale_messages else []),
+ *(["--keymap", context.config.keymap] if context.config.keymap else []),
+ *(["--timezone", context.config.timezone] if context.config.timezone else []),
+ *(["--hostname", context.config.hostname] if context.config.hostname else []),
*(["--root-password", rootpwopt] if rootpwopt else []),
- *([f"--environment={k}='{v}'" for k, v in state.config.environment.items()]),
- *(["--tools-tree", str(state.config.tools_tree)] if state.config.tools_tree else []),
- *(["-f"] * state.args.force),
+ *([f"--environment={k}='{v}'" for k, v in context.config.environment.items()]),
+ *(["--tools-tree", str(context.config.tools_tree)] if context.config.tools_tree else []),
+ *(["-f"] * context.args.force),
]
with resource_path(mkosi.resources) as r:
cmdline += ["--include", os.fspath(r / "mkosi-initrd")]
- for include in state.config.initrd_include:
+ for include in context.config.initrd_include:
cmdline += ["--include", os.fspath(include)]
args, [config] = parse_config(cmdline + ["build"])
return config.output_dir / config.output
-def build_microcode_initrd(state: MkosiState) -> Optional[Path]:
- microcode = state.workspace / "initrd-microcode.img"
+def build_microcode_initrd(context: Context) -> Optional[Path]:
+ microcode = context.workspace / "initrd-microcode.img"
if microcode.exists():
return microcode
- amd = state.root / "usr/lib/firmware/amd-ucode"
- intel = state.root / "usr/lib/firmware/intel-ucode"
+ amd = context.root / "usr/lib/firmware/amd-ucode"
+ intel = context.root / "usr/lib/firmware/intel-ucode"
if not amd.exists() and not intel.exists():
return None
- root = state.workspace / "initrd-microcode-root"
+ root = context.workspace / "initrd-microcode-root"
destdir = root / "kernel/x86/microcode"
with umask(~0o755):
for p in intel.iterdir():
f.write(p.read_bytes())
- make_cpio(state, root, microcode)
+ make_cpio(context, root, microcode)
return microcode
-def build_kernel_modules_initrd(state: MkosiState, kver: str) -> Path:
- kmods = state.workspace / f"initrd-kernel-modules-{kver}.img"
+def build_kernel_modules_initrd(context: Context, kver: str) -> Path:
+ kmods = context.workspace / f"initrd-kernel-modules-{kver}.img"
if kmods.exists():
return kmods
make_cpio(
- state, state.root, kmods,
+ context, context.root, kmods,
gen_required_kernel_modules(
- state.root, kver,
- state.config.kernel_modules_initrd_include,
- state.config.kernel_modules_initrd_exclude,
- state.config.kernel_modules_initrd_include_host,
+ context.root, kver,
+ context.config.kernel_modules_initrd_include,
+ context.config.kernel_modules_initrd_exclude,
+ context.config.kernel_modules_initrd_include_host,
)
)
# Debian/Ubuntu do not compress their kernel modules, so we compress the initramfs instead. Note that
# this is not ideal since the compressed kernel modules will all be decompressed on boot which
# requires significant memory.
- if state.config.distribution.is_apt_distribution():
- maybe_compress(state, Compression.zstd, kmods, kmods)
+ if context.config.distribution.is_apt_distribution():
+ maybe_compress(context, Compression.zstd, kmods, kmods)
return kmods
return output
-def python_binary(config: MkosiConfig) -> str:
+def python_binary(config: Config) -> str:
# If there's no tools tree, prefer the interpreter from MKOSI_INTERPRETER. If there is a tools
# tree, just use the default python3 interpreter.
return "python3" if config.tools_tree else os.getenv("MKOSI_INTERPRETER", "python3")
-def extract_pe_section(state: MkosiState, binary: Path, section: str, output: Path) -> None:
+def extract_pe_section(context: Context, binary: Path, section: str, output: Path) -> None:
# When using a tools tree, we want to use the pefile module from the tools tree instead of requiring that
# python-pefile is installed on the host. So we execute python as a subprocess to make sure we load
# pefile from the tools tree if one is used.
"""
)
- bwrap(state, [python_binary(state.config)], input=pefile)
+ bwrap(context, [python_binary(context.config)], input=pefile)
def build_uki(
- state: MkosiState,
+ context: Context,
stub: Path,
kver: str,
kimg: Path,
if roothash:
cmdline += [roothash]
- cmdline += state.config.kernel_command_line
+ cmdline += context.config.kernel_command_line
# 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.
- (state.workspace / "cmdline").write_text(f"{' '.join(cmdline).strip()}\x00")
+ (context.workspace / "cmdline").write_text(f"{' '.join(cmdline).strip()}\x00")
- if not (arch := state.config.architecture.to_efi()):
- die(f"Architecture {state.config.architecture} does not support UEFI")
+ if not (arch := context.config.architecture.to_efi()):
+ die(f"Architecture {context.config.architecture} does not support UEFI")
cmd: list[PathString] = [
shutil.which("ukify") or "/usr/lib/systemd/ukify",
- "--cmdline", f"@{state.workspace / 'cmdline'}",
- "--os-release", f"@{state.root / 'usr/lib/os-release'}",
+ "--cmdline", f"@{context.workspace / 'cmdline'}",
+ "--os-release", f"@{context.root / 'usr/lib/os-release'}",
"--stub", stub,
"--output", output,
"--efi-arch", arch,
"--uname", kver,
]
- if not state.config.tools_tree:
- for p in state.config.extra_search_paths:
+ if not context.config.tools_tree:
+ for p in context.config.extra_search_paths:
cmd += ["--tools", p]
- if state.config.secure_boot:
- assert state.config.secure_boot_key
- assert state.config.secure_boot_certificate
+ if context.config.secure_boot:
+ assert context.config.secure_boot_key
+ assert context.config.secure_boot_certificate
cmd += ["--sign-kernel"]
- if state.config.secure_boot_sign_tool != SecureBootSignTool.pesign:
+ if context.config.secure_boot_sign_tool != SecureBootSignTool.pesign:
cmd += [
"--signtool", "sbsign",
- "--secureboot-private-key", state.config.secure_boot_key,
- "--secureboot-certificate", state.config.secure_boot_certificate,
+ "--secureboot-private-key",
+ context.config.secure_boot_key,
+ "--secureboot-certificate",
+ context.config.secure_boot_certificate,
]
else:
- pesign_prepare(state)
+ pesign_prepare(context)
cmd += [
"--signtool", "pesign",
- "--secureboot-certificate-dir", state.workspace / "pesign",
- "--secureboot-certificate-name", certificate_common_name(state, state.config.secure_boot_certificate),
+ "--secureboot-certificate-dir",
+ context.workspace / "pesign",
+ "--secureboot-certificate-name",
+ certificate_common_name(context, context.config.secure_boot_certificate),
]
- sign_expected_pcr = (state.config.sign_expected_pcr == ConfigFeature.enabled or
- (state.config.sign_expected_pcr == ConfigFeature.auto and
+ sign_expected_pcr = (context.config.sign_expected_pcr == ConfigFeature.enabled or
+ (context.config.sign_expected_pcr == ConfigFeature.auto and
shutil.which("systemd-measure") is not None))
if sign_expected_pcr:
cmd += [
- "--pcr-private-key", state.config.secure_boot_key,
+ "--pcr-private-key", context.config.secure_boot_key,
"--pcr-banks", "sha1,sha256",
]
cmd += ["--initrd", initrd]
with complete_step(f"Generating unified kernel image for kernel version {kver}"):
- bwrap(state, cmd)
+ bwrap(context, cmd)
-def want_efi(config: MkosiConfig) -> bool:
+def want_efi(config: Config) -> bool:
# Do we want to make the image bootable on EFI firmware?
# Note that this returns True also in the case where autodetection might later
# cause the system to not be made bootable on EFI firmware after the filesystem
return True
-def find_entry_token(state: MkosiState) -> str:
+def find_entry_token(context: Context) -> str:
if (
"--version" not in run(["kernel-install", "--help"], stdout=subprocess.PIPE).stdout or
systemd_tool_version("kernel-install") < "255.1"
):
- return state.config.image_id or state.config.distribution.name
+ return context.config.image_id or context.config.distribution.name
- output = json.loads(bwrap(state, ["kernel-install", "--root", state.root, "--json=pretty", "inspect"],
+ output = json.loads(bwrap(context, ["kernel-install", "--root", context.root, "--json=pretty", "inspect"],
stdout=subprocess.PIPE).stdout)
logging.debug(json.dumps(output, indent=4))
return cast(str, output["EntryToken"])
-def install_uki(state: MkosiState, partitions: Sequence[Partition]) -> None:
+def install_uki(context: Context, partitions: Sequence[Partition]) -> None:
# Iterates through all kernel versions included in the image and generates a combined
# kernel+initrd+cmdline+osrelease EFI file from it and places it in the /EFI/Linux directory of the ESP.
# sd-boot iterates through them and shows them in the menu. These "unified" 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 not want_efi(state.config) or state.config.output_format in (OutputFormat.uki, OutputFormat.esp):
+ if not want_efi(context.config) or context.config.output_format in (OutputFormat.uki, OutputFormat.esp):
return
- arch = state.config.architecture.to_efi()
- stub = state.root / f"usr/lib/systemd/boot/efi/linux{arch}.efi.stub"
- if not stub.exists() and state.config.bootable == ConfigFeature.auto:
+ arch = context.config.architecture.to_efi()
+ stub = context.root / f"usr/lib/systemd/boot/efi/linux{arch}.efi.stub"
+ if not stub.exists() and context.config.bootable == ConfigFeature.auto:
return
roothash = finalize_roothash(partitions)
- for kver, kimg in gen_kernel_images(state):
+ for kver, kimg in gen_kernel_images(context):
# See https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/#boot-counting
boot_count = ""
- if (state.root / "etc/kernel/tries").exists():
- boot_count = f'+{(state.root / "etc/kernel/tries").read_text().strip()}'
+ if (context.root / "etc/kernel/tries").exists():
+ boot_count = f'+{(context.root / "etc/kernel/tries").read_text().strip()}'
- if state.config.bootloader == Bootloader.uki:
- if state.config.shim_bootloader != ShimBootloader.none:
- boot_binary = state.root / shim_second_stage_binary(state)
+ if context.config.bootloader == Bootloader.uki:
+ if context.config.shim_bootloader != ShimBootloader.none:
+ boot_binary = context.root / shim_second_stage_binary(context)
else:
- boot_binary = state.root / efi_boot_binary(state)
+ boot_binary = context.root / efi_boot_binary(context)
else:
- token = find_entry_token(state)
+ token = find_entry_token(context)
if roothash:
_, _, h = roothash.partition("=")
- boot_binary = state.root / f"boot/EFI/Linux/{token}-{kver}-{h}{boot_count}.efi"
+ boot_binary = context.root / f"boot/EFI/Linux/{token}-{kver}-{h}{boot_count}.efi"
else:
- boot_binary = state.root / f"boot/EFI/Linux/{token}-{kver}{boot_count}.efi"
+ boot_binary = context.root / f"boot/EFI/Linux/{token}-{kver}{boot_count}.efi"
- microcode = build_microcode_initrd(state)
+ microcode = build_microcode_initrd(context)
initrds = [microcode] if microcode else []
- initrds += state.config.initrds or [build_initrd(state)]
+ initrds += context.config.initrds or [build_initrd(context)]
- if state.config.kernel_modules_initrd:
- initrds += [build_kernel_modules_initrd(state, kver)]
+ if context.config.kernel_modules_initrd:
+ initrds += [build_kernel_modules_initrd(context, kver)]
# Make sure the parent directory where we'll be writing the UKI exists.
with umask(~0o700):
boot_binary.parent.mkdir(parents=True, exist_ok=True)
- if (state.root / "etc/kernel/cmdline").exists():
- cmdline = [(state.root / "etc/kernel/cmdline").read_text().strip()]
- elif (state.root / "usr/lib/kernel/cmdline").exists():
- cmdline = [(state.root / "usr/lib/kernel/cmdline").read_text().strip()]
+ if (context.root / "etc/kernel/cmdline").exists():
+ cmdline = [(context.root / "etc/kernel/cmdline").read_text().strip()]
+ elif (context.root / "usr/lib/kernel/cmdline").exists():
+ cmdline = [(context.root / "usr/lib/kernel/cmdline").read_text().strip()]
else:
cmdline = []
- build_uki(state, stub, kver, state.root / kimg, initrds, cmdline, boot_binary, roothash=roothash)
+ build_uki(context, stub, kver, context.root / kimg, initrds, cmdline, boot_binary, roothash=roothash)
- if not (state.staging / state.config.output_split_initrd).exists():
+ if not (context.staging / context.config.output_split_initrd).exists():
# Extract the combined initrds from the UKI so we can use it to direct kernel boot with qemu
# if needed.
- extract_pe_section(state, boot_binary, ".initrd", state.staging / state.config.output_split_initrd)
+ extract_pe_section(context, boot_binary, ".initrd", context.staging / context.config.output_split_initrd)
- if not (state.staging / state.config.output_split_uki).exists():
- shutil.copy(boot_binary, state.staging / state.config.output_split_uki)
+ if not (context.staging / context.config.output_split_uki).exists():
+ shutil.copy(boot_binary, context.staging / context.config.output_split_uki)
# ukify will have signed the kernel image as well. Let's make sure we put the signed kernel
# image in the output directory instead of the unsigned one by reading it from the UKI.
- extract_pe_section(state, boot_binary, ".linux", state.staging / state.config.output_split_kernel)
+ extract_pe_section(context, boot_binary, ".linux", context.staging / context.config.output_split_kernel)
print_output_size(boot_binary)
- if state.config.bootloader == Bootloader.uki:
+ if context.config.bootloader == Bootloader.uki:
break
- if state.config.bootable == ConfigFeature.enabled and not (state.staging / state.config.output_split_uki).exists():
+ if (
+ context.config.bootable == ConfigFeature.enabled and
+ not (context.staging / context.config.output_split_uki).exists()
+ ):
die("A bootable image was requested but no kernel was found")
-def make_uki(state: MkosiState, stub: Path, kver: str, kimg: Path, output: Path) -> None:
- microcode = build_microcode_initrd(state)
- make_cpio(state, state.root, state.workspace / "initrd")
- maybe_compress(state, state.config.compress_output, state.workspace / "initrd", state.workspace / "initrd")
+def make_uki(context: Context, stub: Path, kver: str, kimg: Path, output: Path) -> None:
+ microcode = build_microcode_initrd(context)
+ make_cpio(context, context.root, context.workspace / "initrd")
+ maybe_compress(context, context.config.compress_output, context.workspace / "initrd", context.workspace / "initrd")
initrds = [microcode] if microcode else []
- initrds += [state.workspace / "initrd"]
+ initrds += [context.workspace / "initrd"]
- build_uki(state, stub, kver, kimg, initrds, [], output)
- extract_pe_section(state, output, ".linux", state.staging / state.config.output_split_kernel)
- extract_pe_section(state, output, ".initrd", state.staging / state.config.output_split_initrd)
+ build_uki(context, stub, kver, kimg, initrds, [], output)
+ extract_pe_section(context, output, ".linux", context.staging / context.config.output_split_kernel)
+ extract_pe_section(context, output, ".initrd", context.staging / context.config.output_split_initrd)
def compressor_command(compression: Compression) -> list[PathString]:
die(f"Unknown compression {compression}")
-def maybe_compress(state: MkosiState, compression: Compression, src: Path, dst: Optional[Path] = None) -> None:
+def maybe_compress(context: Context, compression: Compression, src: Path, dst: Optional[Path] = None) -> None:
if not compression or src.is_dir():
if dst:
- move_tree(src, dst, use_subvolumes=state.config.use_subvolumes)
+ move_tree(src, dst, use_subvolumes=context.config.use_subvolumes)
return
if not dst:
src.unlink() # if src == dst, make sure dst doesn't truncate the src file but creates a new file.
with dst.open("wb") as o:
- bwrap(state, compressor_command(compression), stdin=i, stdout=o)
+ bwrap(context, compressor_command(compression), stdin=i, stdout=o)
-def copy_vmlinuz(state: MkosiState) -> None:
- if (state.staging / state.config.output_split_kernel).exists():
+def copy_vmlinuz(context: Context) -> None:
+ if (context.staging / context.config.output_split_kernel).exists():
return
- for _, kimg in gen_kernel_images(state):
- shutil.copy(state.root / kimg, state.staging / state.config.output_split_kernel)
+ for _, kimg in gen_kernel_images(context):
+ shutil.copy(context.root / kimg, context.staging / context.config.output_split_kernel)
break
-def copy_initrd(state: MkosiState) -> None:
- if (state.staging / state.config.output_split_initrd).exists():
+def copy_initrd(context: Context) -> None:
+ if (context.staging / context.config.output_split_initrd).exists():
return
- if state.config.bootable == ConfigFeature.disabled:
+ if context.config.bootable == ConfigFeature.disabled:
return
- if state.config.output_format not in (OutputFormat.disk, OutputFormat.directory):
+ if context.config.output_format not in (OutputFormat.disk, OutputFormat.directory):
return
- for kver, _ in gen_kernel_images(state):
- microcode = build_microcode_initrd(state)
+ for kver, _ in gen_kernel_images(context):
+ microcode = build_microcode_initrd(context)
initrds = [microcode] if microcode else []
- initrds += state.config.initrds or [build_initrd(state)]
- if state.config.kernel_modules_initrd:
- kver = next(gen_kernel_images(state))[0]
- initrds += [build_kernel_modules_initrd(state, kver)]
- join_initrds(initrds, state.staging / state.config.output_split_initrd)
+ initrds += context.config.initrds or [build_initrd(context)]
+ if context.config.kernel_modules_initrd:
+ kver = next(gen_kernel_images(context))[0]
+ initrds += [build_kernel_modules_initrd(context, kver)]
+ join_initrds(initrds, context.staging / context.config.output_split_initrd)
break
of.write(h.hexdigest() + " *" + path.name + "\n")
-def calculate_sha256sum(state: MkosiState) -> None:
- if state.config.output_format == OutputFormat.directory:
+def calculate_sha256sum(context: Context) -> None:
+ if context.config.output_format == OutputFormat.directory:
return None
- if not state.config.checksum:
+ if not context.config.checksum:
return None
with complete_step("Calculating SHA256SUMS…"):
- with open(state.workspace / state.config.output_checksum, "w") as f:
- for p in state.staging.iterdir():
+ with open(context.workspace / context.config.output_checksum, "w") as f:
+ for p in context.staging.iterdir():
hash_file(f, p)
- (state.workspace / state.config.output_checksum).rename(state.staging / state.config.output_checksum)
+ (context.workspace / context.config.output_checksum).rename(context.staging / context.config.output_checksum)
-def calculate_signature(state: MkosiState) -> None:
- if not state.config.sign:
+def calculate_signature(context: Context) -> None:
+ if not context.config.sign:
return None
with complete_step("Signing SHA256SUMS…"):
cmdline: list[PathString] = ["gpg", "--detach-sign"]
# Need to specify key before file to sign
- if state.config.key is not None:
- cmdline += ["--default-key", state.config.key]
+ if context.config.key is not None:
+ cmdline += ["--default-key", context.config.key]
cmdline += [
- "--output", state.staging / state.config.output_signature,
- state.staging / state.config.output_checksum,
+ "--output", context.staging / context.config.output_signature,
+ context.staging / context.config.output_checksum,
]
# Set the path of the keyring to use based on the environment if possible and fallback to the default
env |= dict(GPGTTY=os.ttyname(sys.stderr.fileno()))
# Do not output warnings about keyring permissions
- bwrap(state, cmdline, stderr=subprocess.DEVNULL, env=env)
+ bwrap(context, cmdline, stderr=subprocess.DEVNULL, env=env)
def dir_size(path: Union[Path, os.DirEntry[str]]) -> int:
return dir_sum
-def save_manifest(state: MkosiState, manifest: Optional[Manifest]) -> None:
+def save_manifest(context: Context, manifest: Optional[Manifest]) -> None:
if not manifest:
return
if manifest.has_data():
- if ManifestFormat.json in state.config.manifest_format:
- with complete_step(f"Saving manifest {state.config.output_manifest}"):
- with open(state.staging / state.config.output_manifest, 'w') as f:
+ if ManifestFormat.json in context.config.manifest_format:
+ with complete_step(f"Saving manifest {context.config.output_manifest}"):
+ with open(context.staging / context.config.output_manifest, 'w') as f:
manifest.write_json(f)
- if ManifestFormat.changelog in state.config.manifest_format:
- with complete_step(f"Saving report {state.config.output_changelog}"):
- with open(state.staging / state.config.output_changelog, 'w') as f:
+ if ManifestFormat.changelog in context.config.manifest_format:
+ with complete_step(f"Saving report {context.config.output_changelog}"):
+ with open(context.staging / context.config.output_changelog, 'w') as f:
manifest.write_package_report(f)
pass
-def unlink_output(args: MkosiArgs, config: MkosiConfig) -> None:
+def unlink_output(args: Args, config: Config) -> None:
# We remove any cached images if either the user used --force twice, or he/she called "clean" with it
# passed once. Let's also remove the downloaded package cache if the user specified one additional
# "--force".
))
-def cache_tree_paths(config: MkosiConfig) -> tuple[Path, Path, Path]:
+def cache_tree_paths(config: Config) -> tuple[Path, Path, Path]:
fragments = [config.distribution, config.release, config.architecture]
if config.image:
)
-def check_inputs(config: MkosiConfig) -> None:
+def check_inputs(config: Config) -> None:
"""
Make sure all the inputs exist that aren't checked during config parsing because they might be created by an
earlier build.
die(f"Initrd {p} is not a file")
-def check_outputs(config: MkosiConfig) -> None:
+def check_outputs(config: Config) -> None:
for f in (
config.output_with_compression,
config.output_checksum if config.checksum else None,
hint=f"Use ToolsTree=default to get a newer version of '{tools[0]}'.")
-def check_tools(verb: Verb, config: MkosiConfig) -> None:
+def check_tools(verb: Verb, config: Config) -> None:
if verb == Verb.build:
if want_efi(config):
check_systemd_tool(
check_systemd_tool("systemd-nspawn", version="254", reason="boot images")
-def configure_ssh(state: MkosiState) -> None:
- if not state.config.ssh:
+def configure_ssh(context: Context) -> None:
+ if not context.config.ssh:
return
- unitdir = state.root / "usr/lib/systemd/system"
+ unitdir = context.root / "usr/lib/systemd/system"
with umask(~0o755):
unitdir.mkdir(parents=True, exist_ok=True)
)
)
- preset = state.root / "usr/lib/systemd/system-preset/80-mkosi-ssh.preset"
+ preset = context.root / "usr/lib/systemd/system-preset/80-mkosi-ssh.preset"
with umask(~0o755):
preset.parent.mkdir(parents=True, exist_ok=True)
with umask(~0o644):
preset.write_text("enable ssh.socket\n")
-def configure_initrd(state: MkosiState) -> None:
- if state.config.overlay or state.config.output_format.is_extension_image():
+def configure_initrd(context: Context) -> None:
+ if context.config.overlay or context.config.output_format.is_extension_image():
return
if (
- not (state.root / "init").exists() and
- not (state.root / "init").is_symlink() and
- (state.root / "usr/lib/systemd/systemd").exists()
+ not (context.root / "init").exists() and
+ not (context.root / "init").is_symlink() and
+ (context.root / "usr/lib/systemd/systemd").exists()
):
- (state.root / "init").symlink_to("/usr/lib/systemd/systemd")
+ (context.root / "init").symlink_to("/usr/lib/systemd/systemd")
- if not state.config.make_initrd:
+ if not context.config.make_initrd:
return
- if not (state.root / "etc/initrd-release").exists() and not (state.root / "etc/initrd-release").is_symlink():
- (state.root / "etc/initrd-release").symlink_to("/etc/os-release")
+ if not (context.root / "etc/initrd-release").exists() and not (context.root / "etc/initrd-release").is_symlink():
+ (context.root / "etc/initrd-release").symlink_to("/etc/os-release")
-def configure_clock(state: MkosiState) -> None:
- if state.config.overlay or state.config.output_format.is_extension_image():
+def configure_clock(context: Context) -> None:
+ if context.config.overlay or context.config.output_format.is_extension_image():
return
with umask(~0o644):
- (state.root / "usr/lib/clock-epoch").touch()
+ (context.root / "usr/lib/clock-epoch").touch()
-def run_depmod(state: MkosiState) -> None:
- if state.config.overlay or state.config.output_format.is_extension_image():
+def run_depmod(context: Context) -> None:
+ if context.config.overlay or context.config.output_format.is_extension_image():
return
- for kver, _ in gen_kernel_images(state):
+ for kver, _ in gen_kernel_images(context):
process_kernel_modules(
- state.root, kver,
- state.config.kernel_modules_include,
- state.config.kernel_modules_exclude,
- state.config.kernel_modules_include_host,
+ context.root, kver,
+ context.config.kernel_modules_include,
+ context.config.kernel_modules_exclude,
+ context.config.kernel_modules_include_host,
)
with complete_step(f"Running depmod for {kver}"):
- bwrap(state, ["depmod", "--all", "--basedir", state.root, kver])
+ bwrap(context, ["depmod", "--all", "--basedir", context.root, kver])
-def run_sysusers(state: MkosiState) -> None:
+def run_sysusers(context: Context) -> None:
if not shutil.which("systemd-sysusers"):
logging.info("systemd-sysusers is not installed, not generating system users")
return
with complete_step("Generating system users"):
- bwrap(state, ["systemd-sysusers", "--root", state.root])
+ bwrap(context, ["systemd-sysusers", "--root", context.root])
-def run_preset(state: MkosiState) -> None:
+def run_preset(context: Context) -> None:
if not shutil.which("systemctl"):
logging.info("systemctl is not installed, not applying presets")
return
with complete_step("Applying presets…"):
- bwrap(state, ["systemctl", "--root", state.root, "preset-all"])
- bwrap(state, ["systemctl", "--root", state.root, "--global", "preset-all"])
+ bwrap(context, ["systemctl", "--root", context.root, "preset-all"])
+ bwrap(context, ["systemctl", "--root", context.root, "--global", "preset-all"])
-def run_hwdb(state: MkosiState) -> None:
- if state.config.overlay or state.config.output_format.is_extension_image():
+def run_hwdb(context: Context) -> None:
+ if context.config.overlay or context.config.output_format.is_extension_image():
return
if not shutil.which("systemd-hwdb"):
return
with complete_step("Generating hardware database"):
- bwrap(state, ["systemd-hwdb", "--root", state.root, "--usr", "--strict", "update"])
+ bwrap(context, ["systemd-hwdb", "--root", context.root, "--usr", "--strict", "update"])
# Remove any existing hwdb in /etc in favor of the one we just put in /usr.
- (state.root / "etc/udev/hwdb.bin").unlink(missing_ok=True)
+ (context.root / "etc/udev/hwdb.bin").unlink(missing_ok=True)
-def run_firstboot(state: MkosiState) -> None:
- if state.config.overlay or state.config.output_format.is_extension_image():
+def run_firstboot(context: Context) -> None:
+ if context.config.overlay or context.config.output_format.is_extension_image():
return
- password, hashed = state.config.root_password or (None, False)
+ password, hashed = context.config.root_password or (None, False)
pwopt = "--root-password-hashed" if hashed else "--root-password"
pwcred = "passwd.hashed-password.root" if hashed else "passwd.plaintext-password.root"
settings = (
- ("--locale", "firstboot.locale", state.config.locale),
- ("--locale-messages", "firstboot.locale-messages", state.config.locale_messages),
- ("--keymap", "firstboot.keymap", state.config.keymap),
- ("--timezone", "firstboot.timezone", state.config.timezone),
- ("--hostname", None, state.config.hostname),
+ ("--locale", "firstboot.locale", context.config.locale),
+ ("--locale-messages", "firstboot.locale-messages", context.config.locale_messages),
+ ("--keymap", "firstboot.keymap", context.config.keymap),
+ ("--timezone", "firstboot.timezone", context.config.timezone),
+ ("--hostname", None, context.config.hostname),
(pwopt, pwcred, password),
- ("--root-shell", "passwd.shell.root", state.config.root_shell),
+ ("--root-shell", "passwd.shell.root", context.config.root_shell),
)
options = []
return
with complete_step("Applying first boot settings"):
- bwrap(state, ["systemd-firstboot", "--root", state.root, "--force", *options])
+ bwrap(context, ["systemd-firstboot", "--root", context.root, "--force", *options])
# Initrds generally don't ship with only /usr so there's not much point in putting the credentials in
# /usr/lib/credstore.
- if state.config.output_format != OutputFormat.cpio or not state.config.make_initrd:
+ if context.config.output_format != OutputFormat.cpio or not context.config.make_initrd:
with umask(~0o755):
- (state.root / "usr/lib/credstore").mkdir(exist_ok=True)
+ (context.root / "usr/lib/credstore").mkdir(exist_ok=True)
for cred, value in creds:
with umask(~0o600 if "password" in cred else ~0o644):
- (state.root / "usr/lib/credstore" / cred).write_text(value)
+ (context.root / "usr/lib/credstore" / cred).write_text(value)
-def run_selinux_relabel(state: MkosiState) -> None:
- if state.config.selinux_relabel == ConfigFeature.disabled:
+def run_selinux_relabel(context: Context) -> None:
+ if context.config.selinux_relabel == ConfigFeature.disabled:
return
- selinux = state.root / "etc/selinux/config"
+ selinux = context.root / "etc/selinux/config"
if not selinux.exists():
- if state.config.selinux_relabel == ConfigFeature.enabled:
+ if context.config.selinux_relabel == ConfigFeature.enabled:
die("SELinux relabel is requested but could not find selinux config at /etc/selinux/config")
return
- policy = bwrap(state, ["sh", "-c", f". {selinux} && echo $SELINUXTYPE"], stdout=subprocess.PIPE).stdout.strip()
+ policy = bwrap(context, ["sh", "-c", f". {selinux} && echo $SELINUXTYPE"], stdout=subprocess.PIPE).stdout.strip()
if not policy:
- if state.config.selinux_relabel == ConfigFeature.enabled:
+ if context.config.selinux_relabel == ConfigFeature.enabled:
die("SELinux relabel is requested but no selinux policy is configured in /etc/selinux/config")
return
logging.info("setfiles is not installed, not relabeling files")
return
- fc = state.root / "etc/selinux" / policy / "contexts/files/file_contexts"
- binpolicydir = state.root / "etc/selinux" / policy / "policy"
+ fc = context.root / "etc/selinux" / policy / "contexts/files/file_contexts"
+ binpolicydir = context.root / "etc/selinux" / policy / "policy"
try:
# The policy file is named policy.XX where XX is the policy version that indicates what features are
die(f"SELinux binary policy not found in {binpolicydir}")
with complete_step(f"Relabeling files using {policy} policy"):
- bwrap(state, ["setfiles", "-mFr", state.root, "-c", binpolicy, fc, state.root],
- check=state.config.selinux_relabel == ConfigFeature.enabled)
+ bwrap(context, ["setfiles", "-mFr", context.root, "-c", binpolicy, fc, context.root],
+ check=context.config.selinux_relabel == ConfigFeature.enabled)
-def need_build_overlay(config: MkosiConfig) -> bool:
+def need_build_overlay(config: Config) -> bool:
return bool(config.build_scripts and (config.build_packages or config.prepare_scripts))
-def save_cache(state: MkosiState) -> None:
- if not state.config.incremental:
+def save_cache(context: Context) -> None:
+ if not context.config.incremental:
return
- final, build, manifest = cache_tree_paths(state.config)
+ final, build, manifest = cache_tree_paths(context.config)
with complete_step("Installing cache copies"):
rmtree(final)
# We only use the cache-overlay directory for caching if we have a base tree, otherwise we just
# cache the root directory.
- if (state.workspace / "cache-overlay").exists():
- move_tree(state.workspace / "cache-overlay", final, use_subvolumes=state.config.use_subvolumes)
+ if (context.workspace / "cache-overlay").exists():
+ move_tree(context.workspace / "cache-overlay", final, use_subvolumes=context.config.use_subvolumes)
else:
- move_tree(state.root, final, use_subvolumes=state.config.use_subvolumes)
+ move_tree(context.root, final, use_subvolumes=context.config.use_subvolumes)
- if need_build_overlay(state.config) and (state.workspace / "build-overlay").exists():
+ if need_build_overlay(context.config) and (context.workspace / "build-overlay").exists():
rmtree(build)
- move_tree(state.workspace / "build-overlay", build, use_subvolumes=state.config.use_subvolumes)
+ move_tree(context.workspace / "build-overlay", build, use_subvolumes=context.config.use_subvolumes)
manifest.write_text(
json.dumps(
- state.config.cache_manifest(),
- cls=MkosiJsonEncoder,
+ context.config.cache_manifest(),
+ cls=JsonEncoder,
indent=4,
sort_keys=True,
)
)
-def reuse_cache(state: MkosiState) -> bool:
- if not state.config.incremental:
+def reuse_cache(context: Context) -> bool:
+ if not context.config.incremental:
return False
- final, build, manifest = cache_tree_paths(state.config)
- if not final.exists() or (need_build_overlay(state.config) and not build.exists()):
+ final, build, manifest = cache_tree_paths(context.config)
+ if not final.exists() or (need_build_overlay(context.config) and not build.exists()):
return False
if manifest.exists():
prev = json.loads(manifest.read_text())
- if prev != json.loads(json.dumps(state.config.cache_manifest(), cls=MkosiJsonEncoder)):
+ if prev != json.loads(json.dumps(context.config.cache_manifest(), cls=JsonEncoder)):
return False
else:
return False
return False
with complete_step("Copying cached trees"):
- copy_tree(final, state.root, use_subvolumes=state.config.use_subvolumes)
- if need_build_overlay(state.config):
- (state.workspace / "build-overlay").symlink_to(build)
+ copy_tree(final, context.root, use_subvolumes=context.config.use_subvolumes)
+ if need_build_overlay(context.config):
+ (context.workspace / "build-overlay").symlink_to(build)
return True
-def save_uki_components(state: MkosiState) -> tuple[Optional[Path], Optional[str], Optional[Path]]:
- if state.config.output_format not in (OutputFormat.uki, OutputFormat.esp):
+def save_uki_components(context: Context) -> tuple[Optional[Path], Optional[str], Optional[Path]]:
+ if context.config.output_format not in (OutputFormat.uki, OutputFormat.esp):
return None, None, None
try:
- kver, kimg = next(gen_kernel_images(state))
+ kver, kimg = next(gen_kernel_images(context))
except StopIteration:
die("A kernel must be installed in the image to build a UKI")
- kimg = shutil.copy2(state.root / kimg, state.workspace)
+ kimg = shutil.copy2(context.root / kimg, context.workspace)
- if not (arch := state.config.architecture.to_efi()):
- die(f"Architecture {state.config.architecture} does not support UEFI")
+ if not (arch := context.config.architecture.to_efi()):
+ die(f"Architecture {context.config.architecture} does not support UEFI")
- stub = state.root / f"usr/lib/systemd/boot/efi/linux{arch}.efi.stub"
+ stub = context.root / f"usr/lib/systemd/boot/efi/linux{arch}.efi.stub"
if not stub.exists():
- die(f"sd-stub not found at /{stub.relative_to(state.root)} in the image")
+ die(f"sd-stub not found at /{stub.relative_to(context.root)} in the image")
- stub = shutil.copy2(stub, state.workspace)
+ stub = shutil.copy2(stub, context.workspace)
return stub, kver, kimg
def make_image(
- state: MkosiState,
+ context: Context,
msg: str,
skip: Sequence[str] = [],
split: bool = False,
"--dry-run=no",
"--json=pretty",
"--no-pager",
- f"--offline={yes_no(state.config.repart_offline)}",
- "--seed", str(state.config.seed) if state.config.seed else "random",
- state.staging / state.config.output_with_format,
+ f"--offline={yes_no(context.config.repart_offline)}",
+ "--seed", str(context.config.seed) if context.config.seed else "random",
+ context.staging / context.config.output_with_format,
]
if root:
cmdline += ["--root", root]
- if not state.config.architecture.is_native():
- cmdline += ["--architecture", str(state.config.architecture)]
- if not (state.staging / state.config.output_with_format).exists():
+ if not context.config.architecture.is_native():
+ cmdline += ["--architecture", str(context.config.architecture)]
+ if not (context.staging / context.config.output_with_format).exists():
cmdline += ["--empty=create"]
- if state.config.passphrase:
- cmdline += ["--key-file", state.config.passphrase]
- if state.config.verity_key:
- cmdline += ["--private-key", state.config.verity_key]
- if state.config.verity_certificate:
- cmdline += ["--certificate", state.config.verity_certificate]
+ if context.config.passphrase:
+ cmdline += ["--key-file", context.config.passphrase]
+ if context.config.verity_key:
+ cmdline += ["--private-key", context.config.verity_key]
+ if context.config.verity_certificate:
+ cmdline += ["--certificate", context.config.verity_certificate]
if skip:
cmdline += ["--defer-partitions", ",".join(skip)]
if split:
cmdline += ["--split=yes"]
- if state.config.sector_size:
- cmdline += ["--sector-size", str(state.config.sector_size)]
+ if context.config.sector_size:
+ cmdline += ["--sector-size", str(context.config.sector_size)]
for d in definitions:
cmdline += ["--definitions", d]
env = {
option: value
- for option, value in state.config.environment.items()
+ for option, value in context.config.environment.items()
if option.startswith("SYSTEMD_REPART_MKFS_OPTIONS_") or option == "SOURCE_DATE_EPOCH"
}
with complete_step(msg):
output = json.loads(
- bwrap(state, cmdline, devices=not state.config.repart_offline, stdout=subprocess.PIPE, env=env).stdout
+ bwrap(context, cmdline, devices=not context.config.repart_offline, stdout=subprocess.PIPE, env=env).stdout
)
logging.debug(json.dumps(output, indent=4))
if split:
for p in partitions:
if p.split_path:
- maybe_compress(state, state.config.compress_output, p.split_path)
+ maybe_compress(context, context.config.compress_output, p.split_path)
return partitions
def make_disk(
- state: MkosiState,
+ context: Context,
msg: str,
skip: Sequence[str] = [],
split: bool = False,
) -> list[Partition]:
- if state.config.output_format != OutputFormat.disk:
+ if context.config.output_format != OutputFormat.disk:
return []
- if state.config.repart_dirs:
- definitions = state.config.repart_dirs
+ if context.config.repart_dirs:
+ definitions = context.config.repart_dirs
else:
- defaults = state.workspace / "repart-definitions"
+ defaults = context.workspace / "repart-definitions"
if not defaults.exists():
defaults.mkdir()
- if (arch := state.config.architecture.to_efi()):
- bootloader = state.root / f"efi/EFI/BOOT/BOOT{arch.upper()}.EFI"
+ if (arch := context.config.architecture.to_efi()):
+ bootloader = context.root / f"efi/EFI/BOOT/BOOT{arch.upper()}.EFI"
else:
bootloader = None
esp = (
- state.config.bootable == ConfigFeature.enabled or
- (state.config.bootable == ConfigFeature.auto and bootloader and bootloader.exists())
+ context.config.bootable == ConfigFeature.enabled or
+ (context.config.bootable == ConfigFeature.auto and bootloader and bootloader.exists())
)
- bios = (state.config.bootable != ConfigFeature.disabled and want_grub_bios(state))
+ bios = (context.config.bootable != ConfigFeature.disabled and want_grub_bios(context))
if esp or bios:
# Even if we're doing BIOS, let's still use the ESP to store the kernels, initrds and grub
f"""\
[Partition]
Type=root
- Format={state.config.distribution.filesystem()}
+ Format={context.config.distribution.filesystem()}
CopyFiles=/
Minimize=guess
"""
definitions = [defaults]
- return make_image(state, msg=msg, skip=skip, split=split, root=state.root, definitions=definitions)
+ return make_image(context, msg=msg, skip=skip, split=split, root=context.root, definitions=definitions)
-def make_esp(state: MkosiState, uki: Path) -> list[Partition]:
- if not (arch := state.config.architecture.to_efi()):
- die(f"Architecture {state.config.architecture} does not support UEFI")
+def make_esp(context: Context, uki: Path) -> list[Partition]:
+ if not (arch := context.config.architecture.to_efi()):
+ die(f"Architecture {context.config.architecture} does not support UEFI")
- definitions = state.workspace / "esp-definitions"
+ definitions = context.workspace / "esp-definitions"
definitions.mkdir(exist_ok=True)
# Use a minimum of 36MB or 260MB depending on sector size because otherwise the generated FAT filesystem will have
# too few clusters to be considered a FAT32 filesystem by OVMF which will refuse to boot from it.
# See https://superuser.com/questions/1702331/what-is-the-minimum-size-of-a-4k-native-partition-when-formatted-with-fat32/1717643#1717643
- if state.config.sector_size == 512:
+ if context.config.sector_size == 512:
m = 36
# TODO: Figure out minimum size for 2K sector size
else:
)
)
- return make_image(state, msg="Generating ESP image", definitions=[definitions])
+ return make_image(context, msg="Generating ESP image", definitions=[definitions])
-def make_extension_image(state: MkosiState, output: Path) -> None:
+def make_extension_image(context: Context, output: Path) -> None:
cmdline: list[PathString] = [
"systemd-repart",
- "--root", state.root,
+ "--root", context.root,
"--dry-run=no",
"--no-pager",
- f"--offline={yes_no(state.config.repart_offline)}",
- "--seed", str(state.config.seed) if state.config.seed else "random",
+ f"--offline={yes_no(context.config.repart_offline)}",
+ "--seed", str(context.config.seed) if context.config.seed else "random",
"--empty=create",
"--size=auto",
output,
]
- if not state.config.architecture.is_native():
- cmdline += ["--architecture", str(state.config.architecture)]
- if state.config.passphrase:
- cmdline += ["--key-file", state.config.passphrase]
- if state.config.verity_key:
- cmdline += ["--private-key", state.config.verity_key]
- if state.config.verity_certificate:
- cmdline += ["--certificate", state.config.verity_certificate]
- if state.config.sector_size:
- cmdline += ["--sector-size", str(state.config.sector_size)]
+ if not context.config.architecture.is_native():
+ cmdline += ["--architecture", str(context.config.architecture)]
+ if context.config.passphrase:
+ cmdline += ["--key-file", context.config.passphrase]
+ if context.config.verity_key:
+ cmdline += ["--private-key", context.config.verity_key]
+ if context.config.verity_certificate:
+ cmdline += ["--certificate", context.config.verity_certificate]
+ if context.config.sector_size:
+ cmdline += ["--sector-size", str(context.config.sector_size)]
env = {
option: value
- for option, value in state.config.environment.items()
+ for option, value in context.config.environment.items()
if option.startswith("SYSTEMD_REPART_MKFS_OPTIONS_") or option == "SOURCE_DATE_EPOCH"
}
with (
resource_path(mkosi.resources) as r,
- complete_step(f"Building {state.config.output_format} extension image")
+ complete_step(f"Building {context.config.output_format} extension image")
):
bwrap(
- state,
- cmdline + ["--definitions", r / f"repart/definitions/{state.config.output_format}.repart.d"],
- devices=not state.config.repart_offline,
+ context,
+ cmdline + ["--definitions", r / f"repart/definitions/{context.config.output_format}.repart.d"],
+ devices=not context.config.repart_offline,
env=env,
)
-def finalize_staging(state: MkosiState) -> None:
+def finalize_staging(context: Context) -> None:
# Our output unlinking logic removes everything prefixed with the name of the image, so let's make
# sure that everything we put into the output directory is prefixed with the name of the output.
- for f in state.staging.iterdir():
+ for f in context.staging.iterdir():
# Skip the symlink we create without the version that points to the output with the version.
- if f.name.startswith(state.config.output) and f.is_symlink():
+ if f.name.startswith(context.config.output) and f.is_symlink():
continue
name = f.name
- if not name.startswith(state.config.output):
- name = f"{state.config.output}-{name}"
+ if not name.startswith(context.config.output):
+ name = f"{context.config.output}-{name}"
if name != f.name:
- f.rename(state.staging / name)
+ f.rename(context.staging / name)
- for f in state.staging.iterdir():
+ for f in context.staging.iterdir():
# Make sure all build outputs that are not directories are owned by the user running mkosi.
if not f.is_dir():
os.chown(f, INVOKING_USER.uid, INVOKING_USER.gid, follow_symlinks=False)
- move_tree(f, state.config.output_dir_or_cwd(), use_subvolumes=state.config.use_subvolumes)
+ move_tree(f, context.config.output_dir_or_cwd(), use_subvolumes=context.config.use_subvolumes)
def normalize_mtime(root: Path, mtime: Optional[int], directory: Optional[Path] = None) -> None:
@contextlib.contextmanager
-def setup_workspace(args: MkosiArgs, config: MkosiConfig) -> Iterator[Path]:
+def setup_workspace(args: Args, config: Config) -> Iterator[Path]:
with contextlib.ExitStack() as stack:
workspace = Path(tempfile.mkdtemp(dir=config.workspace_dir_or_default(), prefix="mkosi-workspace"))
stack.callback(lambda: rmtree(workspace))
raise
-def build_image(args: MkosiArgs, config: MkosiConfig) -> None:
+def build_image(args: Args, config: Config) -> None:
manifest = Manifest(config) if config.manifest_format else None
with setup_workspace(args, config) as workspace:
- state = MkosiState(args, config, workspace)
- install_package_manager_trees(state)
+ context = Context(args, config, workspace)
+ install_package_manager_trees(context)
- with mount_base_trees(state):
- install_base_trees(state)
- install_skeleton_trees(state)
- cached = reuse_cache(state)
+ with mount_base_trees(context):
+ install_base_trees(context)
+ install_skeleton_trees(context)
+ cached = reuse_cache(context)
- state.config.distribution.setup(state)
+ context.config.distribution.setup(context)
if not cached:
- with mount_cache_overlay(state):
- install_distribution(state)
- run_prepare_scripts(state, build=False)
- install_build_packages(state)
- run_prepare_scripts(state, build=True)
+ with mount_cache_overlay(context):
+ install_distribution(context)
+ run_prepare_scripts(context, build=False)
+ install_build_packages(context)
+ run_prepare_scripts(context, build=True)
- save_cache(state)
- reuse_cache(state)
+ save_cache(context)
+ reuse_cache(context)
- check_root_populated(state)
- run_build_scripts(state)
+ check_root_populated(context)
+ run_build_scripts(context)
- if state.config.output_format == OutputFormat.none:
+ if context.config.output_format == OutputFormat.none:
# Touch an empty file to indicate the image was built.
- (state.staging / state.config.output).touch()
- finalize_staging(state)
+ (context.staging / context.config.output).touch()
+ finalize_staging(context)
return
- install_build_dest(state)
- install_extra_trees(state)
- run_postinst_scripts(state)
+ install_build_dest(context)
+ install_extra_trees(context)
+ run_postinst_scripts(context)
- configure_autologin(state)
- configure_os_release(state)
- configure_initrd(state)
- configure_ssh(state)
- configure_clock(state)
+ configure_autologin(context)
+ configure_os_release(context)
+ configure_initrd(context)
+ configure_ssh(context)
+ configure_clock(context)
- install_systemd_boot(state)
- install_shim(state)
- run_sysusers(state)
- run_preset(state)
- run_depmod(state)
- run_firstboot(state)
- run_hwdb(state)
+ install_systemd_boot(context)
+ install_shim(context)
+ run_sysusers(context)
+ run_preset(context)
+ run_depmod(context)
+ 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(state)
+ stub, kver, kimg = save_uki_components(context)
- remove_packages(state)
+ remove_packages(context)
if manifest:
with complete_step("Recording packages in manifest…"):
- manifest.record_packages(state.root)
-
- clean_package_manager_metadata(state)
- remove_files(state)
- run_selinux_relabel(state)
- run_finalize_scripts(state)
-
- normalize_mtime(state.root, state.config.source_date_epoch)
- partitions = make_disk(state, skip=("esp", "xbootldr"), msg="Generating disk image")
- install_uki(state, partitions)
- prepare_grub_efi(state)
- prepare_grub_bios(state, partitions)
- normalize_mtime(state.root, state.config.source_date_epoch, directory=Path("boot"))
- normalize_mtime(state.root, state.config.source_date_epoch, directory=Path("efi"))
- partitions = make_disk(state, msg="Formatting ESP/XBOOTLDR partitions")
- install_grub_bios(state, partitions)
-
- if state.config.split_artifacts:
- make_disk(state, split=True, msg="Extracting partitions")
-
- copy_vmlinuz(state)
- copy_initrd(state)
-
- if state.config.output_format == OutputFormat.tar:
- make_tar(state, state.root, state.staging / state.config.output_with_format)
- elif state.config.output_format == OutputFormat.cpio:
- make_cpio(state, state.root, state.staging / state.config.output_with_format)
- elif state.config.output_format == OutputFormat.uki:
+ manifest.record_packages(context.root)
+
+ clean_package_manager_metadata(context)
+ remove_files(context)
+ run_selinux_relabel(context)
+ run_finalize_scripts(context)
+
+ normalize_mtime(context.root, context.config.source_date_epoch)
+ partitions = make_disk(context, skip=("esp", "xbootldr"), msg="Generating disk image")
+ install_uki(context, partitions)
+ prepare_grub_efi(context)
+ prepare_grub_bios(context, partitions)
+ normalize_mtime(context.root, context.config.source_date_epoch, directory=Path("boot"))
+ normalize_mtime(context.root, context.config.source_date_epoch, directory=Path("efi"))
+ partitions = make_disk(context, msg="Formatting ESP/XBOOTLDR partitions")
+ install_grub_bios(context, partitions)
+
+ if context.config.split_artifacts:
+ make_disk(context, split=True, msg="Extracting partitions")
+
+ copy_vmlinuz(context)
+ copy_initrd(context)
+
+ if context.config.output_format == OutputFormat.tar:
+ make_tar(context, context.root, context.staging / context.config.output_with_format)
+ elif context.config.output_format == OutputFormat.cpio:
+ make_cpio(context, context.root, context.staging / context.config.output_with_format)
+ elif context.config.output_format == OutputFormat.uki:
assert stub and kver and kimg
- make_uki(state, stub, kver, kimg, state.staging / state.config.output_with_format)
- elif state.config.output_format == OutputFormat.esp:
+ make_uki(context, stub, kver, kimg, context.staging / context.config.output_with_format)
+ elif context.config.output_format == OutputFormat.esp:
assert stub and kver and kimg
- make_uki(state, stub, kver, kimg, state.staging / state.config.output_split_uki)
- make_esp(state, state.staging / state.config.output_split_uki)
- elif state.config.output_format.is_extension_image():
- make_extension_image(state, state.staging / state.config.output_with_format)
- elif state.config.output_format == OutputFormat.directory:
- state.root.rename(state.staging / state.config.output_with_format)
+ make_uki(context, stub, kver, kimg, 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)
+ elif context.config.output_format == OutputFormat.directory:
+ context.root.rename(context.staging / context.config.output_with_format)
if config.output_format not in (OutputFormat.uki, OutputFormat.esp):
- maybe_compress(state, state.config.compress_output,
- state.staging / state.config.output_with_format,
- state.staging / state.config.output_with_compression)
+ maybe_compress(context, context.config.compress_output,
+ context.staging / context.config.output_with_format,
+ context.staging / context.config.output_with_compression)
- calculate_sha256sum(state)
- calculate_signature(state)
- save_manifest(state, manifest)
+ calculate_sha256sum(context)
+ calculate_signature(context)
+ save_manifest(context, manifest)
- output_base = state.staging / state.config.output
+ output_base = context.staging / context.config.output
if not output_base.exists() or output_base.is_symlink():
output_base.unlink(missing_ok=True)
- output_base.symlink_to(state.config.output_with_compression)
+ output_base.symlink_to(context.config.output_with_compression)
- finalize_staging(state)
+ finalize_staging(context)
print_output_size(config.output_dir_or_cwd() / config.output)
@contextlib.contextmanager
-def acl_maybe_toggle(config: MkosiConfig, root: Path, uid: int, *, always: bool) -> Iterator[None]:
+def acl_maybe_toggle(config: Config, root: Path, uid: int, *, always: bool) -> Iterator[None]:
if not config.acl:
yield
return
@contextlib.contextmanager
-def acl_toggle_build(config: MkosiConfig, uid: int) -> Iterator[None]:
+def acl_toggle_build(config: Config, uid: int) -> Iterator[None]:
if not config.acl:
yield
return
@contextlib.contextmanager
-def acl_toggle_boot(config: MkosiConfig, uid: int) -> Iterator[None]:
+def acl_toggle_boot(config: Config, uid: int) -> Iterator[None]:
if not config.acl or config.output_format != OutputFormat.directory:
yield
return
yield
-def run_shell(args: MkosiArgs, config: MkosiConfig) -> None:
+def run_shell(args: Args, config: Config) -> None:
cmdline: list[PathString] = ["systemd-nspawn", "--quiet"]
# If we copied in a .nspawn file, make sure it's actually honoured
run(cmdline, stdin=sys.stdin, stdout=sys.stdout, env=os.environ, log=False)
-def run_systemd_tool(tool: str, args: MkosiArgs, config: MkosiConfig) -> None:
+def run_systemd_tool(tool: str, args: Args, config: Config) -> None:
if config.output_format not in (OutputFormat.disk, OutputFormat.directory):
die(f"{config.output_format} images cannot be inspected with {tool}")
)
-def run_journalctl(args: MkosiArgs, config: MkosiConfig) -> None:
+def run_journalctl(args: Args, config: Config) -> None:
run_systemd_tool("journalctl", args, config)
-def run_coredumpctl(args: MkosiArgs, config: MkosiConfig) -> None:
+def run_coredumpctl(args: Args, config: Config) -> None:
run_systemd_tool("coredumpctl", args, config)
-def run_serve(config: MkosiConfig) -> None:
+def run_serve(config: Config) -> None:
"""Serve the output directory via a tiny HTTP server"""
port = "8081"
user=INVOKING_USER.uid, group=INVOKING_USER.gid, stdin=sys.stdin, stdout=sys.stdout)
-def generate_key_cert_pair(args: MkosiArgs) -> None:
+def generate_key_cert_pair(args: Args) -> None:
"""Generate a private key and accompanying X509 certificate using openssl"""
keylength = 2048
os.chown("mkosi.version", INVOKING_USER.uid, INVOKING_USER.gid)
-def show_docs(args: MkosiArgs) -> None:
+def show_docs(args: Args) -> None:
if args.doc_format == DocFormat.auto:
formats = [DocFormat.man, DocFormat.pandoc, DocFormat.markdown, DocFormat.system]
else:
return s.replace("%u", INVOKING_USER.name())
-def needs_build(args: MkosiArgs, config: MkosiConfig) -> bool:
+def needs_build(args: Args, config: Config) -> bool:
return (
args.verb.needs_build() and
(args.force > 0 or not (config.output_dir_or_cwd() / config.output_with_compression).exists())
@contextlib.contextmanager
-def prepend_to_environ_path(config: MkosiConfig) -> Iterator[None]:
+def prepend_to_environ_path(config: Config) -> Iterator[None]:
if config.tools_tree or not config.extra_search_paths:
yield
return
os.environ["PATH"] = ":".join(olds)
-def finalize_tools(args: MkosiArgs, images: Sequence[MkosiConfig]) -> Sequence[MkosiConfig]:
+def finalize_tools(args: Args, images: Sequence[Config]) -> Sequence[Config]:
new = []
for config in images:
return new
-def check_workspace_directory(config: MkosiConfig) -> None:
+def check_workspace_directory(config: Config) -> None:
wd = config.workspace_dir_or_default()
if wd.is_relative_to(Path.cwd()):
hint="Use WorkspaceDirectory= to configure a different workspace directory")
-def run_verb(args: MkosiArgs, images: Sequence[MkosiConfig]) -> None:
+def run_verb(args: Args, images: Sequence[Config]) -> None:
if args.verb.needs_root() and os.getuid() != 0:
die(f"Must be root to run the {args.verb} command")
if args.verb == Verb.genkey:
return generate_key_cert_pair(args)
- if all(config == MkosiConfig.default() for config in images):
+ if all(config == Config.default() for config in images):
die("No configuration found",
hint="Make sure you're running mkosi from a directory with configuration files")
if args.json:
text = json.dumps(
{"Images": [config.to_dict() for config in images]},
- cls=MkosiJsonEncoder,
+ cls=JsonEncoder,
indent=4,
sort_keys=True
)
@dataclasses.dataclass(frozen=True)
-class MkosiConfigSetting:
+class ConfigSetting:
dest: str
section: str
parse: ConfigParseCallback = config_parse_string
@dataclasses.dataclass(frozen=True)
-class MkosiMatch:
+class Match:
name: str
match: Callable[[str], bool]
@dataclasses.dataclass(frozen=True)
-class MkosiArgs:
+class Args:
verb: Verb
cmdline: list[str]
force: int
json: bool
@classmethod
- def default(cls) -> "MkosiArgs":
+ def default(cls) -> "Args":
"""Alternative constructor to generate an all-default MkosiArgs.
This prevents MkosiArgs being generated with defaults values implicitly.
return args
@classmethod
- def from_namespace(cls, ns: argparse.Namespace) -> "MkosiArgs":
+ def from_namespace(cls, ns: argparse.Namespace) -> "Args":
return cls(**{
k: v for k, v in vars(ns).items()
if k in inspect.signature(cls).parameters
def to_json(self, *, indent: Optional[int] = 4, sort_keys: bool = True) -> str:
"""Dump MkosiArgs as JSON string."""
- return json.dumps(self.to_dict(), cls=MkosiJsonEncoder, indent=indent, sort_keys=sort_keys)
+ return json.dumps(self.to_dict(), cls=JsonEncoder, indent=indent, sort_keys=sort_keys)
@classmethod
def _load_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> dict[str, Any]:
return {(tk := key_transformer(k)): value_transformer(tk, v) for k, v in j.items()}
@classmethod
- def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "MkosiArgs":
+ def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Args":
"""Instantiate a MkosiArgs object from a full JSON dump."""
j = cls._load_json(s)
return cls(**j)
@classmethod
- def from_partial_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "MkosiArgs":
+ def from_partial_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Args":
"""Return a new MkosiArgs with defaults overwritten by the attributes from passed in JSON."""
j = cls._load_json(s)
return dataclasses.replace(cls.default(), **j)
@dataclasses.dataclass(frozen=True)
-class MkosiConfig:
+class Config:
"""Type-hinted storage for command line arguments.
Only user configuration is stored here while dynamic state exists in
- MkosiState. If a field of the same name exists in both classes always
- access the value from state.
+ Mkosicontext. If a field of the same name exists in both classes always
+ access the value from context.
"""
profile: Optional[str]
return Path("/var/tmp")
@classmethod
- def default(cls) -> "MkosiConfig":
+ def default(cls) -> "Config":
"""Alternative constructor to generate an all-default MkosiArgs.
This prevents MkosiArgs being generated with defaults values implicitly.
return config
@classmethod
- def from_namespace(cls, ns: argparse.Namespace) -> "MkosiConfig":
+ def from_namespace(cls, ns: argparse.Namespace) -> "Config":
return cls(**{
k: v for k, v in vars(ns).items()
if k in inspect.signature(cls).parameters
def to_json(self, *, indent: Optional[int] = 4, sort_keys: bool = True) -> str:
"""Dump MkosiConfig as JSON string."""
- return json.dumps(self.to_dict(), cls=MkosiJsonEncoder, indent=indent, sort_keys=sort_keys)
+ return json.dumps(self.to_dict(), cls=JsonEncoder, indent=indent, sort_keys=sort_keys)
@classmethod
def _load_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> dict[str, Any]:
return {(tk := key_transformer(k)): value_transformer(tk, v) for k, v in j.items()}
@classmethod
- def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "MkosiConfig":
+ def from_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Config":
"""Instantiate a MkosiConfig object from a full JSON dump."""
j = cls._load_json(s)
return cls(**j)
@classmethod
- def from_partial_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "MkosiConfig":
+ def from_partial_json(cls, s: Union[str, dict[str, Any], SupportsRead[str], SupportsRead[bytes]]) -> "Config":
"""Return a new MkosiConfig with defaults overwritten by the attributes from passed in JSON."""
j = cls._load_json(s)
return dataclasses.replace(cls.default(), **j)
SETTINGS = (
- MkosiConfigSetting(
+ ConfigSetting(
dest="include",
section="Config",
parse=config_make_list_parser(delimiter=",", reset=False, parse=make_path_parser()),
help="Include configuration from the specified file or directory",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="initrd_include",
section="Config",
parse=config_make_list_parser(delimiter=",", reset=False, parse=make_path_parser()),
help="Include configuration from the specified file or directory when building the initrd",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="profile",
section="Config",
help="Build the specified profile",
parse=config_parse_profile,
match=config_make_string_matcher(),
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="images",
compat_names=("Presets",),
long="--image",
parse=config_make_list_parser(delimiter=","),
help="Specify which images to build",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="dependencies",
long="--dependency",
section="Config",
parse=config_make_list_parser(delimiter=","),
help="Specify other images that this image depends on",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="minimum_version",
section="Config",
parse=config_parse_minimum_version,
help="Specify the minimum required mkosi version",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="distribution",
short="-d",
section="Distribution",
choices=Distribution.values(),
help="Distribution to install",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="release",
short="-r",
section="Distribution",
default_factory_depends=("distribution",),
help="Distribution release to install",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="architecture",
section="Distribution",
specifier="a",
choices=Architecture.values(),
help="Override the architecture of installation",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="mirror",
short="-m",
section="Distribution",
help="Distribution mirror to use",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="local_mirror",
section="Distribution",
help="Use a single local, flat and plain mirror to build the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="repository_key_check",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Controls signature and key checks on repositories",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="repositories",
metavar="REPOS",
section="Distribution",
parse=config_make_list_parser(delimiter=","),
help="Repositories to use",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="cache_only",
metavar="BOOL",
section="Distribution",
parse=config_parse_boolean,
help="Only use the package cache when installing packages",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="package_manager_trees",
long="--package-manager-tree",
metavar="PATH",
help="Use a package manager tree to configure the package manager",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="output_format",
short="-t",
long="--format",
choices=OutputFormat.values(),
help="Output Format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="manifest_format",
metavar="FORMAT",
section="Output",
parse=config_make_list_parser(delimiter=",", parse=make_enum_parser(ManifestFormat)),
help="Manifest Format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="output",
short="-o",
metavar="NAME",
default_factory_depends=("image_id", "image_version"),
help="Output name",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="compress_output",
metavar="ALG",
nargs="?",
default_factory_depends=("distribution", "release", "output_format"),
help="Enable whole-output compression (with images or archives)",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="output_dir",
short="-O",
metavar="DIR",
paths=("mkosi.output",),
help="Output directory",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="workspace_dir",
metavar="DIR",
name="WorkspaceDirectory",
parse=config_make_path_parser(required=False),
help="Workspace directory",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="cache_dir",
metavar="PATH",
name="CacheDirectory",
paths=("mkosi.cache",),
help="Package cache path",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="build_dir",
metavar="PATH",
name="BuildDirectory",
paths=("mkosi.builddir",),
help="Path to use as persistent build directory",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="image_version",
match=config_match_version,
section="Output",
paths=("mkosi.version",),
path_read_text=True,
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="image_id",
match=config_make_string_matcher(allow_globs=True),
section="Output",
specifier="i",
help="Set ID for image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="split_artifacts",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Generate split partitions",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="repart_dirs",
long="--repart-dir",
metavar="PATH",
paths=("mkosi.repart",),
help="Directory containing systemd-repart partition definitions",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="sector_size",
section="Output",
parse=config_parse_sector_size,
help="Set the disk image sector size",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="repart_offline",
section="Output",
parse=config_parse_boolean,
help="Build disk images without using loopback devices",
default=True,
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="overlay",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Only output the additions on top of the given base trees",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="use_subvolumes",
metavar="FEATURE",
nargs="?",
parse=config_parse_feature,
help="Use btrfs subvolumes for faster directory operations where possible",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="seed",
metavar="UUID",
section="Output",
help="Set the seed for systemd-repart",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="packages",
short="-p",
long="--package",
parse=config_make_list_parser(delimiter=","),
help="Add an additional package to the OS image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="build_packages",
long="--build-package",
metavar="PACKAGE",
parse=config_make_list_parser(delimiter=","),
help="Additional packages needed for build scripts",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="with_recommends",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Install recommended packages",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="with_docs",
metavar="BOOL",
nargs="?",
default=True,
help="Install documentation",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="base_trees",
long='--base-tree',
metavar='PATH',
parse=config_make_list_parser(delimiter=",", parse=make_path_parser(required=False)),
help='Use the given tree as base tree (e.g. lower sysext layer)',
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="skeleton_trees",
long="--skeleton-tree",
metavar="PATH",
path_default=False,
help="Use a skeleton tree to bootstrap the image before installing anything",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="extra_trees",
long="--extra-tree",
metavar="PATH",
path_default=False,
help="Copy an extra tree on top of image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="remove_packages",
long="--remove-package",
metavar="PACKAGE",
parse=config_make_list_parser(delimiter=","),
help="Remove package from the image OS image after installation",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="remove_files",
metavar="GLOB",
section="Content",
parse=config_make_list_parser(delimiter=","),
help="Remove files from built image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="clean_package_metadata",
metavar="FEATURE",
section="Content",
parse=config_parse_feature,
help="Remove package manager database and other files",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="source_date_epoch",
metavar="TIMESTAMP",
section="Content",
default_factory_depends=("environment",),
help="Set the $SOURCE_DATE_EPOCH timestamp",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="prepare_scripts",
long="--prepare-script",
metavar="PATH",
help="Prepare script to run inside the image before it is cached",
compat_names=("PrepareScript",),
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="build_scripts",
long="--build-script",
metavar="PATH",
help="Build script to run inside image",
compat_names=("BuildScript",),
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="postinst_scripts",
long="--postinst-script",
metavar="PATH",
help="Postinstall script to run inside image",
compat_names=("PostInstallationScript",),
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="finalize_scripts",
long="--finalize-script",
metavar="PATH",
help="Postinstall script to run outside image",
compat_names=("FinalizeScript",),
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="build_sources",
metavar="PATH",
section="Content",
match=config_match_build_sources,
help="Path for sources to build",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="build_sources_ephemeral",
metavar="BOOL",
section="Content",
parse=config_parse_boolean,
help="Make build sources ephemeral when running scripts",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="environment",
short="-E",
metavar="NAME[=VALUE]",
parse=config_make_list_parser(delimiter=" ", unescape=True),
help="Set an environment variable when running scripts",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="environment_files",
long="--env-file",
metavar="PATH",
path_default=False,
help="Enviroment files to set when running scripts",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="with_tests",
short="-T",
long="--without-tests",
default=True,
help="Do not run tests as part of build scripts, if supported",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="with_network",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Run build and postinst scripts with network access (instead of private network)",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="bootable",
metavar="FEATURE",
nargs="?",
match=config_match_feature,
help="Generate ESP partition with systemd-boot and UKIs for installed kernels",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="bootloader",
metavar="BOOTLOADER",
section="Content",
default=Bootloader.systemd_boot,
help="Specify which UEFI bootloader to use",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="bios_bootloader",
metavar="BOOTLOADER",
section="Content",
default=BiosBootloader.none,
help="Specify which BIOS bootloader to use",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="shim_bootloader",
metavar="BOOTLOADER",
section="Content",
default=ShimBootloader.none,
help="Specify whether to use shim",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="initrds",
long="--initrd",
metavar="PATH",
parse=config_make_list_parser(delimiter=",", parse=make_path_parser(required=False)),
help="Add a user-provided initrd to image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="initrd_packages",
long="--initrd-package",
metavar="PACKAGE",
parse=config_make_list_parser(delimiter=","),
help="Add additional packages to the default initrd",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_command_line",
metavar="OPTIONS",
section="Content",
default_factory=config_default_kernel_command_line,
help="Set the kernel command line (only bootable images)",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_include",
metavar="REGEX",
section="Content",
parse=config_make_list_parser(delimiter=","),
help="Include the specified kernel modules in the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_include_host",
metavar="BOOL",
section="Content",
parse=config_parse_boolean,
help="Include the currently loaded modules on the host in the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_exclude",
metavar="REGEX",
section="Content",
parse=config_make_list_parser(delimiter=","),
help="Exclude the specified kernel modules from the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_initrd",
metavar="BOOL",
nargs="?",
default=True,
help="When building a bootable image, add an extra initrd containing the kernel modules",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_initrd_include",
metavar="REGEX",
section="Content",
parse=config_make_list_parser(delimiter=","),
help="When building a kernel modules initrd, include the specified kernel modules",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_initrd_include_host",
metavar="BOOL",
section="Content",
parse=config_parse_boolean,
help="When building a kernel modules initrd, include the currently loaded modules on the host in the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_modules_initrd_exclude",
metavar="REGEX",
section="Content",
parse=config_make_list_parser(delimiter=","),
help="When building a kernel modules initrd, exclude the specified kernel modules",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="locale",
section="Content",
parse=config_parse_string,
help="Set the system locale",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="locale_messages",
metavar="LOCALE",
section="Content",
parse=config_parse_string,
help="Set the messages locale",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="keymap",
metavar="KEYMAP",
section="Content",
parse=config_parse_string,
help="Set the system keymap",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="timezone",
metavar="TIMEZONE",
section="Content",
parse=config_parse_string,
help="Set the system timezone",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="hostname",
metavar="HOSTNAME",
section="Content",
parse=config_parse_string,
help="Set the system hostname",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="root_password",
metavar="PASSWORD",
section="Content",
path_secret=True,
help="Set the password for root",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="root_shell",
metavar="SHELL",
section="Content",
parse=config_parse_string,
help="Set the shell for root",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="autologin",
short="-a",
metavar="BOOL",
parse=config_parse_boolean,
help="Enable root autologin",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="make_initrd",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Make sure the image can be used as an initramfs",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="ssh",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Set up SSH access from the host to the final image via 'mkosi ssh'",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="selinux_relabel",
name="SELinuxRelabel",
metavar="FEATURE",
help="Specify whether to relabel all files with setfiles",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="secure_boot",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Sign the resulting kernel/initrd image for UEFI SecureBoot",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="secure_boot_auto_enroll",
metavar="BOOL",
section="Validation",
default=True,
help="Automatically enroll the secureboot signing key on first boot",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="secure_boot_key",
metavar="PATH",
section="Validation",
paths=("mkosi.key",),
help="UEFI SecureBoot private key in PEM format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="secure_boot_certificate",
metavar="PATH",
section="Validation",
paths=("mkosi.crt",),
help="UEFI SecureBoot certificate in X509 format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="secure_boot_sign_tool",
metavar="TOOL",
section="Validation",
choices=SecureBootSignTool.values(),
help="Tool to use for signing PE binaries for secure boot",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="verity_key",
metavar="PATH",
section="Validation",
paths=("mkosi.key",),
help="Private key for signing verity signature in PEM format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="verity_certificate",
metavar="PATH",
section="Validation",
paths=("mkosi.crt",),
help="Certificate for signing verity signature in X509 format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="sign_expected_pcr",
metavar="FEATURE",
section="Validation",
parse=config_parse_feature,
help="Measure the components of the unified kernel image (UKI) and embed the PCR signature into the UKI",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="passphrase",
metavar="PATH",
section="Validation",
paths=("mkosi.passphrase",),
help="Path to a file containing the passphrase to use when LUKS encryption is selected",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="checksum",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Write SHA256SUMS file",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="sign",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Write and sign SHA256SUMS file",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="key",
section="Validation",
help="GPG key to use for signing",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="incremental",
short="-i",
metavar="BOOL",
parse=config_parse_boolean,
help="Make use of and generate intermediary cache images",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="nspawn_settings",
name="NSpawnSettings",
long="--settings",
paths=("mkosi.nspawn",),
help="Add in .nspawn settings file",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="extra_search_paths",
long="--extra-search-path",
metavar="PATH",
parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
help="List of comma-separated paths to look for programs before looking in PATH",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="ephemeral",
metavar="BOOL",
section="Host",
'image that is removed immediately when the container/VM terminates'),
nargs="?",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="credentials",
long="--credential",
metavar="NAME=VALUE",
parse=config_make_list_parser(delimiter=" "),
help="Pass a systemd credential to systemd-nspawn or qemu",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="kernel_command_line_extra",
metavar="OPTIONS",
section="Host",
parse=config_make_list_parser(delimiter=" "),
help="Append extra entries to the kernel command line when booting the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="acl",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Set ACLs on generated directories to permit the user running mkosi to remove them",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="tools_tree",
metavar="PATH",
section="Host",
paths=("mkosi.tools",),
help="Look up programs to execute inside the given tree",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="tools_tree_distribution",
metavar="DISTRIBUTION",
section="Host",
parse=config_make_enum_parser(Distribution),
help="Set the distribution to use for the default tools tree",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="tools_tree_release",
metavar="RELEASE",
section="Host",
parse=config_parse_string,
help="Set the release to use for the default tools tree",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="tools_tree_mirror",
metavar="MIRROR",
section="Host",
help="Set the mirror to use for the default tools tree",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="tools_tree_packages",
long="--tools-tree-package",
metavar="PACKAGE",
parse=config_make_list_parser(delimiter=","),
help="Add additional packages to the default tools tree",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="runtime_trees",
long="--runtime-tree",
metavar="SOURCE:[TARGET]",
parse=config_make_list_parser(delimiter=",", parse=make_tree_parser(absolute=False)),
help="Additional mounts to add when booting the image",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="runtime_size",
metavar="SIZE",
section="Host",
parse=config_parse_bytes,
help="Grow disk images to the specified size before booting them",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="ssh_key",
metavar="PATH",
section="Host",
paths=("mkosi.key",),
help="Private key for use with mkosi ssh in PEM format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="ssh_certificate",
metavar="PATH",
section="Host",
paths=("mkosi.crt",),
help="Certificate for use with mkosi ssh in X509 format",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_gui",
metavar="BOOL",
nargs="?",
parse=config_parse_boolean,
help="Start QEMU in graphical mode",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_smp",
metavar="SMP",
section="Host",
default="1",
help="Configure guest's SMP settings",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_mem",
metavar="MEM",
section="Host",
default="2G",
help="Configure guest's RAM size",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_kvm",
metavar="FEATURE",
nargs="?",
parse=config_parse_feature,
help="Configure whether to use KVM or not",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_vsock",
metavar="FEATURE",
nargs="?",
parse=config_parse_feature,
help="Configure whether to use qemu with a vsock or not",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_vsock_cid",
name="QemuVsockConnectionId",
long="--qemu-vsock-cid",
default=QemuVsockCID.hash,
help="Specify the VSock connection ID to use",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_swtpm",
metavar="FEATURE",
nargs="?",
parse=config_parse_feature,
help="Configure whether to use qemu with swtpm or not",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_cdrom",
metavar="BOOLEAN",
nargs="?",
parse=config_parse_boolean,
help="Attach the image as a CD-ROM to the virtual machine",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_firmware",
metavar="FIRMWARE",
section="Host",
help="Set qemu firmware to use",
choices=QemuFirmware.values(),
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_firmware_variables",
metavar="PATH",
section="Host",
parse=config_make_path_parser(),
help="Set the path to the qemu firmware variables file to use",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_kernel",
metavar="PATH",
section="Host",
parse=config_make_path_parser(),
help="Specify the kernel to use for qemu direct kernel boot",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_drives",
long="--qemu-drive",
metavar="DRIVE",
parse=config_make_list_parser(delimiter=" ", parse=parse_drive),
help="Specify a qemu drive that mkosi should create and pass to qemu",
),
- MkosiConfigSetting(
+ ConfigSetting(
dest="qemu_args",
metavar="ARGS",
section="Host",
SETTINGS_LOOKUP_BY_SPECIFIER = {s.specifier: s for s in SETTINGS if s.specifier}
MATCHES = (
- MkosiMatch(
+ Match(
name="PathExists",
match=match_path_exists,
),
- MkosiMatch(
+ Match(
name="SystemdVersion",
match=match_systemd_version,
),
- MkosiMatch(
+ Match(
name="HostArchitecture",
match=match_host_architecture,
),
return parser
-def resolve_deps(images: Sequence[MkosiConfig], include: Sequence[str]) -> list[MkosiConfig]:
+def resolve_deps(images: Sequence[Config], include: Sequence[str]) -> list[Config]:
graph = {config.image: config.dependencies for config in images}
if include:
return sorted(images, key=lambda i: order.index(i.image))
-def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig, ...]]:
+def parse_config(argv: Sequence[str] = ()) -> tuple[Args, tuple[Config, ...]]:
# Compare inodes instead of paths so we can't get tricked by bind mounts and such.
parsed_includes: set[tuple[int, int]] = set()
immutable_settings: set[str] = set()
parse_config(p if p.is_file() else Path("."), namespace, defaults)
parsed_includes.add((st.st_dev, st.st_ino))
- class MkosiAction(argparse.Action):
+ class ConfigAction(argparse.Action):
def __call__(
self,
parser: argparse.ArgumentParser,
setattr(namespace, s.dest, s.parse(v, getattr(namespace, self.dest, None)))
def finalize_default(
- setting: MkosiConfigSetting,
+ setting: ConfigSetting,
namespace: argparse.Namespace,
defaults: argparse.Namespace
) -> Optional[Any]:
defaults: argparse.Namespace,
profiles: bool = False,
) -> bool:
- s: Optional[MkosiConfigSetting] # Make mypy happy
+ s: Optional[ConfigSetting] # Make mypy happy
extras = path.is_dir()
if path.is_dir():
argv += ["--", "build"]
namespace = argparse.Namespace()
- argparser = create_argument_parser(MkosiAction)
+ argparser = create_argument_parser(ConfigAction)
argparser.parse_args(argv, namespace)
cli_ns = copy.deepcopy(namespace)
return env
-def load_args(args: argparse.Namespace) -> MkosiArgs:
+def load_args(args: argparse.Namespace) -> Args:
if args.cmdline and not args.verb.supports_cmdline():
die(f"Arguments after verb are not supported for {args.verb}.")
if args.debug_shell:
ARG_DEBUG_SHELL.set(args.debug_shell)
- return MkosiArgs.from_namespace(args)
+ return Args.from_namespace(args)
-def load_config(args: MkosiArgs, config: argparse.Namespace) -> MkosiConfig:
+def load_config(args: Args, config: argparse.Namespace) -> Config:
if config.build_dir:
config.build_dir = config.build_dir / f"{config.distribution}~{config.release}~{config.architecture}"
):
die("This unprivileged build configuration requires at least Linux v5.11")
- return MkosiConfig.from_namespace(config)
+ return Config.from_namespace(config)
def yes_no(b: bool) -> str:
return format_bytes(num_bytes) if num_bytes is not None else "none"
-def summary(config: MkosiConfig) -> str:
+def summary(config: Config) -> str:
def bold(s: Any) -> str:
return f"{Style.bold}{s}{Style.reset}"
return summary
-class MkosiJsonEncoder(json.JSONEncoder):
+class JsonEncoder(json.JSONEncoder):
def default(self, o: Any) -> Any:
if isinstance(o, StrEnum):
return str(o)
return os.fspath(o)
elif isinstance(o, uuid.UUID):
return str(o)
- elif isinstance(o, (MkosiArgs, MkosiConfig)):
+ elif isinstance(o, (Args, Config)):
return o.to_dict()
return json.JSONEncoder.default(self, o)
E = TypeVar("E", bound=StrEnum)
-def json_type_transformer(refcls: Union[type[MkosiArgs], type[MkosiConfig]]) -> Callable[[str, Any], Any]:
+def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[str, Any], Any]:
fields_by_name = {field.name: field for field in dataclasses.fields(refcls)}
def path_transformer(path: str, fieldtype: type[Path]) -> Path: