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

Building images

Single-page view · as markdown

Building a Peios image

Peios / Peiso / Building images

peiso is the Peios image builder. Given a spec of a few lines — which edition, where its packages come from, a couple of flags — it produces a bootable installation medium: a UEFI ISO carrying a live Peios and everything needed to install it onto a disk.

It is a user-facing tool. Building a slightly customised Peios image is meant to be an ordinary thing to do, not a project: you should never have to list packages, name registry seeds, or run anything as root.

The release is a package #

The thing peiso builds from is not a list. A Peios release is an edition package — dev.peios.peios-experimental today, discoverable to image builds through its peios-experimental compatibility capability — whose version is the OS version, whose dependency closure is the base system (kernel, initramfs, services, firmware, the installer), and which ships the system's identity (/usr/lib/os-release) and a small data file, release.toml, stating what the release asks of a system beyond its packages. Editions explains that package in full.

peiso's job is therefore small: resolve that one package, compose its closure, and add what makes the result a medium rather than an installed system. An installed system moves to the next release by upgrading the same package, through upgrade-peios.

What peiso adds to an edition #

An edition describes an installed Peios. A live medium needs three more things, and they are peiso's, not the edition's:

  • The boot flavour. first_boot = "live" (the default) adds live-boot, whose initramfs hook finds the medium and mounts the live root from it. An installed system carries disk-boot instead; the installer swaps one for the other on the target. Neither belongs in the edition, because which one a system carries is a property of the medium it boots from.
  • The medium repository. The installer takes the target's boot packages from a small signed repository carried on the ISO. peiso publishes it per build, with a throwaway signing key.
  • Devtools. dwe = true adds the Developer Workstation Environment: dwed running as SYSTEM, reachable over vsock. It makes the image ownable, which is why it is a property of a development medium and can never be installed as a mere package.

Everything else in the image comes from the edition.

Where the stages happen #

flowchart LR
    S["spec<br/>(edition, source)"] --> R["root/<br/>(peipkg compose)"]
    R --> M["repo/<br/>(medium repository)"]
    R --> Q["seeds<br/>(release.toml → autoapply.d)"]
    Q --> I["initramfs cpio<br/>(mkirf)"]
    I --> F["rootfs.squashfs"]
    I --> U["UKI<br/>(mkuki)"]
    F --> O["ISO"]
    U --> O
    M --> O

The root is composed by peipkg's compose library — the same code as peipkg-compose, driven in-process. The initramfs and the UKI are packed by mkirf and mkuki, the applets shipped inside the root, so the tools that build the boot artifacts are the ones the running system carries. The build pipeline walks every stage.

No privilege #

Nothing in a peiso build runs as root. The old builder chrooted into the composed root to run the shipped applets; peiso runs them on the host under the root's own dynamic loader instead. Where a package would have a security.* attribute set on a file — the kernel's firmware signature, security.peios.sig — compose hands the signature to peiso and peiso writes it into the squashfs directly, where placing an attribute needs no capability. The composed root/ on the build host is only an intermediate; the image is the artifact, and the image is complete.

That property is what lets a build run on any machine, in CI, or by any user, and it is why sudo appears nowhere in this anthology.

What it is not #

peiso does not build packages (that is pekit), does not resolve or fetch them itself (compose does), and does not decide what a Peios contains (the edition does). A spec can add packages, files, first-boot scripts, features and medium contents on top of the edition, adjust the packers, and choose the boot flavour and devtools — Customising an image — and every addition is recorded beside the build. What the release is remains the edition package's to say.

Quick start

Peios / Peiso / Building images

This page builds the Peios Experimental medium and boots it. It assumes a checkout of the Peios tree with a populated signed package repository (pkgs/_repo2_), which supplies the edition and its closure.

What you need on the build host #

  • peiso, built from peiso/ in the tree (go build -o peiso .), or on your PATH.
  • squashfs-tools, xorriso, dosfstools and mtools — the host-side packers. Everything Peios-specific (the initramfs and UKI tools) is taken from the root being built.
  • For booting: QEMU with KVM and the OVMF firmware.

No root. If any step asks for it, that is a bug.

The spec #

dist/release/experimental.toml:

[[packages.repository]]
url = "file://../../pkgs/_repo2_"
trust_anchors = ["63977c7be45624999b88bac5aa55ab5280656ee076617a285c87602a0d980602"]
keys = ["../../pkgs/dev-signing.pub"]

[baseline]
edition = "Experimental"
package = "dev.peios.peios-experimental"
source_date = "2026-06-22T00:00:00Z"

The explicit package selects dev.peios.peios-experimental; without a version, the newest available version wins. The trust anchor authenticates repository metadata, and keys supplies the package-signing public key. The spec lists every key.

Build #

cd dist/release
make iso

peiso reports each stage:

resolving peios-experimental
resolved dev.peios.peios-experimental 2026.8-1 (… packages)
composing dist/peios-experimental-2026.8/root
resolving medium packages
publishing dist/peios-experimental-2026.8/repo
medium repo: 2 packages, trust anchor 6e50…
staged seed authd-policy
…
seeds: 15 staged
packing initramfs -> …/root/system/boot/initramfs.cpio.zst
squashing root -> …/rootfs.squashfs
squashed with 4414 signature attribute(s)
building UKI -> …/root/boot/efi/EFI/BOOT/BOOTX64.EFI
building ISO -> dist/peios-experimental-2026.8/peios-experimental-2026.8.iso
dist/peios-experimental-2026.8/peios-experimental-2026.8.iso

The last line is the ISO. Everything the build made is beside it, in dist/peios-experimental-2026.8/ — see Running a build for the layout.

Boot #

make boot

runs QEMU with UEFI firmware, the ISO attached as a virtio disk, and the serial console on your terminal. You will see the kernel, then prelude (the initramfs PID 1) running its hooks — live-boot finds the medium and mounts the live root — then peinit bringing up the services the release's seeds define, and finally a login prompt. The image autologs in a development account.

Open the web desktop #

make iso includes GXWI, the Fenestra compositor, the fenesh desktop shell, Gexora file explorer and the Hello demonstration app. Their packages supply binaries, application declarations, icons and registry seeds; no development share or checkout is needed in the guest.

After make boot reaches the console, open https://127.0.0.1:7780/ on the host. The live image has the peios development account with no password. Fenestra and its apps run as the logged-on principal using a RemoteInteractive session. Installed systems use their provisioned account instead.

GXWI serves HTTPS with a certificate the machine makes for itself, so the browser warns before the first sign-in. Before going past the warning, check the certificate's SHA-256 fingerprint against the machine's own: sign in at the VM's console (the live image's peios account has no password) and run cat /var/state/gxwi/certificate.sha256. A live medium makes a new key every boot, so the warning comes back each time. installerd attempts to carry the key into the installed system, but a skipped or failed transfer does not fail the installation. Verify any changed fingerprint through the installed machine's trusted console before entering credentials. See certificate carry-over. Plain http:// is answered with a redirect to https:// and nothing else.

The Experimental edition enables TCP 7780 on all guest interfaces and admits it through the guest packet policy. The Makefile's host port forwards bind to localhost; that protects the QEMU host listener, not a machine booted directly from the ISO or a VM configured with bridged networking.

The edition opts into gxwid-service, gxwi-config, gxwi-network, fenestra-config and fenesh-config. Machine\Software\GXWI selects the listener and compositor; Machine\Software\Fenestra selects the shell. Package installation alone does not activate these policy seeds. The GXWI service retains SeImpersonatePrivilege so it can hand the authenticated principal's token to peinit for the session job.

SSH remains available on host port 2222 after provisioning a permitted key or password credential. GXWI does not currently offer SSH-key authentication.

The same Makefile has boot-dwe, boot-install and boot-installed; Running a build covers them.

Change something #

Two things are yours to change in the spec without touching anything else:

[devtools]
dwe = true            # a development medium; builds to …-2026.8-dwe/

[registry]
remove = ["lpsd-first-account"]   # drop a seed the release would apply
add    = ["my-service"]           # add one a package in the closure ships

[[package]]
name = "org.gnu.bash"             # carry a package the edition does not

To change what the release is, change the edition package (pkgs/dev.peios.peios-experimental/) and republish it; the spec does not change. Editions explains why that line is drawn where it is.

The build pipeline

Peios / Peiso / Building images

peiso iso runs seven stages. peiso root runs only the first. Each stage consumes what earlier ones produced, and everything lands in one build directory, dist/peios-<edition>-<version>[-dwe]/ — The build directory lists its contents.

1. Resolve and compose the root #

peiso writes a compose manifest naming the edition package (at the spec's version, or any), the medium's extras (live-boot for first_boot = "live", none at all for "none"; peios-dwe when dwe = true), the spec's package sources, and one named root, initramfs at boot/initramfs.

A file:// source that holds a repository descriptor is declared as a repository; one that does not (the local pool, pkgs/_pkgsOut_) joins as local packages. Then peiso resolves — a lock — and reads the edition's resolved version from it, because the build directory is named after that version and the spec may not have said it. Only then does it compose, from the lock, into root/. The manifest and lock are kept beside the root: they record exactly what was resolved.

Compose writes each repository it drew from into the root as lcl/conf/peipkg/source<N>.repo, so that the system goes on drawing from it. peiso then takes the file:// ones out again. Such a source is a path on the machine that ran the build, and on the image it names nothing. It could not be left as a harmless leftover either: peipkg refuses to resolve while any configured repository has no usable index, so a repository nothing can refresh would stop every install, upgrade and removal on the live system and on what is installed from it — the installer's own boot package swap among them. An http(s):// source stays, and is the image's to add with peipkg repo add source<N> like any repository it carries. The build says which sources it left out.

Two things about the composed tree:

  • It is complete but attribute-less. Where a package carries a signature sidecar for a non-ELF file (device firmware: <file>.peios.sig), compose would normally set the target's security.peios.sig attribute — which needs CAP_SYS_ADMIN. peiso asks compose to record each one instead (sidecars.jsonl), and writes them into the squashfs at stage 5. The root/ on the build host therefore carries no security attributes; the image does.
  • The initramfs is a second root inside it. Packages the initramfs runs — prelude, its hooks, the module subset — are placed there by the edition's cross-root dependency edges, not by anything peiso does.

Compose is given the composer's half of the Special System Package exemption: dev.peios.fsbase lays down the mountpoint tree the layout rules otherwise protect, and a root with no /proc is not a root.

2. Publish the medium repository #

The installer swaps the live boot packages for the disk ones on the target, and it takes dev.peios.disk-boot and dev.peios.disk-boot-irf from a repository carried on the medium — a live image cannot carry them installed, because live-boot-irf and dev.peios.disk-boot-irf conflict in the initramfs.

peiso resolves those two packages against the same source scan the root's lock came from — not merely the same source declarations, the same gathered universe, so a package republished mid-build cannot give the root and the medium different revisions. It publishes them into repo/ with a key generated for this build, and writes root/lcl/conf/peipkg/peios-medium.repo naming that key as the trust anchor and file:///media/peios/repo as the base URL — where live-boot mounts the medium.

The key is a throwaway and its private half is never written anywhere. A key shipped beside the repository it signs proves nothing about it — tampering with the ISO tampers with both — so its only job is to satisfy the "required" signature policy that peipkg rightly insists on. The repository's descriptor also lists the keys the packages were signed with (keys in the spec): a repository accepts a package only if its signer is in the descriptor.

3. Stage the registry seeds #

Packages ship registry seed masters under /usr/share/regim/, and installing a package applies none of them: which seeds a system runs is policy, not payload. The edition states that policy in /usr/share/peios/release.toml. peiso reads it from the composed root, applies the spec's [registry] add and remove, and copies each named seed into one of three queues under root/lcl/policy/:

QueueFromApplied by
autoapply.d/autoapplyevery system
autoapply.live.d/live_autoapply, plus dwed-service for a DWE mediumthe boot medium only
autoapply.install.d/install_autoapplythe installed machine only

Three, not one, because an installer copies the shipped image verbatim: what is staged for the medium arrives on the disk unless something takes it off again. A development account whose password ships in the image belongs on one and not the other; so does first-boot setup, the other way round.

peiso also places the autorun script that drains the first two — reg apply --dir /lcl/policy/autoapply.d --once-delete, then the same for autoapply.live.d — which peinit runs at boot before it plans its services, so the services the seeds define start on the very boot that creates them. Base before live, so a live seed can override a value the base one set.

autoapply.install.d/ is deliberately absent from the drain: applying it on the medium would start a first-boot flow on a machine nobody has installed yet. The installer promotes it into the target's autoapply.d/ and deletes autoapply.live.d/.

A seed the spec names that no package in the root ships is an error, not a warning. So is a seed named in two queues.

Medium-only packages #

peiso also writes /lcl/policy/medium-only-packages into the image. Both installerd and the legacy peios-install script use it to remove packages selected only for the medium before installing disk-boot. A DWE image carries:

peios-medium-packages-v1
root dev.peios.live-boot
initramfs dev.peios.live-boot-irf
root dev.peios.dwed

The first line identifies the format. Each remaining nonempty line names one of the two supported roots and a package name; duplicate entries, unknown roots and unknown formats are errors. Plain images omit the DWE row. peiso does not mark arbitrary [[package]] additions for removal.

The copied list remains on the target while the package swap is incomplete. A retry removes only listed packages still installed, and a package-database error stops the operation. Package failures preserve the medium repository's configuration so a retry can still install disk-boot.

After the swap succeeds, the installer atomically replaces the list with the single newline-terminated line peios-medium-packages-complete-v1, then removes the medium repository and the checkpoint. A retry of this completed state only finishes cleanup: repository removal may already have deleted the configuration needed by repo add, so it must not repeat trust setup or package installation. Repair boot files recognises either checkpoint state even if live-boot has already been removed. Registry seed queue handling is unchanged.

Media built before this list existed use the historical two boot-package removals. Rebuild a DWE medium with the updated peiso and installers to remove its DWE package automatically; updating an installer alone cannot infer the medium's original package choices.

4. Pack the initramfs #

mkirf packs root/boot/initramfs/ into root/system/boot/initramfs.cpio.zst — inside the root, where an installed system keeps its own, so the squashfs carries it. Two subtrees are excluded: var/state/peipkg (the initramfs root's package database) and lcl/conf/peipkg (its repository configuration). Both belong to the real root.

mkirf is the copy in the root being built, run on the host under the root's own dynamic loader (usr/lib/<triplet>/ld-linux-*.so.2 --library-path … --argv0 mkirf …/usr/bin/peiosutils). No chroot, no privilege, and no second implementation of the tool the running system uses.

5. Squash the root #

The whole root/ becomes rootfs.squashfs (zstd). peiso streams the tree to mksquashfs -tar as a PAX tar rather than pointing it at the directory, for three reasons that all come down to the same property — the image is what a privileged build would have produced:

  • every entry is root:root, though the build user owns the files on disk;
  • every recorded signature from stage 1 becomes the file's security.peios.sig attribute, written into the squashfs by name — a squashfs stores whatever attributes the tar names;
  • every mtime is the spec's source_date, and entries are emitted in sorted order, so the image is reproducible.

A recorded signature whose file is not in the root is an error: a stale record must not ship unsigned firmware.

6. Build the UKI #

mkuki — again the shipped copy, run the same way — bundles the kernel from root/usr/lib/modules/<release>/, the cpio from stage 4, and the command line live-boot ships (root/usr/share/live-boot/cmdline) into one EFI binary at root/boot/efi/EFI/BOOT/BOOTX64.EFI. It runs after the squashfs so the image does not carry the ESP tree.

That command line names no console=. As on any distribution, the kernel chooses: the display's virtual terminal on a machine that has one, the serial port otherwise, and /dev/console follows the choice, so the login prompt appears wherever the kernel put its messages. A boot that wants the serial line regardless says so at boot time through the UKI stub's SMBIOS channel, which is what the dist/release targets do.

7. Author the ISO #

root/boot/efi/ becomes a FAT32 image (mkfs.vfat + mcopy, no mount) at boot/efi.img inside the ISO's data area, beside rootfs.squashfs and repo/. xorriso names that one file twice: as the El Torito boot image, which is how firmware boots an optical drive (a physical disc, or an ISO a hypervisor attaches as a CD-ROM), and as the EFI System Partition in a GPT, which is how firmware boots the same bytes written to a USB stick or attached as a disk. The image has to sit inside the ISO9660 volume rather than be appended after it: El Torito cannot record a boot image larger than 32 MiB, and firmware reads the missing size as "to the end of the volume" — an appended partition starts exactly there and comes to nothing. The result is peios-<edition>-<version>[-dwe].iso, label PEIOS, which UEFI firmware boots directly either way: the UKI's initramfs runs live-boot, which finds the medium by that label, mounts the squashfs over an overlay, and pivots.

What each stage needs #

StageRunsFrom
composepeipkg's compose libraryin-process
medium repositorypeipkg's repopub libraryin-process
seedsfile copiesin-process
initramfsmkirfthe root, via its loader
squashfsmksquashfsbuild host
UKImkukithe root, via its loader
ISOmkfs.vfat, mcopy, xorrisobuild host

Nothing runs as root, and nothing runs inside the root.

Running a build

Peios / Peiso / Building images

The commands #

peiso root <spec.toml>... [--out <dir>] [--overwrite]
peiso iso  <spec.toml>... [--out <dir>] [--overwrite]

peiso root composes the spec's edition into a root tree and stops — useful for inspecting what a release contains, or for a root that will be packed some other way. --out names the directory; without it the root goes to the same place iso would put it.

peiso iso runs the whole pipeline and prints the ISO's path on stdout as its last line; progress goes to stderr. Both commands work from any current directory — relative paths inside a spec resolve against that spec's own directory, and the output directory dist/peios-…/ is relative to where you run.

A build refuses an output path where a root already sits. --overwrite replaces it and rebuilds — the rebuild loop after republishing a package — but replaces only something recognisably a composed root (it carries the package database) or an empty directory, never an arbitrary directory the path happens to name. make iso passes it, so rebuilding is the default there.

Both also take more than one spec, layering them left to right, so a statement of where packages come from can be shared across many builds that each name only what they build. The spec has the rules.

There is no --version, --edition or --dwe flag. Everything that shapes an image is in the spec, so a build is reproducible from the file alone.

The Makefile #

dist/release/Makefile wraps the commands and the QEMU invocations that boot their output:

Target
make isobuild peiso from the tree, then peiso iso experimental.toml
make iso-devthe same with the dev.toml layer: a low squashfs level, so the squash stage takes a fraction of the time and the image is somewhat larger — the edit-boot loop's target, never a release. It writes the same build directory as iso.
make iso-dwethe same from experimental-dwe.toml
make bootboot the newest plain build: UEFI, serial console (the Makefile appends console=ttyS0 through the stub's SMBIOS channel; the image itself names none), GXWI available at https://127.0.0.1:7780/ on the host
make boot-cdboot the newest plain build with the medium attached as an optical drive, the way a hypervisor attaches an ISO
make boot-dweboot the newest DWE build with a vsock device (DWE_CID, default 3)
make boot-quiet / boot-breakadd peios.quiet=2 / rd.break to the command line via SMBIOS
make boot-vgaboot with the framebuffer in a window and console=tty0 last, so the surface is drawn on a VT — what a real machine shows. The serial log stays on stdio. Combine with any target by hand: make boot-installed GRAPHICS='-display gtk -vga std' SMBIOS=…
make boot-installboot the DWE build with a blank 8 GiB disk.img attached as /dev/vdb
make boot-installedboot disk.img alone — no medium
make clean-disk / make cleanstart the install test over / remove every build directory

The build directory a target uses is the newest ../peios-experimental-<version>[-dwe]/; only peiso knows a build's resolved version, so the Makefile looks rather than guesses.

Before signing in to GXWI, follow the Quick start's certificate check.

Driving the running system #

dist/release/drive.py boots a build by the direct kernel path, waits for the login prompt, feeds a script to the console, and prints what the guest said:

./drive.py --no-disk probe.sh                 # in the live system
./drive.py --installed probe.sh               # on the installed disk
./drive.py --build ../peios-experimental-2026.9 --no-disk probe.sh

It talks to the ordinary autologin session — an administrator, not SYSTEM. For anything that needs SYSTEM, use a DWE build and dwe exec.

The legacy peios-install test #

An installation medium is only proven by installing from it and booting what it produced. The test below exercises the older peios-install script through DWE. For ordinary console or browser installation, follow Install Peios on a PC and Installing to disk: the current installerd daemon performs privileged disk operations as SYSTEM for its unprivileged surfaces and does not require this DWE test path.

The legacy script is supported only from a SYSTEM session. Its ordinary cp -ax must read SYSTEM-only source files such as /boot/initramfs/var/state/secrets; direct installation from an administrator session is unsupported. The console administrator can open the raw disk, and the PEI-549 kernel change separately allows partition maintenance through SeManageVolumePrivilege. DWE provides the SYSTEM session used below:

make iso-dwe
make boot-install          # leave it running

then, from another terminal:

DWE_TARGET=vsock:3 dwe exec -- peios-install --yes --whole-disk /dev/vdb

The script partitions and formats the disk, copies the live root, establishes trust in the medium repository on the target, replaces live-boot with disk-boot in both the root and its initramfs, writes a command line with the root filesystem's UUID, and rebuilds the target's initramfs and UKI. Then:

make boot-installed

boots the disk with nothing else attached. The initramfs hook is now disk-boot's (mount-root-disk.sh); the login prompt on the serial console is the installed system. An installed system moves to the next release with upgrade-peios.

Note

With a rebuilt medium containing the updated peiso and installers, both installation paths remove the medium-selected dev.peios.dwed package from the target. Older media without the medium-only package list keep the historical boot-package-only cleanup. The live-only DWE service seed is discarded in either case. See medium-only packages.

Variants #

A variant is another spec file. experimental-dwe.toml is the Experimental spec plus [devtools] dwe = true, and builds into its own …-dwe/ directory so it never overwrites the plain image. A spec that pins version = "2026.8" reproduces a release after newer ones exist; one that names a different [[packages.repository]] builds from another source.

Customising an image

Peios / Peiso / Building images

peiso's job is to make a custom Peios image an ordinary thing to build. The release stays what the edition says it is; everything you add on top is yours, and peiso writes it all down in customisations.toml beside the build so two images from the same release differ auditably rather than silently.

This page goes through the additions in the order the build applies them. All of them are optional.

Extra packages #

[[package]]
name    = "org.gnu.bash"
version = ">= 5.2"        # optional

Resolved together with the edition's closure from the spec's sources, so a conflict fails the build. See The spec.

Files #

[[file]]
src  = "overlay/motd"                        # relative to the spec
dest = "usr/etc/motd"                        # inside the root
sddl = "O:SYG:SYD:(A;;GA;;;SY)(A;;FR;;;WD)" # optional

The file is copied into the composed root before anything is packed, so a dest under boot/initramfs/ lands in the initramfs — a hook you are still writing, say — and one under lcl/ lands in the operator tree. A file at a dest the release already has replaces it.

A program stays a program: a file whose source anyone can execute is injected with mode 0755, and any other with 0644. The mode means nothing more than that on Peios, but Linux still refuses to execute a file with no execute bit, whatever its descriptor allows.

sddl gives the file a security descriptor. Peios has no file modes; the descriptor is the whole of a file's access control. peiso compiles the SDDL and writes it into the squashfs as the file's security.peios.sd attribute — the same route as the firmware signatures, and just as privilege-free. Without sddl the file gets whatever the mount synthesises for an object with no descriptor, which follows its parent. The initramfs carries no attributes at all, so sddl on a file under boot/initramfs/ is refused.

Note

An injected file has no owning package. Nothing verifies it, nothing upgrades it, and peipkg does not know it exists. That is fine for a motd, a hook under development or a site's own policy file; the moment a file matters, make it a package.

First-boot scripts #

[[autorun]]
src  = "scripts/setup.sh"
name = "50-setup"         # optional; default: the file's name

The script is placed, executable, in /lcl/policy/autorun.d/, which peinit runs in order after the registry is serving and before it plans its services. peiso's own entries there are 10-apply-seeds.sh (the release's registry seeds) and 20-features.sh; a name that sorts after those runs once the release's policy is in place, which is what a script usually wants.

Whether a script runs once is the script's business. The idiom is to end with rm -- "$0" — on an installed system that is once; on the live medium the removal is an overlay whiteout that does not survive a reboot, so the script runs again against the fresh tmpfs registry, which is usually what a live image needs. The script runs with peinit's identity at that phase, not as a logged-in user.

Features #

[[feature]]
name  = "dynamic-boot"
state = "enable"          # or "install"; default enable

A feature is set-up a package's scripts do beyond its files. This generates 20-features.sh, which runs feat add <name> (install then enable) or feat install <name> per entry, in order, and removes itself. The feature's definition must be in the root — usually by a [[package]] that ships it (dev.peios.feat-dynamic-boot here). Services a feature creates start on the same boot, since autoruns finish before peinit enumerates them.

The dynamic-boot definition creates mkirf and mkuki foreground watcher services. It deliberately follows the unique kernel below /usr/lib/modules rather than recording the release present when the feature was installed, so a kernel package upgrade is adopted automatically. The UKI watcher prefers the installed system command line at /lcl/etc/boot/cmdline; a live image that has not been installed falls back to /usr/share/live-boot/cmdline.

Files on the medium #

[[medium]]
src  = "docs/"            # a file or a directory
dest = "docs"

Placed in the ISO's data area beside rootfs.squashfs and repo/. The live system mounts the medium at /media/peios, so this appears at /media/peios/docs. It is on the stick, not in the system: use it for payloads an installer or a script will read from the medium, or for things a person should find on it.

The packers #

[squashfs]
compression = "zstd"                     # any mksquashfs -comp; default zstd
exclude     = ["usr/share/doc"]          # root-relative; default none

[initramfs]
exclude = ["var/state/peipkg", "lcl/conf/peipkg"]   # the default; replaces it

[boot]
cmdline_extra = "loglevel=7 ignore_loglevel"   # appended to live-boot's command line

initramfs.exclude replaces the default list rather than adding to it, so an explicit [] packs everything; the two defaults are the initramfs root's package database and repository configuration, which belong to the real root. boot.cmdline_extra is appended to the command line the boot package ships and baked into the UKI — there is no full replacement, because the shipped line carries init=, and losing it quietly is how a custom image fails to boot.

The shipped line runs the kernel at loglevel=4, so the console carries kernel errors and worse and peinit's own boot narrative stays legible. The example above is the common override: it puts the full kernel log back. ignore_loglevel is the token that does the work — while it is set the kernel prints every message whatever loglevel says — so raising the number alone will not undo the default.

Note

The kernel's loglevel and peinit's peios.quiet are independent settings that both sound like "how noisy is the boot". loglevel governs what the kernel prints; peios.quiet governs what peinit prints. Quieting one does nothing to the other.

Not a knob: the ISO's label. live-boot finds the medium by it.

The record #

Every build writes customisations.toml beside the root:

# What this spec added beyond the release. Written by peiso.

[[file]]
src  = "/home/me/site/overlay/motd"
dest = "usr/etc/motd"
sddl = "O:SYG:SYD:(A;;GA;;;SY)(A;;FR;;;WD)"

[[autorun]]
src  = "/home/me/site/scripts/setup.sh"
name = "50-setup"

[[feature]]
name  = "dynamic-boot"
state = "enable"

Together with compose.lock.toml (every package that was resolved) it is the complete answer to "what is in this image that is not in the release".

Peios Learn — documentation for the Peios project.

Built with Trail.