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

Reference

Single-page view · as markdown

The build spec

Peios / Peiso / Reference

peiso.toml is the declarative TOML spec that drives peiso. A single file describes the whole bootable image: the peipkg-compose manifest to build the package root from, how the initramfs is packed, and the optional squashfs, UKI, ISO, registry-seed and feature stages that the build pipeline layers on top. This page documents schema version 1 — the only schema peiso accepts — field by field.

The spec is passed to peiso positionally and defaults to peiso.toml in the current directory:

sudo peiso build [spec.toml]

Three properties hold across the whole file:

  • TOML, parsed strictly. An unknown key anywhere in the spec is a hard error, not a warning — peiso rejects the first undecoded key by name. A mistyped field fails the build rather than being silently ignored.
  • schema is checked first. The spec must declare schema = 1; any other value (or a missing schema, which reads as 0) fails with unsupported schema.
  • Relative paths resolve against the spec file's own directory — never the current working directory. So a build behaves the same wherever you invoke it from. There are two kinds of path handling, and each field uses one of them:
    • Spec-relative paths (root.manifest, root.out, initramfs.manifest, squashfs.out, uki.cmdline_file, iso.source, iso.out, inject.src) are made absolute against the spec's directory. An already-absolute value is kept as given.
    • Root-relative paths (initramfs.dir, initramfs.cpio, uki.out, iso.efi_boot, inject.dest) are cleaned and kept relative (any leading / is stripped). These double as chroot-absolute paths inside the composed root, so they are validated to not escape with .. (see Validation).

A complete example #

A full spec that composes a root, packs the initramfs, squashes the rootfs, builds a UKI, emits a bootable ISO, and enables a feature on first boot:

schema      = 1
source_date = "2026-06-22T00:00:00Z"   # pins reproducible build timestamps

# Let packages declaring `special_system_package` compose outside the layout
# rules. Needed by any image carrying the base-filesystem package, whose whole
# job is to lay down the mountpoint tree those rules protect.
bypass_path_restrictions = true

# The whole image. manifest.toml is multi-root — it composes the main system
# root into `out` and the nested initramfs root (at out/boot/initramfs) in one
# pass, so [initramfs] below needs no manifest of its own.
[root]
manifest = "manifest.toml"
out      = "root"

# How mkirf packs the already-composed initramfs root into a cpio. `dir` and
# `cpio` are root-relative: inside the chroot they are mkirf's /<dir> source and
# /<cpio> output. The peipkg database isn't needed at early boot, so it's excluded.
[initramfs]
dir     = "boot/initramfs"
cpio    = "system/boot/initramfs.cpio.gz"
exclude = ["var/state/peipkg", "lcl/conf/peipkg"]

# Read-only rootfs image, squashed from the WHOLE composed root. `out` is written
# OUTSIDE root/ so the image can never contain itself.
[squashfs]
out         = "rootfs.squashfs"
compression = "zstd"

# Unified Kernel Image: kernel + initramfs cpio + cmdline in one EFI binary.
# cmdline_file is read from the composed root, so build-time and runtime agree.
[uki]
cmdline_file = "root/usr/share/live-boot/cmdline"
out          = "boot/efi/EFI/BOOT/BOOTX64.EFI"

# Bootable UEFI ISO built from the ESP tree at `source`, with the UKI marked as
# the EFI boot image at `efi_boot` (relative to source).
[iso]
source   = "root/boot/efi"
efi_boot = "EFI/BOOT/BOOTX64.EFI"
out      = "peios.iso"
label    = "PEIOS"

# Features this image enables on first boot (feat add <name>).
[features]
enable = ["dynamic-boot"]

[squashfs], [uki], [iso], [registry] and [features] are all optional — a minimal spec is just schema, [root] and [initramfs], which composes the root and packs the initramfs.

Top-level fields #

KeyTypeRequiredMeaning
schemaintyesSpec schema version. Must be 1; any other value fails.
source_datestringnoA fixed build timestamp for reproducible artifacts, passed through to mksquashfs. Empty (or omitted) means epoch 0. The format is not validated by peiso — the example uses an RFC 3339 timestamp, mirroring the compose manifest's source_date.
bypass_path_restrictionsbooleannoPermits packages that declare special_system_package to compose payloads outside the package layout rules — the base-filesystem package that mints the mountpoint tree being the case that needs it. Passed through to peipkg-compose as --dangerously-bypass-path-restrictions. Defaults to false. It exempts nothing that has not declared itself special: the package proposes, and this line is how the image decides.

[root] — the package root (required) #

The main system root, composed by shelling out to peipkg-compose build. Both keys are required.

KeyTypeRequiredMeaning
manifeststring (spec-relative)yesThe peipkg-compose manifest to build the root from. A multi-root manifest composes both the main system root and the nested initramfs root in one pass.
outstring (spec-relative)yesThe directory to compose into and chroot into. peiso owns this as a rebuildable artifact and clears it before each build (a single clean that also clears the nested initramfs root).
injectarray of tablesnoA packaging bypass. Files copied straight into the composed main root after compose. See below.

[[root.inject]] #

Each entry copies one file into the composed main root, after compose and before every stage that reads the root — so an injected file is visible to the registry-seed staging, the squashfs, and the UKI. src and dest are both required.

KeyTypeRequiredMeaning
srcstring (spec-relative)yesThe source file to copy.
deststring (root-relative)yesThe destination inside the main root. Must not escape with ...
modestring (octal)noPermission bits as an octal string (e.g. "0644"). Empty (or omitted) preserves the source file's mode.

An injected file has no package owner, so peipkg will not upgrade, verify or remove it. That cost is the reason to prefer a package for anything images should get properly. What it buys is a home for the composing artifact's own contributions — an image-specific registry seed, a boot script — which is the same authority peiso already exercises when it stages the seed queue and the autorun script into /lcl/policy.

The common pairing is a seed: inject a .reg master to usr/share/regim/<name> and name it in [registry] autoapply. The inject runs first, so by the time the queue is built the seed is indistinguishable from one a package shipped.

Caution

An injected file is only as removable as the spec that placed it. That is an advantage for a bring-up affordance — deleting the [[root.inject]] entry deletes the file, with nothing to uninstall — and a hazard for anything else, because no package manifest records that it was ever there.

[initramfs] — packing the initramfs (required) #

Describes how peiso packs the initramfs root into a cpio archive using the composed root's own mkirf applet, run inside the chroot. dir and cpio are required; the rest are optional.

KeyTypeRequiredMeaning
dirstring (root-relative)yesThe initramfs root, relative to root.out (e.g. boot/initramfs). Inside the chroot this is mkirf's /<dir> source. Must not escape the root with ...
cpiostring (root-relative)yesThe cpio output path, relative to root.out (e.g. system/boot/initramfs.cpio.gz). Inside the chroot this is mkirf's /<cpio> output. Must not escape the root with ...
excludearray of stringsnoGlobs passed to mkirf --exclude, relative to the initramfs root — paths omitted from the cpio (e.g. the peipkg database, not needed at early boot).
manifeststring (spec-relative)noLegacy — normally unused. The older separate-compose form: a manifest that composes the initramfs root on its own. It is normally absent because a multi-root root.manifest already produces the nested initramfs root, and this section then only describes how mkirf packs it. When present, peiso composes it into root.out/<dir> as a second, separate compose.
injectarray of tablesnoTemporary — a packaging bypass. Files copied straight into the initramfs root before mkirf runs, a stopgap for landing files (e.g. the live-boot hook) without packaging them. See below.

[[initramfs.inject]] (temporary) #

Each entry copies one file into the initramfs root before it is packed. This is a transitional mechanism — the intended path is to ship such files in a package so a cross-root dependency pulls them into the initramfs root during compose. src and dest are both required.

KeyTypeRequiredMeaning
srcstring (spec-relative)yesThe source file to copy.
deststring (root-relative)yesThe destination inside the initramfs root. Must not escape with ...
modestring (octal)noPermission bits as an octal string (e.g. "0755"). Empty (or omitted) preserves the source file's mode.

[squashfs] — the read-only rootfs image (optional) #

When present, peiso squashes the whole composed root into a read-only image (the live-boot lower layer). Presence of the table is what turns the stage on; out is then required.

KeyTypeRequiredMeaning
outstring (spec-relative)yes (when [squashfs] present)The image output path. Conventionally written outside root.out so the image can never contain itself.
excludearray of stringsnoSource-relative paths to omit from the image.
compressionstringnoThe mksquashfs -comp value (e.g. zstd). Empty uses the mksquashfs default.

[uki] — the Unified Kernel Image (optional) #

When present, peiso bundles a UKI — kernel + initramfs cpio + kernel cmdline in one EFI binary — using the composed root's own mkuki applet in the chroot. You must supply the cmdline exactly one way: cmdline or cmdline_file, never both and never neither.

KeyTypeRequiredMeaning
cmdlinestringconditionalThe literal kernel command line. Mutually exclusive with cmdline_file.
cmdline_filestring (spec-relative)conditionalA file holding the cmdline, read at build time. Pointing at a file inside the composed root keeps build-time and runtime single-sourced. Mutually exclusive with cmdline.
outstring (root-relative)yes (when [uki] present)The UKI output, relative to root.out — the ESP EFI path (e.g. boot/efi/EFI/BOOT/BOOTX64.EFI). Must not escape the root with ...

[iso] — the bootable ISO (optional) #

When present, peiso emits a UEFI-bootable ISO9660 image (via xorriso) from an ESP tree. source, efi_boot and out are required; label defaults.

KeyTypeRequiredMeaning
sourcestring (spec-relative)yes (when [iso] present)The ESP directory tree packed into the ISO (holding the UKI).
efi_bootstring (root-relative to source)yes (when [iso] present)The EFI boot binary (the UKI) inside the ESP tree, relative to source (e.g. EFI/BOOT/BOOTX64.EFI) — the file firmware boots from the ESP partition. peiso checks it exists before authoring the ISO. Must not escape source with ...
outstring (spec-relative)yes (when [iso] present)The .iso output path.
labelstringnoThe ISO9660 volume label. Defaults to PEIOS.

[[iso.include]] #

Places a file or a whole directory tree into the ISO9660 data area — the part of the medium that is neither the ESP nor the boot chain. Repeatable.

KeyTypeRequiredMeaning
srcstring (spec-relative)yesSource file or directory on the build host.
deststring (ISO-relative)yesWhere it lands on the medium. Must not escape the ISO root with .., and must not collide with the rootfs squashfs peiso places there itself.

peiso already puts the rootfs squashfs in the data area; this makes the same space available to the image.

[[iso.include]]
src  = "repo"
dest = "repo"

Data-area content is not in the squashfs, which is the point. It costs nothing in the root filesystem, is not decompressed at boot, and — because the squashfs is byte-identical to an installed root — is not inherited by machines installed from the medium. An installation medium can carry a package repository without imposing one on every system installed from it.

Files are hard-linked into the staging tree where the filesystems allow it, so including a large tree that already exists in the build directory costs no extra space or time.

Important

A booted system can only read the data area if something mounts the medium. On a live image that is live-boot's root-mount hook, which carries the medium to /media/peios after the pivot — prelude moves only /proc, /sys and /dev into the new root and then chroots, so a mount nobody relocates is unreachable afterwards.

Note

Symlinks are refused rather than followed. ISO9660 without Rock Ridge cannot represent one, and silently following it would place a copy where the image author wrote a reference.

[registry] — registry seeds auto-applied on first boot (optional) #

Selects which vendor registry-seed masters (shipped by packages into /usr/share/regim/) this image auto-applies on first boot. peiso stages each into the composed root's autoapply queue; peinit drains it at first boot. Selection lives here, in the image, not in the packages.

KeyTypeRequiredMeaning
autoapplyarray of stringsnoBare seed filenames (no path separators), relative to /usr/share/regim/ in the composed root. Each entry is validated to be a bare filename.

[features] — features enabled on first boot (optional) #

Selects which curated features (shipped by packages into /usr/libexec/features/ and exposed at runtime through /libexec/features/) this image enables on first boot. peiso writes a self-removing autorun script of feat add <name> lines; peinit runs it before Phase 2, so the services a feature creates start the same boot.

KeyTypeRequiredMeaning
enablearray of stringsnoFeature names to feat add on first boot. Each must match feat's name grammar (see Validation); the name is embedded verbatim in a generated shell line.

Validation #

peiso loads, decodes and validates the spec before running anything. The rules, exactly as enforced:

  • Unknown keys are rejected. Any key the schema does not define — at any level — fails the build, naming the first offending key.
  • Schema gate. schema must equal 1, or the build fails with unsupported schema.
  • Required fields. root.manifest, root.out, initramfs.dir and initramfs.cpio must all be present and non-empty. Within an optional table that is present, its own required keys apply: squashfs.out; uki.out; iso.source, iso.efi_boot and iso.out; and both src and dest on every inject entry, in [[root.inject]] and [[initramfs.inject]] alike.
  • No-escape path checks. The root-relative paths initramfs.dir, initramfs.cpio, uki.out, iso.efi_boot and every inject.dest (both roots) may not be .. or begin with ../ — they become chroot-absolute paths and must stay inside their tree. (Spec-relative paths are not subject to this check; they resolve to absolute paths.)
  • UKI cmdline mutual-exclusion. When [uki] is present, exactly one of cmdline or cmdline_file must be set. Supplying both, or neither, is an error.
  • Registry seeds must be bare filenames. Each registry.autoapply entry must be non-empty and contain no path separator (it must equal its own basename); a path component is a spec error.
  • Feature-name grammar. Each features.enable entry must be non-empty, must not be . or .., and must consist only of ASCII alphanumerics plus -, _ and .. This mirrors feat's own name grammar and doubles as a guard against shell metacharacters, since the name is embedded in a generated feat add line.
  • Inject mode. inject.mode, when set, must parse as an octal integer; a malformed value fails the build. Applies to both inject tables.

A spec that passes all of these checks is what the build pipeline runs.

See also #

  • Building a Peios image — what peiso is and where it sits above compose.
  • The build pipeline — the stage each table drives, in run order.
  • Running a build — the command, exit codes, and required tools.

Peios Learn — documentation for the Peios project.

Built with Trail.