Boot images
Single-page view · as markdown
mkirf
Peios / Using Peios / Peiosutils / Boot images
mkirf compiles an initramfs source tree into an initramfs image. The source tree is an ordinary directory — on a running Peios system that directory is /boot/initramfs/ — whose contents map 1:1 onto / inside the initramfs. The image is a single deterministic, gzip-compressed newc cpio archive that the bootloader loads into memory alongside the kernel.
mkirf [--watch] [--debounce SECS] [--exclude GLOB]... <src-dir> <out-file>
This page is the command reference. For what the initramfs stage is — prelude, the /boot/initramfs/ layout, and the handoff to the real root — see The initramfs stage; for how hooks declare their order, see Boot hooks. mkirf reads <src-dir> and writes <out-file>; it never writes back into the source tree, so the directory you inspect is always exactly what packages and you have put there.
What a build does #
Given a source tree, one build runs a fixed sequence:
-
Validate the layout.
<src-dir>must be a directory, must carry an executableinit(the initramfs PID 1 — a symlink is fine as long as it resolves within the tree), and must not already contain a hook sequence —hooks.seqorsystem/prelude/hooks.seq.<n>— whichmkirfgenerates. A missing or non-executableinit, or a stray sequence file, stops the build. -
Walk the tree. Every object beneath
<src-dir>becomes a cpio entry — regular files (with their executable bit preserved), directories, symlinks (target stored verbatim), character/block device nodes, and FIFOs. Sockets, and any other unsupported type, are rejected. Entries are sorted inLC_ALL=Cbyte order, which places every directory before its descendants — the order the kernel's unpacker requires. -
Resolve the hook order. Hooks are the regular files directly under
<src-dir>/usr/libexec/prelude/hooks.d/(packaged) or<src-dir>/lcl/libexec/prelude/hooks.d/(operator-placed), with the operator's copy winning when both hold the same file name. The legacy<src-dir>/hooks/is still read, and each hook found there is reported with the directory it should move to.mkirfparses each hook's# /// hookmetadata block, topologically sorts the resulting capability DAG, and bakes the execution order into generated manifests injected into the image at/system/prelude/hooks.seq.<n>. A missinghooks/directory simply means no hooks — the manifests are still written, empty, because prelude treats a missing sequence as a broken image rather than as "nothing to run".The version is in the file name, and
mkirfwrites every version it can express faithfully — currentlyhooks.seq.1(the flat resolved order) andhooks.seq.2(that order plus each hook's declarations).mkirfships inpeiosutilsand prelude ships in its own package, so an image can pair a newer writer with an older reader; naming the versions separately lets one image satisfy both, which a single file carrying an in-band version cannot. A version is dropped only when a future format carries something it can no longer project honestly.They live under
/system— the tier for derived content, alongsidesystem/retc— rather than/usr, which is the vendor tier no package ships these into, or/var/state, which is for mutable state rather than immutable build output. See Validation and errors below. -
Write atomically. The archive is written to a sibling temp file and
renamed into place, so a failed run never leaves a half-written image behind. On successmkirfprints the path, entry count, and byte size to stderr.
Early (pre-decompression) segments #
A reserved <src-dir>/++/ directory, if present, holds early initramfs segments — content the kernel consumes before it decompresses the main archive, namely CPU microcode and ACPI table overrides. Each immediate child of ++/ is one segment whose own contents map onto the cpio root (++/microcode/kernel/x86/microcode/GenuineIntel.bin becomes kernel/x86/microcode/GenuineIntel.bin); the segment directory name is a human label and never appears in the archive. Every ++/ entry must itself be a directory. The segments are merged into one uncompressed cpio prepended ahead of the gzip-compressed main archive. With no ++/ directory the output is exactly a single gzip member. mkirf stays format-agnostic here: it knows "early segments", never "microcode".
The deterministic-build guarantee #
The same source tree always compiles to the same image, byte for byte. This is what makes "did anything actually change?" a meaningful question and underpins later work such as signed boot artifacts. Determinism comes from normalising everything that would otherwise vary between builds:
- Ownership, inode numbers, and timestamps are all emitted as zero.
- Permission bits are normalised. The source tree is authoritative for file type and, for regular files, executability — nothing else. Read/write permission bits are not Peios's access mechanism, so they are flattened to a constant: directories
0755, symlinks0777, FIFOs and device nodes0644, regular files0755if executable and0644otherwise. - The gzip member carries mtime 0 and no embedded filename (the
gzip -nequivalent), at compression level 9. - The early region is byte-stable, with file payloads padded to a 16-byte boundary by widening the preceding entry's name field with NUL bytes.
Validation and errors #
mkirf will not produce an image from a source tree that cannot boot. A misconfiguration is caught when the image is built — on a running system where the message is easy to read — rather than as a mystery failure at the next boot. The checks:
- Layout.
<src-dir>is not a directory; noinit, orinitis not a regular file, or is not executable; a pre-existing hook sequence; a++entry that is not a directory; two++segments defining the same file. (Exit 1.) - Hooks. A malformed
# /// hookmetadata block (unclosed, a non-comment line inside it, a duplicate or unknown key, a badly-formed array), a dependency cycle, arequiresthat nothing supplies, or a capability that is both provided and contributed to (a capability is either a set of alternatives or a set of contributors, never both). Anafternaming a capability nothing supplies is not an error — that is what distinguishes it fromrequires. (Exit 1.) A hook with no metadata block at all is not an error — it is the escape hatch: it is scheduled last, in name order, andmkirfprints a warning so a forgotten block is visible. - I/O. Any filesystem read, write, or compression failure. (Exit 1.)
- Usage. A semantic invocation error clap cannot express — currently,
--watchwith<out-file>inside<src-dir>(see below). (Exit 2.)
Watch mode #
With --watch, mkirf stays resident: it builds once up front (so the watch never begins from a stale archive), then watches <src-dir> recursively and rebuilds after every change, debounced. This is what lets /boot/initramfs/ behave like an ordinary part of the filesystem — edit a hook and the initramfs is current again — rather than a build artifact an administrator has to remember to regenerate.
Watch mode is a foreground loop that runs until killed; supervising and restarting it is a service manager's job, not mkirf's. A rebuild that fails (say, a hook edited into a dependency cycle) is logged but does not stop the watch, so fixing the offending file recovers on the next change.
<out-file> must not live inside <src-dir>: each rebuild would write the image back into the watched tree and retrigger the watch endlessly. mkirf refuses this invocation up front (exit 2). The --debounce window (default 5 seconds) is the settle time — mkirf waits for the tree to be quiet for that long before rebuilding, so a burst of edits produces one rebuild rather than many.
Options #
| Option | Effect |
|---|---|
--watch | Stay resident and rebuild on every change to <src-dir>, after an initial build. Runs until killed. |
--debounce SECS | With --watch, the settle time before a rebuild. Default 5. A non-numeric value is a usage error. |
--exclude GLOB | Exclude paths (relative to <src-dir>) matching GLOB from the image. Repeatable. * and ? stay within a path segment; ** crosses separators. A matched directory is pruned with its whole subtree. A malformed glob is a usage error. |
The two positionals are both required:
| Positional | Meaning |
|---|---|
<src-dir> | The source tree; its contents map onto / in the initramfs. |
<out-file> | The output cpio.gz archive. |
Exit status #
| Code | Meaning |
|---|---|
0 | The image was built (or --help/--version was printed). |
1 | An operational failure — an invalid layout, an unresolvable hook set, or an I/O/compression error. |
2 | A usage error — a bad option, a missing positional, or --watch with <out-file> inside <src-dir>. |
See also #
- The initramfs stage — what
mkirf's output is for. - Boot hooks — the
# /// hookmetadata block and how order is resolved. - mkuki — wraps
mkirf's image, together with a kernel and command line, into a bootable UEFI unified kernel image.
mkuki
Peios / Using Peios / Peiosutils / Boot images
mkuki builds a UEFI unified kernel image (UKI): it takes a PE/COFF EFI stub and appends the kernel, the initramfs, and the kernel command line to it as named PE sections, producing a single EFI binary that UEFI firmware boots directly. Where mkirf produces the initramfs image, mkuki is the step that wraps that image — together with a kernel and a command line — into one bootable artifact. The two are the two halves of Peios' Dynamic Boot system.
mkuki --kernel PATH --initramfs PATH (--cmdline TEXT | --cmdline-file PATH) --out PATH
[--stub PATH] [--watch] [--debounce SECS]
mkuki --stub-info
The UKI section layout #
A UKI is an ordinary PE/COFF EFI executable — the stub — with three extra sections appended, which the stub reads at boot to find its payloads:
| Section | Content | Source |
|---|---|---|
.cmdline | The kernel command line, NUL-terminated. | --cmdline or --cmdline-file |
.linux | The kernel image. | --kernel |
.initrd | The initramfs cpio image. | --initramfs |
mkuki appends them in exactly that order. Each new section is marked as initialised, read-only data. The tool works at the PE level directly: it parses the stub's DOS/PE headers and section table, computes correctly-aligned virtual addresses and file offsets for the new sections, appends their data, updates the section count and the SizeOfImage/SizeOfHeaders fields, and — if the stub's header has no spare room for three more section entries — grows the header region and relocates the existing sections' file pointers to make space. A stub that is not a valid MZ/PE image, or whose optional header is malformed, is rejected.
The command line #
The command line is taken either literally from --cmdline or read from the file named by --cmdline-file (exactly one is required). Either way, mkuki trims trailing newlines and carriage returns and appends a single NUL terminator. A command line containing an embedded NUL byte is rejected.
The stub #
--stub names the EFI stub to append to. With no --stub, mkuki uses a stub bundled into the binary (the systemd EFI stub). mkuki --stub-info prints that bundled stub's provenance and exits, so you can see exactly what the default is without building anything.
Output and atomicity #
mkuki creates any missing parent directories of --out, writes the assembled image to a sibling temp file, and renames it into place, so an interrupted or failed build never leaves a half-written UKI behind. On success it prints the output path and byte size to stderr. Each of the three payloads must be non-empty; an empty .cmdline, .linux, or .initrd fails the build.
Watch mode #
With --watch, mkuki stays resident: it builds once up front, then rebuilds the UKI whenever an input changes — so the boot image tracks a new kernel, a freshly-repacked initramfs (mkirf's half of Dynamic Boot), or an edited command-line file with no manual step. Like mkirf's watch mode, it is a foreground loop that runs until killed; supervising it is a service manager's job, and a rebuild that fails (for example, a kernel caught mid-copy) is logged rather than fatal, so fixing the input recovers on the next change.
The watched inputs are --kernel, --initramfs, and — only if it is a --cmdline-file, since a literal --cmdline is static — the command-line file. mkuki watches each input's parent directory (non-recursively), not the file itself: this survives the atomic temp-and-rename writes that mkirf and mkuki both perform (which appear as a directory event a stale single-file watch would miss) and catches a versioned kernel being swapped in /boot. Because the watch is non-recursive, a write to an --out path nested deeper under a watched directory does not retrigger it — but an --out sitting directly in a watched input directory would, so mkuki refuses that invocation. The --debounce window (default 5 seconds) is the settle time before a rebuild.
Options #
| Option | Effect |
|---|---|
--kernel PATH | The kernel image; becomes the .linux section. Required (except with --stub-info). |
--initramfs PATH | The initramfs cpio; becomes the .initrd section. Required (except with --stub-info). |
--cmdline TEXT | The kernel command line, given literally; becomes the .cmdline section. Mutually exclusive with --cmdline-file. |
--cmdline-file PATH | Read the kernel command line from PATH instead. Mutually exclusive with --cmdline. |
--out PATH | The output UKI path. Required (except with --stub-info). |
--stub PATH | The PE/COFF EFI stub to append sections to. Defaults to the bundled systemd stub. |
--stub-info | Print the bundled stub's provenance and exit. |
--watch | Stay resident and rebuild the UKI whenever --kernel, --initramfs, or --cmdline-file changes. Runs until killed. |
--debounce SECS | With --watch, the settle time before a rebuild. Default 5. |
Exactly one of --cmdline or --cmdline-file must be given: supplying both, or neither, is a usage error.
Exit status #
| Code | Meaning |
|---|---|
0 | The UKI was built (or --stub-info, --help, or --version was printed). |
1 | A build or watch failure — a missing or unreadable input, an invalid stub, an empty payload, or an I/O error. |
2 | A usage error — a bad or missing option, or an invalid --cmdline/--cmdline-file combination. |
See also #
- mkirf — builds the
.initrdpayloadmkukiwraps. - The initramfs stage — what the bundled initramfs does once firmware boots the UKI.