Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

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:

  1. Validate the layout. <src-dir> must be a directory, must carry an executable init (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.seq or system/prelude/hooks.seq.<n> — which mkirf generates. A missing or non-executable init, or a stray sequence file, stops the build.

  2. 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 in LC_ALL=C byte order, which places every directory before its descendants — the order the kernel's unpacker requires.

  3. 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. mkirf parses each hook's # /// hook metadata 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 missing hooks/ 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 mkirf writes every version it can express faithfully — currently hooks.seq.1 (the flat resolved order) and hooks.seq.2 (that order plus each hook's declarations). mkirf ships in peiosutils and 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, alongside system/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.

  4. 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 success mkirf prints 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, symlinks 0777, FIFOs and device nodes 0644, regular files 0755 if executable and 0644 otherwise.
  • The gzip member carries mtime 0 and no embedded filename (the gzip -n equivalent), 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; no init, or init is 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 # /// hook metadata block (unclosed, a non-comment line inside it, a duplicate or unknown key, a badly-formed array), a dependency cycle, a requires that 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). An after naming a capability nothing supplies is not an error — that is what distinguishes it from requires. (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, and mkirf prints 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, --watch with <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 #

OptionEffect
--watchStay resident and rebuild on every change to <src-dir>, after an initial build. Runs until killed.
--debounce SECSWith --watch, the settle time before a rebuild. Default 5. A non-numeric value is a usage error.
--exclude GLOBExclude 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:

PositionalMeaning
<src-dir>The source tree; its contents map onto / in the initramfs.
<out-file>The output cpio.gz archive.

Exit status #

CodeMeaning
0The image was built (or --help/--version was printed).
1An operational failure — an invalid layout, an unresolvable hook set, or an I/O/compression error.
2A 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 # /// hook metadata 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:

SectionContentSource
.cmdlineThe kernel command line, NUL-terminated.--cmdline or --cmdline-file
.linuxThe kernel image.--kernel
.initrdThe 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.

Note

mkuki does not sign the image. Producing the unified binary and signing it for Secure Boot are separate steps; mkuki has no signing options. Because a UKI bundles the kernel, initramfs, and command line into one PE image, that image is what a later Secure Boot milestone signs and the firmware verifies as a whole — but the signing itself is out of mkuki's scope.

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 #

OptionEffect
--kernel PATHThe kernel image; becomes the .linux section. Required (except with --stub-info).
--initramfs PATHThe initramfs cpio; becomes the .initrd section. Required (except with --stub-info).
--cmdline TEXTThe kernel command line, given literally; becomes the .cmdline section. Mutually exclusive with --cmdline-file.
--cmdline-file PATHRead the kernel command line from PATH instead. Mutually exclusive with --cmdline.
--out PATHThe output UKI path. Required (except with --stub-info).
--stub PATHThe PE/COFF EFI stub to append sections to. Defaults to the bundled systemd stub.
--stub-infoPrint the bundled stub's provenance and exit.
--watchStay resident and rebuild the UKI whenever --kernel, --initramfs, or --cmdline-file changes. Runs until killed.
--debounce SECSWith --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 #

CodeMeaning
0The UKI was built (or --stub-info, --help, or --version was printed).
1A build or watch failure — a missing or unreadable input, an invalid stub, an empty payload, or an I/O error.
2A usage error — a bad or missing option, or an invalid --cmdline/--cmdline-file combination.

See also #

  • mkirf — builds the .initrd payload mkuki wraps.
  • The initramfs stage — what the bundled initramfs does once firmware boots the UKI.

Peios Learn — documentation for the Peios project.

Built with Trail.