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
pekit

Reference

Single-page view · as markdown

Recipe format reference

pekit / Reference

This is the normative schema for the two TOML files at the heart of a pekit recipe: pekit.toml (the recipe proper) and package.pekit.toml (what to package). It enumerates every section, every key, its type, whether it is required, and its meaning. Behaviour that a key merely triggers is cross-linked to the concept pages; this page is the field-by-field contract.

Two strictness rules hold throughout and are called out per-section below:

  • Unknown keys are hard errors. Every table the loader owns has a fixed set of accepted keys. Any other key — including a camelCase spelling of a known snake_case key, such as outDir — is rejected with an unknown_key diagnostic. There is no lenient/forward-compatible mode.
  • Types are checked on decode. A key declared here as a string must be a string, a table must be a table, and so on; a mismatch is an invalid_type error.

pekit.toml #

Worked example #

out_dir = "dist"

[env]
CFLAGS = "-O2"
PREFIX = "/usr"

[wrap]
command = ["pesb", "run", "--", "{{command}}"]

[source.git]
url = "https://github.com/example/tool.git"
ref = "v{{version}}"
versions = ">=1.0"
tag_regex = "^v(?P<version>[0-9.]+)$"

[build]
command = "make PREFIX=$PREFIX"

[build.docs]
command = ["make", "docs"]
needs = ["main"]

[build.linked]
command = "make link"
dependencies.pesb-dev.libc = ">=2.38"

[test]
command = "make check"

[install]
command = "make install DESTDIR=$PEKIT_OUT"

[clean]
command = "make clean"

Top-level keys #

The recipe file accepts exactly these top-level keys. Anything else is an unknown recipe key error.

KeyTypeRequiredMeaning
out_dirstringnoOutput directory name, relative to the recipe root. Default "out".
envtablenoEnvironment variables exported to every target command. See [env].
wraptablenoCommand wrapper applied to every target. See [wrap].
sourcetablenoWhere the recipe's source tree comes from. See [source].
delegatebool or tablenoBorrow build/env/wrap/package definitions from the source tree. See [delegate].
source_packagetablenoCorresponding-source package emission control. See [source_package].
buildtablenoBuild target(s). See [build] / [test] / [install] / [clean].
testtablenoTest target(s).
installtablenoInstall target(s).
cleantablenoClean target(s).
gentablenoSource-generating target(s). See [gen].

[env] #

A flat table of environment-variable name → value. Every entry is exported into each target command's environment (layered on top of the PEKIT_* contract; see Anatomy of a recipe).

KeyTypeRequiredMeaning
<NAME>string—Value for variable NAME.

Each NAME must match ^[A-Za-z_][A-Za-z0-9_]*$; anything else is an invalid_env error. Values must be strings. Declaration order in the file is preserved, so a later entry may reference an earlier one via shell expansion in the target command.

[wrap] #

Wraps every target command in another command (for example, running it inside a sandbox launcher).

KeyTypeRequiredMeaning
commandstring or array of stringsnoThe wrapper. Must contain the placeholder {{command}} exactly once.

No other key is permitted under [wrap]. The {{command}} rules mirror a target command:

  • String form: the whole string is run through a shell; it must contain the substring {{command}} exactly once.
  • Array form: {{command}} must appear as a complete argument (not a substring of a larger argument) exactly once, and never as the first element (element 0 is the wrapper program itself).

An omitted command yields an empty wrap (no wrapping).

[source] #

Selects and configures the source tree. It has three sub-tables. At most one reproducible source (git or url) may be present — declaring both is a mixed_source error. A local source may be combined with a reproducible one (the local path acts as an override). Unknown keys directly under [source] are rejected.

Sub-tableMeaning
[source.git]Clone from a git repository.
[source.url]Download from a URL (optionally an archive).
[source.local]Use a directory on disk.

One direct key is accepted alongside the sub-tables:

KeyTypeRequiredMeaning
patchesstringnoName of a recipe-root directory (a single path segment) whose series file pekit applies to the materialised source tree before any target runs. Requires a reproducible source (patches_source otherwise). See Patches.

See Sources for materialisation, caching, and provenance behaviour. Resolved git and url sources are pinned trust-on-first-use in the recipe's machine-written pekit.lock.

[source.git] #

KeyTypeRequiredMeaning
urlstringyesGit remote URL to clone.
refstringnoRef (tag/branch/commit) to check out. Templated. Default "{{version}}".
versionsstringnoVersion cap: a constraint string filtering enumerated or requested versions (see Versions).
tag_regexstringnoRegex extracting version numbers from tag names during enumeration.

[source.url] #

KeyTypeRequiredMeaning
urlstringyesURL to download. Templated with the version.
extractboolnoTreat the download as an archive and extract it. Default false.
rootstringnoSub-directory within the extracted tree to use as the source root. Default ".".
versionsstringnoVersion cap: a constraint string filtering enumerated or requested versions (see Versions).
file_regexstringnoRegex extracting version numbers when enumerating from a listing.
checksumstring or tablenoExpected checksum. A bare string applies to all versions; a table maps version → checksum.
signaturetablenoUpstream signature verification — see [source.url.signature] below.

[source.url.signature] #

Optional sub-table of [source.url]. Its presence makes upstream signature verification required: the detached signature is fetched and verified against the pinned keys before a version is locked or built. A missing signature is signature_missing; a failed verification is signature_invalid; either aborts the run and writes no lock entry. Verification is in-process OpenPGP — no host gpg is involved.

KeyTypeRequiredMeaning
key_filesstring arrayyesCommitted public key files (armoured or binary OpenPGP), resolved relative to the recipe root. Must be non-empty.
urlstringnoSignature URL template. {{source_url}} expands to the rendered artifact URL; version variables are also available. Default "{{source_url}}.sig".
ofstringnoWhat the signature covers: "artifact" (the published file, default) or "decompressed" (its decompressed content — kernel.org's .tar.sign signs the uncompressed tar). Any other value is invalid_signature.
fingerprintsstring arraynoAllowlist of signer fingerprints (hex; spaces and 0x ignored, case-insensitive). When set, a valid signature by any other pinned key is signature_untrusted_key.

[source.local] #

KeyTypeRequiredMeaning
pathstringyesPath to the source directory, resolved relative to the recipe root.

[delegate] #

Lets a recipe borrow definitions that live inside the fetched source tree rather than in the recipe directory. May be written as a bare boolean (delegate = true, equivalent to all = true) or as a table.

KeyTypeRequiredMeaning
allboolnoDelegate everything (build, env, wrap, packages).
buildboolnoDelegate build targets.
envboolnoDelegate [env].
wrapboolnoDelegate [wrap].
packagesboolnoDelegate package definitions.

Any other key is an unknown delegate key error. A category is delegated when either its own flag or all is set.

[source_package] #

Controls the corresponding-source package a recipe emits alongside its binary packages. Emission is automatic — the table exists only to opt out or rename. A recipe emits one when both of these hold: it has a reproducible [source] (git or url; local overrides and bare branch refs never qualify), and it produces at least one peipkg-format package.

KeyTypeRequiredMeaning
namestringnoPackage name. Default <recipe-dir>-source.
enabledboolnoSet false to emit no source package. Default true.

Any other key is an unknown source_package key error.

The emitted package is noarch, versioned identically to the recipe's package members (members that disagree on version are a source_package_version_conflict error), licensed as the conjunction of the members' licenses, and installs under /usr/src/dist/<name>-<version>/ (with any -source suffix stripped from <name>):

  • upstream/ — the pristine source input: a url source's downloaded artifact byte-for-byte, so its hash matches the committed pekit.lock, or a git archive export of the locked commit.
  • patches/ — the recipe's patch series, when a patches/ directory exists.
  • recipe/ — the build-controlling files from the recipe directory: pekit.toml, package definitions (including packages.pekit/), env files, pekit.lock, and keys/. *.keyring.pekit.toml files are never included.

Every peipkg-format member the recipe emits carries the source package's name in its manifest's build.source_package field, linking each binary to its corresponding source.

[build] / [test] / [install] / [clean] {#targets} #

These four sections define targets — the commands pekit runs. Each section takes one of two shapes, distinguished by whether a command key is present directly in the section:

  • Bare target: command (and the other target keys) sit directly under the section, e.g. [build] with command = "make". This defines a single target named main.
  • Named targets: the section contains sub-tables, e.g. [build.docs] and [build.linked], each a target. Each name must be a valid selector.

Mixing the two — a bare command alongside a named sub-table in the same section — is a mixed_targets error. (A sub-table whose key is itself a target key, such as [build.dependencies], is read as part of the bare target, not as a named target.)

Fields of a single target:

KeyTypeRequiredMeaning
commandstring or array of stringsyesThe command. String form runs through a shell; array form is an argv executed directly. An empty array is rejected.
needsarray of stringsnoNames of other targets in the same section that must run first.
clear_outboolnoWipe this target's output directory before running. Default true.
dependenciestablenoBuild targets only. Build-time dependencies the target needs provisioned. See below.
signtablenoBuild targets only. Files in the target's output to sign after the command succeeds, by signature kind. See sign below.

Any other key in a target table is an unknown target key error. dependencies and sign are accepted only in [build] targets; using either in test/install/clean is an unknown key.

The dependencies table is keyed by provider (a selector), each mapping dependency names to version constraints:

[build.linked.dependencies]
pesb-dev.libc = ">=2.38"
pesb-dev."pkgconfig(zlib)" = "*"
LevelTypeMeaning
providertableA dependency-provider selector.
provider.<name>stringVersion constraint for the capability name. Must be non-empty; use "*" for any version.

Dependency names may be real package names or virtual capabilities (sonames, pkgconfig(...), etc.) and are validated against peipkg's capability grammar. See Commands and targets.

[build.<name>.sign] {#target-sign} #

The sign table is keyed by signature kind. Each kind is a table mapping a path or glob, relative to the target's $PEKIT_OUT, to the dotted path of the keyring entry holding the signing key:

[build.main.sign.pip]
"bin/peinit"  = "tcb.priv"
"lib/*.so.*"  = "tcb.priv"

The kind decides the signature format and where it is stored; the keyring entry is resolved through the ordinary keyring mechanism when the target runs. Signing happens after the target's command exits 0 and before any dependent target or package sees the output.

LevelTypeMeaning
kindtableA signature kind. The only kind is pip — the Peios binary signature that confers a Process Integrity Protection tier. Any other name is unknown_key.
kind.<pattern>stringA relative path or glob (the same glob syntax as [files]). The value is a keyring leaf path such as tcb.priv whose value is the path to the private key. Must be non-empty.

For pip, the key is an ML-DSA-65 private key — PKCS#8 PEM as produced by openssl genpkey -algorithm ML-DSA-65, or a raw 32-byte seed — and every matched file must be a 64-bit little-endian ELF. A pattern that matches nothing (sign_target_missing), a keyring entry that is absent or does not load (signing_key), a file that cannot carry the signature (sign_failed), and two patterns naming different keys for one file (sign_conflict) are all errors; pekit never leaves a listed file unsigned. The mechanism is described in Signing and provenance.

[gen] {#gen} #

A [gen] section defines source-generating targets: they run at the recipe root and write generated source into the tree rather than producing an out_dir artifact. Bare/named shapes work exactly as for the target sections above ([gen] is gen.main; [gen.<name>] names a target). Gen targets have their own fields — they take no needs or clear_out:

KeyTypeRequiredMeaning
commandstring or array of stringsyesThe generator. Runs at the recipe root; writes generated source in place.
verify_commandstring or array of stringsnoThe drift gate: exit 0 = in sync, non-zero = stale. Run by pekit verify and by the consuming-command pre-flight.
verify_on_buildarray of stringsnoBuild target names this gate applies to. Absent gates every build; [] gates none; a list gates only those.
verify_on_testarray of stringsnoAs verify_on_build, for the test namespace.
dependenciestablenoBuild-time dependencies for command (and for verify_command, unless overridden). Same shape as a build target's dependencies.
verify_dependenciestablenoWhen present, fully replaces dependencies for the verify_command run.

Any other key is an unknown target key error. Setting verify_on_build, verify_on_test, or verify_dependencies without a verify_command is an invalid_gen error (there is nothing to gate).

A gen target's $PEKIT_OUT is a pekit-managed scratch directory (<out_dir>/.scratch/gen/<name>) outside the artifact area, cleaned on success and retained on failure — a convenient place for verify_command to regenerate into for comparison. For the command surface, the pre-flight, and --no-verify, see Commands and targets.

package.pekit.toml #

The package file describes one or more package artifacts to emit from the built source. The same schema applies to the base file (package.pekit.toml) and to per-member member files (<name>.package.pekit.toml); see Multi-package recipes and Supporting files for how the files are discovered and layered.

Worked example #

format = "peipkg"
builds = ["main"]

[package]
name = "tool"
version = "{{version}}"
architecture = "x86-64-v3"
description = "An example tool"
license = "MIT"
homepage = "https://example.com/tool"
default_root = "opt.tool"

[dependencies]
libc = ">=2.38"
zlib = { constraint = ">=1.3", root = "opt.tool" }

[optional_dependencies]
libcurl = ">=8.0"

[provides]
tool = "{{version}}"

[files]
"main:bin/tool" = "usr/bin/tool"
"@recipe:share/" = { path = "usr/share/tool/", override = true }

[symlinks]
"usr/bin/t" = "tool"

excludes = ["@recipe:share/*.tmp"]

[claims.provides.editor.default]
path = "usr/bin/editor"
target = "usr/bin/tool"

[[publish.localdir]]
path = "dist"
overwrite = false

Top-level keys #

The package file accepts exactly these top-level keys; anything else is an unknown package key error.

KeyTypeRequiredMeaning
formatstringnoArtifact format: "tar" or "peipkg". Default "tar".
clear_outboolnoWipe the package stage directory before building. Default true.
buildsarray of stringsnoNames of build targets this package requires before staging.
packagetablenoPackage metadata. See [package].
filestablenoPayload file mappings. See [files].
symlinkstablenoSymlinks to embed in the payload. See [symlinks].
excludesarray of stringsnoRefs/globs to drop from directory payloads. See excludes.
multipacktablenoExpand into a family of instances. See [multipack].
publishtablenoWhere to copy finished artifacts. See [publish].
dependenciestablenoRuntime dependencies. See [dependencies].
optional_dependenciestablenoOptional runtime dependencies (same schema as dependencies).
conflictstablenoname → constraint of packages this one conflicts with.
providestablenoname → version of capabilities this package provides.
replacestablenoname → constraint of packages this one replaces.
side_effectsarray of stringsnoDeclared install-time side effects.
sd_overridestablenopath → SDDL security-descriptor overrides.
claimstablenoClaim slots on provides/dependencies. See [claims].

Important

The tar format carries payload only: it cannot express manifest metadata. If a tar package sets version, architecture, description, license, homepage, or any of dependencies, optional_dependencies, conflicts, provides, replaces, side_effects, or sd_overrides, the build fails with unsupported_manifest_field. The peipkg format requires [package].version, [package].architecture, and [package].license — the license is optional at the format level, but pekit requires it so every package states its redistribution terms and the corresponding-source package can derive its own.

Most string-valued fields (metadata, dependency names/constraints, file refs and destinations, claim paths/targets) are run through the template engine, where {{version}} and {{multipack}} are available. See Versions and Multi-package recipes.

[package] #

Package metadata. Accepts exactly these keys; any other is an unknown package metadata key error. All values are strings.

KeyTypeRequiredMeaning
namestringnoPackage name. Defaults to the package selector (suffixed with the multipack value for multipack instances) when omitted.
versionstringnoPackage version. Required for peipkg.
architecturestringnoTarget architecture. Required for peipkg.
descriptionstringnoHuman-readable description.
licensestringnoLicense identifier. Required for peipkg.
homepagestringnoProject homepage URL.
default_rootstringnoPreferred named root for a top-level install. Must be a dotted named reference matching [a-z0-9][a-z0-9_-]* per segment, never a filesystem path (a / is rejected).
special_system_packagebooleannoDeclares the package exempt from the payload layout rules, for the rare package whose job is to lay down the structure those rules protect — the base-filesystem package that mints the mountpoint tree is the archetype. Setting it disables pekit's layout validation for this package. It grants nothing at install time: whoever installs or composes the result must also pass --dangerously-bypass-path-restrictions, so a recipe can propose its own exemption but never grant one. Defaults to false.

Note that dependencies, provides, conflicts, and the rest are top-level tables, not sub-keys of [package].

[dependencies] #

Runtime dependency constraints. [optional_dependencies] has the identical schema. Each entry is either a bare constraint string (the short form) or a table that additionally places the dependency in a named root.

FormTypeMeaning
<name> = "constraint"stringVersion constraint; the dependency lands in the depender's own root.
<name> = { constraint, root }tableTable form (below).

Table form keys — only these two are accepted (unknown_key otherwise):

KeyTypeRequiredMeaning
constraintstringnoVersion constraint. Omitting it matches any version.
rootstringnoNamed root to place the dependency in. Same grammar as default_root.

Dependency names may be real or virtual capabilities. See Dependencies and claims.

[files] #

Maps a source ref (the key) to a destination path within the package payload (the value). The value is either a string destination or a table.

Value formMeaning
"ref" = "dest/path"Copy the ref to dest/path.
"ref" = { path, override }Table form (below).

Table form keys — only these are accepted:

KeyTypeRequiredMeaning
pathstringyesDestination path in the payload. Must be non-empty.
overrideboolnoMark the payload as an override entry (skips manifest payload validation for peipkg). Default false.

The source ref selects where the file is read from. A bare path with no prefix is resolved against the recipe root (or the layer's owner). Explicit forms: @recipe:<path>, @source:<path>, @workspace:<path>, and <build-target>:<path> (reading a build target's output; :<path> alone uses the main build target). Destinations ending in / and glob patterns are supported. See Packages for ref resolution and glob semantics.

[symlinks] #

Maps a destination path within the payload (the key) to a symlink target (the value).

Value formMeaning
"usr/bin/x" = "target"Create a symlink at usr/bin/x pointing at target.
"usr/bin/x" = { target, override }Table form (below).

Table form keys — only these are accepted:

KeyTypeRequiredMeaning
targetstringyesSymlink target text. Must be non-empty.
overrideboolnoMark as an override entry. Default false.

excludes #

An array of refs/globs. During directory payload expansion, files matching an exclude pattern (resolved against the same root as the file mapping being expanded) are dropped from the payload. Patterns use the same ref forms as [files] keys.

[multipack] #

Expands one definition into a family of package instances, one per enumerated value. The only accepted key is enum, which takes one of two shapes:

Explicit list:

[multipack]
enum = ["gtk3", "gtk4"]

Each value must be a valid selector, non-empty, and unique.

Enumerated from files:

[multipack.enum.files]
path = "@source:themes/*/theme.ini"
regex = "^(?P<value>.+)$"

[multipack.enum.files] accepts exactly path and regex, both required:

KeyTypeRequiredMeaning
pathstringyesA ref glob (templated) whose matches are enumerated.
regexstringyesRegex applied to each match's basename. Must have exactly one capture group, or a single named value group, which supplies the instance value.

See Multi-package recipes.

[publish] #

Where finished artifacts are copied. The only accepted target is localdir, written as an array of tables:

[[publish.localdir]]
path = "dist"
overwrite = false

Each entry accepts only path and overwrite:

KeyTypeRequiredMeaning
pathstringyesDestination directory, relative to the workspace root (or recipe root outside a workspace). Templated. The artifact keeps its own filename.
overwriteboolnoAllow replacing an existing file at the destination. Default true.

[claims] #

Attaches claim slots to provides and dependency entries (a claim is a symlink slot a provider fills or a consumer expects). The [claims] table has two sides — provides and dependencies — each keyed by the provides or dependency name, then by slot, then to a descriptor. Any side other than provides/dependencies is an unknown_key error.

[claims.provides.<name>.<slot>]
path = "usr/bin/editor"
target = "usr/bin/tool"

[claims.dependencies.<name>.<slot>]
path = "usr/bin/editor"

A slot descriptor accepts exactly these two keys (anything else is rejected); both are strings and both are templated:

KeyTypeRequiredMeaning
pathstringnoThe symlink location (set on a consumer, or as a provider default).
targetstringnoThe holder file within this package (set on a provider). A provider's target must point at a file the package ships.

Provider-side slots (claims.provides) attach to matching [provides] entries by name; consumer-side slots (claims.dependencies) attach to matching [dependencies]/[optional_dependencies] entries. See Dependencies and claims and the Peios claims model for the resolution behaviour.

See also #

  • Supporting files — the workspace, env, keyring, lockfile, and patch-series schemas.
  • Anatomy of a recipe — the narrative walk-through of these files.
  • Packaging files — how [files] refs and destinations resolve.

Supporting files and reference tables

pekit / Reference

This page documents the TOML files that sit alongside a recipe — the workspace file, env files, and keyring files — and gives exhaustive reference tables for the three substitution systems used throughout pekit: template variables, source-ref roots, and publish targets.

All files are TOML. Every loader rejects unknown top-level keys with an unknown_key diagnostic, so the key tables below are complete: any key not listed is an error.

For the recipe file itself (pekit.toml / *.pekit.toml), see recipe-format.


workspace.pekit.toml #

A workspace file marks a directory as a pekit workspace and declares distro-wide defaults.

KeyTypeRequiredMeaning
includearray of stringsyesMember patterns the workspace builds. Must be present and non-empty, or the load fails (missing_key).
excludearray of stringsnoPatterns removed from the include set.
[env]tablenoWorkspace-level environment variables. See env table.
[wrap]tablenoWorkspace-level command wrapper. See wrap table.
[policy]tablenoDistro-wide derivation policy. See below.

[policy] #

The [policy] table currently carries exactly one sub-table. Any other key under [policy] is rejected.

KeyTypeRequiredMeaning
[policy.symbol_versions]table (string → string)noMaps a shared-library soname to the symbol-version token prefix whose tokens are commensurable with the providing package's version. Governs which sonames get a symbol-version floor during dependency derivation.

Each entry in [policy.symbol_versions] is soname = "PREFIX_":

[policy.symbol_versions]
"libc.so.6"     = "GLIBC_"
"libstdc++.so.6" = "GLIBCXX_"

env.pekit.toml #

An env file provides a named, selectable layer of environment variables, command wrapper, and dependency-provider override.

An env file must declare at least one of [env], [wrap], or dependency_provider; a file with none of them is rejected (missing_key).

KeyTypeRequiredMeaning
[env]tableone of the threeEnvironment variables. See env table.
[wrap]tableone of the threeCommand wrapper. See wrap table.
dependency_providerstringone of the threeSelector naming which of a build target's declared dependency-provider blocks ([build.<target>.dependencies.<provider>]) is exported as the PEKIT_DEPENDENCIES* variables. Validated as a selector.

--env <name> selection #

The active env file is chosen from the --env <name> flag (default main), resolved relative to the recipe root:

--env valueFile loadedMust exist?
(unset) or mainenv.pekit.tomlno (silently absent)
none(no env file loaded)n/a
any other <name><name>.env.pekit.tomlyes — a missing file is an error

So --env prod loads prod.env.pekit.toml, and it must exist. The default env.pekit.toml is optional.

[env] table #

Shared by workspace.pekit.toml, env.pekit.toml, and the recipe's own [env]. It is a flat table of NAME = "value" string pairs.

RuleDetail
Value typeEvery value must be a string.
Name grammarNames must match ^[A-Za-z_][A-Za-z0-9_]*$.
Reserved prefixUser env may not set any PEKIT_* name — those are pekit-managed. A PEKIT_-prefixed name is rejected at build time (reserved_env).
LayeringApplied workspace → (delegated source) → recipe → env file, later layers overriding earlier ones.
ExpansionValues are shell expressions, not literals. $NAME expands when the target runs, so a value may reference a managed PEKIT_* variable or an env value declared before it. Values are word-split-safe: spaces are preserved, so a flag list needs no quoting of its own.
[env]
CC = "clang"
CFLAGS = "-O2 -pipe"
CXXFLAGS = "$CFLAGS -Wp,-D_GLIBCXX_ASSERTIONS"
TOOL_PATH = "$PEKIT_ROOT/tools"

$ always expands — a literal $ is not expressible here, and \$ does not escape it. A value needing one (an $ORIGIN rpath, say) belongs in the target's command, where the recipe has the full shell.

[wrap] table #

Shared by workspace.pekit.toml, env.pekit.toml, and the recipe. Wraps every target command in another command (sandbox, nice, a toolchain shim, etc.).

KeyTypeRequiredMeaning
commandstring or array of stringsnoThe wrapper. Must contain exactly one {{command}} placeholder.

{{command}} rules:

  • String form — the string must contain {{command}} exactly once.
  • Array form — exactly one element must be the literal {{command}} (as a complete argument, not a substring), and it may not be element 0 (the program). An element that merely contains {{command}} inside other text is rejected.
[wrap]
command = ["bwrap", "--ro-bind", "/usr", "/usr", "{{command}}"]

Note: {{command}} here is a wrap-only placeholder handled by the wrap machinery. It is not one of the template variables below and is not available in ordinary recipe strings.


*.keyring.pekit.toml #

A keyring file supplies secrets to the build environment as PEKIT_KEYRING_* variables. Keyrings are opt-in per invocation (--keyring); a keyring named <name> resolves to <name>.keyring.pekit.toml, searched in the workspace root then the recipe root (a path-like value is used verbatim).

Schema #

The file is a tree of nested TOML tables whose leaves are strings. Each leaf's dotted path is converted into an environment-variable name:

  • Prefix PEKIT_KEYRING_, then the dotted path uppercased.
  • Every run of non-alphanumeric characters (including the . separators) collapses to a single _.
  • Trailing _ is trimmed.
Leaf pathExported variable
tokenPEKIT_KEYRING_TOKEN
github.tokenPEKIT_KEYRING_GITHUB_TOKEN
registry.api-keyPEKIT_KEYRING_REGISTRY_API_KEY
# placeholders only — never commit real secrets
token = "REPLACE_ME"

[github]
token = "REPLACE_ME"

[registry]
api-key = "REPLACE_ME"

Restrictions #

RuleDetail
Leaf typeEvery leaf must be a string. A non-string leaf is an error.
NestingSub-tables nest arbitrarily; each nesting level adds a segment to the exported name.
Typed entriesA table containing a path or content key is a typed entry and is not supported yet (unsupported_keyring_entry).
CollisionsA keyring export that collides with a normal or managed env variable is an error (env_collision).

Well-known entries #

Most keyring values are opaque to pekit — they exist to be exported. The entries below are additionally read by pekit itself:

Leaf pathMeaning
signing.package_keyPath to an Ed25519 private key — a raw 32-byte seed, or PKCS#8 PEM as produced by openssl genpkey -algorithm ed25519. When present, every peipkg-format artifact the run produces — package members and -source packages alike — is packed with an embedded signature naming the key's fingerprint, and each emits a sign event. A relative path resolves against the invocation's working directory. A configured key that cannot be loaded is an error (signing_key); pekit never silently falls back to unsigned output.

Without a configured signing key, package writes unsigned artifacts, and publish refuses peipkg-format packages unless --allow-unsigned is passed (unsigned_publish).

A build target's sign table reads further entries, but which ones is the recipe's choice: each sign.pip value is the dotted path of a leaf holding the path to an ML-DSA-65 private key. Those entries are still exported to the target like any other.


pekit.lock #

The machine-written source lockfile, created and updated by pekit in the recipe directory beside pekit.toml. It pins fetched source inputs trust-on-first-use: the first resolve of a version records what was fetched, and every later resolve verifies against the record — see Sources for the behaviour. Commit it, and do not edit it by hand: pekit lock --repin --version <v> is the one supported way to change an existing entry. Loading is strict — an unknown key is unknown_key and an unsupported schema is lock_schema.

Schema #

KeyTypeMeaning
schemaintegerLockfile schema version. Currently 1.
[[source]]array of tablesOne entry per locked version, kept sorted by version.

Each [[source]] entry carries the keys for its source kind:

KeySet forMeaning
versionallThe locked version. Empty for a versionless url fetch.
urlurl sourcesThe rendered URL fetched at lock time. Provenance only — the hash, not the address, is the assertion.
sha256url sourcesSHA-256 of the fetched artifact.
refgit sourcesThe rendered ref the version resolved through.
commitgit sourcesThe commit the ref resolved to — the assertion.
signature_keyurl sources with [source.url.signature]Hex fingerprint of the pinned upstream key that verified the artifact at lock time.
locked_atallUTC timestamp of the pinning run (RFC 3339).

Worked example #

schema = 1

[[source]]
  version = "0.5.12"
  url = "https://example.org/dash-0.5.12.tar.gz"
  sha256 = "6a474ac46e8b0b32916c4c60df694c82058d3297d8b385b74508030ca4a8f28a"
  signature_key = "9f0b7ddc1325a3e2c40e5f0a88b1c62f3d94ab07"
  locked_at = "2026-08-10T11:00:00Z"

[[source]]
  version = "1.2.0"
  ref = "v1.2.0"
  commit = "8f3a1bc99e2f6d41c07b21ac1886d63b7f2e5d10"
  locked_at = "2026-08-10T11:00:00Z"

The patches directory #

The directory named by [source] patches = "<dir>" in pekit.toml (conventionally patches, and always a single path segment). pekit applies its series to the materialised source tree before any target runs — see Sources for the behaviour — and the whole directory ships in the recipe's source package under patches/.

The series file #

<dir>/series lists the patches in apply order: one slash-separated relative path per line. # starts a comment, whole-line or trailing; blank lines are ignored; entries must not contain whitespace. Subdirectories are allowed (misc/fix.patch).

Loading is strict:

ConditionError
series missing or unreadablepatch_series
An entry that repeats, contains whitespace, or escapes the directorypatch_series
A series entry with no file behind itpatch_missing
A *.patch file in the directory the series does not listunused_patch (permitted by --allow-unused)

Application is git apply per entry, in series order, with no fuzz: a failed hunk is patch_apply and a patch that matches nothing is patch_skipped.

Worked example #

patches/
  series
  misc/
    mke2fs-root-sd.patch

with a series of:

# Applied in dependency order:
misc/mke2fs-root-sd.patch   # stamp the root inode's SD at format time

Give each patch file a short header — what it changes and its upstream status (submitted, backport, Peios-specific). The series ships with the corresponding source, where that header is a third party's only context.


Template variables #

Recipe and package value strings (source refs, ref, package metadata, file/symlink paths, excludes, claims, multipack enum.files.path, publish path) are run through the template engine before use. The syntax is {{name}}, where name matches [A-Za-z0-9_]+. An unknown name is an error.

VariableSourceAvailability
{{version}}the selected version, verbatim (Raw)Errors when no version is selected.
{{major}}major componentAvailable once a version is selected.
{{minor}}minor componentErrors if the selected version has no minor component.
{{patch}}patch componentErrors if the selected version has no patch component.
{{prerelease}}pre-release component (after -)Empty string when absent.
{{buildmeta}}build-metadata component (after +)Empty string when absent.
{{multipack}}the current multipack instance valueOnly available while expanding a multipack package's per-instance values; errors in any non-multipack context.

A version string parses as major[.minor[.patch]][-prerelease][+buildmeta], so {{minor}} / {{patch}} are only present when the version actually carries them.

Shell targets use $PEKIT_* instead #

The {{...}} engine applies to recipe configuration strings, not to the shell bodies of build/test/install/clean commands. Target commands instead read the pekit-managed environment. The version equivalents are:

Template variableEnvironment variable
{{version}}$PEKIT_VERSION
{{major}}$PEKIT_VERSION_MAJOR
{{minor}}$PEKIT_VERSION_MINOR
{{patch}}$PEKIT_VERSION_PATCH
{{prerelease}}$PEKIT_VERSION_PRERELEASE
{{buildmeta}}$PEKIT_VERSION_BUILDMETA

Alongside these, the managed environment also exports PEKIT_RECIPE_ROOT, PEKIT_SOURCE_ROOT, PEKIT_LITERAL_ROOT, PEKIT_ROOT, PEKIT_OUT_BASE, PEKIT_OUT, PEKIT_COMMAND, PEKIT_TARGET, PEKIT_WORKSPACE_ROOT, PEKIT_BUILD_TIMESTAMP, PEKIT_SOURCE_TIMESTAMP, a PEKIT_<DEP>_OUT per declared needs dependency, and any PEKIT_KEYRING_* from active keyrings. The full environment contract is documented in the command-line reference.


Source-ref roots #

Package files sources, excludes, and multipack.enum.files.path are references whose leading token selects which root the path is resolved against. A reference is ROOT:path (or a bare path, which normalises to the owner's default root).

Reference formRootMeaning
target:paththe build target's output stageReads from the output of build target target.
:pathbuild target main's output stageSame as above with the target name defaulted to main.
@source:paththe materialised source tree (LiteralRoot)Reads from the checked-out / extracted source.
@recipe:paththe recipe directoryReads a file shipped next to the recipe.
@workspace:paththe workspace rootReads a file from the workspace. Errors if used outside a workspace.
bare pathowner defaultA path with no : normalises to the owning layer's root: @recipe: for recipe packages, @source: for source packages, @workspace: for workspace packages.

Any reference containing a : that is not one of the @-prefixed roots is a build reference; the token before the : is the target name. See packages for how these are used in a package's [files] table.


Publish targets #

The publish command copies a package's built artifact to declared destinations. There is exactly one publish kind: localdir. Any other [publish.*] key is rejected (unknown_key). A package selected for publishing that declares no target is an error.

Targets are an array of tables:

[[publish.localdir]]
path = "dist/{{version}}"
overwrite = false
KeyTypeRequiredMeaning
pathstringyesDestination directory, relative to the base root. Templated with {{version}} and {{multipack}}. The artifact keeps its own filename.
overwriteboolno (default true)Whether an existing destination file may be replaced. When false, an existing destination is an error (publish_exists).

Resolution details:

AspectBehaviour
Base rootThe workspace root when building in a workspace, otherwise the recipe root. path is joined onto it.
Final destination<base>/<path>/<artifact-filename>.
CollisionsTwo instances resolving to the same destination is an error (publish_collision) unless they are the same artifact.

Selectors #

Several names above are validated as selectors: target names, package selectors, multipack values, and dependency_provider. A selector must match ^[A-Za-z0-9_.+-]+$, must not be empty, must not start with -, and must not contain / or :.

See also #

  • Recipe format reference — the pekit.toml and package-file schemas.
  • Environments and keyrings — how env and keyring files are selected and layered.
  • Signing and provenance — signing.package_key and pekit.lock in the trust chain.

Command-line reference

pekit / Reference

This is the exhaustive reference for pekit's command line and the environment it exports to the commands it runs. Every flag listed here is a real flag accepted by the parser; there are no others. For a narrative walk-through of the grammar and recipe location, see Invocation and flags; for what each command does, see Commands and targets.

Synopsis #

pekit [global flags] <command> [args and flags]
pekit [global flags] workspace [workspace flags] <command> [args and flags]

<command> is exactly one of build, test, install, package, publish, clean, gen, verify, lock, or workspace. Flags may appear before or after the command word and are parsed identically on both sides — the sole exception is the workspace tail (see Workspace flags). A bare -- ends flag parsing; every following token is captured verbatim as a positional selector. Positional selectors may not begin with -.

There is no --help and no help command, and no version banner: -V is an alias for the --version selector, not a print-version flag.

Flag value forms #

The shape of a flag determines how its value may be written.

ShapeHow a value is written
No-valueNever takes a value. --flag=x errors with unexpected_flag_value.
Required-value--flag value or --flag=value. A missing next token, a next token that is -- or begins with -, or an empty --flag= all error with missing_flag_value.
Optional-valueValue only via --flag=value. The bare --flag is valid and means an empty value; it never consumes the following token.

Global flags #

Accepted by every command, before or after the command word. These are never subject to the capability matrix below.

FlagValue formSemantics
--recipe <path|dir>Required-valueUse this recipe instead of searching upward from the cwd. A directory means <dir>/pekit.toml.
--workspace <path|dir>Required-valueUse this workspace file/root. Valid only with the workspace command.
--allow-unusedNo-valueDowngrade "command does not support this recognised flag" from a fatal error to a suppressed notice.
--dry-runNo-valueRun the full planning pipeline but stop before any side effect; emit plan events instead.
--quietNo-valueHuman renderer, routine progress suppressed (only warning, artifact, publish, workspace_summary, lock, sign events).
--verboseNo-valueHuman renderer plus extra staging/progress events.
--jsonNo-valueEmit newline-delimited JSON events on stdout instead of the human log.

The renderer is chosen from these flags: --json selects the JSON renderer (with --dry-run it buffers and prints a single plan object); otherwise the human renderer runs, in quiet mode when --quiet is set.

Version, source, and build flags #

These are command-specific: each is only accepted by the commands marked in the capability matrix. Passing one to a command that does not support it is unsupported_flag unless --allow-unused is set.

FlagValue formMeaning
--version <v>, -V <v>Required-valueSelect a single recipe version. -V is an exact alias.
--latestNo-valueSelect only the newest available version.
--all-versionsNo-valueAct on every available version.
--local[=<path>]Optional-valueUse a local source tree instead of resolving source; bare form uses the default, =<path> overrides it.
--prefer-local[=<path>]Optional-valueUse the local source when present, otherwise resolve normally.
--no-build[=<list>]Optional-valueReuse already-staged build targets. Bare form reuses all; =a,b,c reuses only the named build targets. Only affects build-kind targets whose stage already exists.
--no-verify[=<list>]Optional-valueSkip the gen drift-check pre-flight. Bare form skips all; =a,b,c skips only the named gen targets (an unknown name errors). On package/publish it passes through to the builds they trigger.
--env <name>Required-valueSelect the environment file layer. main (default) loads env.pekit.toml; <name> loads <name>.env.pekit.toml; none loads no env file.
--keyring <name|path>Required-valueLoad a keyring file. A bare name resolves to <name>.keyring.pekit.toml searched in the workspace root then recipe root; a path-like value is used directly. Repeatable.
--keyring.<path>=<value>Dotted, = requiredSet a single keyring value inline. Requires a non-empty dotted path and the =; otherwise invalid_keyring_flag. Repeatable.
--refresh-sourceNo-valueForce source re-materialisation, ignoring any source cache. The lockfile is still enforced against the fresh download.
--allow-unanchoredNo-valuePermit publish from source with unanchored provenance — no checksum and no lock entry (otherwise unanchored_provenance).
--allow-unsignedNo-valuePermit publish of peipkg-format packages without a configured signing key (otherwise unsigned_publish).
--repinNo-valuelock only: re-download the selected version and replace its lock entry — the explicit accept-changed-upstream-bytes ceremony. Requires an exact single --version.
--allNo-valuepackage/publish: act on every package definition instead of a selector. gen/verify: act on every gen target.
--output-onlyNo-valueclean: remove managed output only, without running a clean target.
--target-onlyNo-valueclean: run the clean target only, without removing managed output.

--version/-V, --latest, and --all-versions form one mutually-exclusive group ("version selection"); --local and --prefer-local form the "local source flags" group.

Workspace flags #

workspace delegates to another command. Its tail is parsed specially: the two workspace-only flags must appear after workspace and before the delegated command word. Everything after the delegated command is parsed as an ordinary invocation of that command.

FlagValue formMeaning
--jobs <N>, --jobs=<N>Required-value, positive integerMaximum concurrent members. Default 1. A non-integer or value <= 0 errors with invalid_flag_value.
--fail-fastNo-valueAbort the whole workspace run on the first member failure.

Global flags (--dry-run, --json, --recipe, …) may also appear in the tail before the delegated command. A version/source/build flag placed there is an error (missing_workspace_command, "workspace command flag … must appear after the delegated command"): those belong after the delegated command. The same code fires when no delegated command follows at all. See Workspaces.

Capability matrix #

Which command accepts which flag group. Global flags and workspace flags are omitted (global flags are accepted everywhere). A blank cell means the flag group is unsupported for that command.

Commandversion selectionlocal source--no-build--no-verify--env--keyring--refresh-source--allow-unanchored--allow-unsigned--allclean mode--repin
buildYesYesYesYesYesYesYes
testYesYesYesYesYesYesYes
installYesYesYesYesYesYesYes
packageYesYesYesYesYesYesYesYes
publishYesYesYesYesYesYesYesYesYesYes
cleanYesYesYes
genYesYesYes
verifyYesYesYes
lockYesYesYes

"clean mode" is the --output-only / --target-only pair.

Validation and mutual-exclusion rules #

The parser rejects contradictory or malformed invocations before doing any work.

Rejected conditionDiagnostic
--quiet with --verbose--quiet and --verbose cannot be used together
--quiet with --json--quiet and --json cannot be used together
--recipe with --workspace--recipe and --workspace cannot be used together
--workspace without the workspace command--workspace can only be used with the workspace command
No command wordmissing_command
More than one of --version / --latest / --all-versionsmutually exclusive
--local with --prefer-localmutually exclusive
--output-only with --target-onlymutually exclusive
A recognised flag the effective command does not supportunsupported_flag (suppressed under --allow-unused)
clean --output-only with --env or --keyring"clean --output-only does not run a target, so … is unused" (suppressed under --allow-unused)
clean with more than one selectorinvalid_selector (clean accepts at most one)
package/publish with --all and a selector"--all cannot be combined with package selectors"
gen/verify with --all and a selector"--all cannot be combined with gen/verify target selectors"
lock with any selectorinvalid_selector (lock does not accept selectors)
lock --repin without an exact single --versioninvalid_flags (--repin requires exactly one exact --version)
--no-verify naming an unknown gen targetmissing_target
A positional selector beginning with -invalid_selector (use -- before selector-like values)

--allow-unused relaxes only the "recognised-but-unused" and "clean --output-only unused env/keyring" checks; it never excuses unknown flags, malformed values, the mutual-exclusion rules, or bad selectors.

Exit codes #

CodeMeaning
0Success.
1Any error — bad invocation, missing recipe, failed target, and so on.

There are no other exit codes.

Environment contract #

Before running a target's command, pekit constructs its environment in layers: managed PEKIT_* variables, then keyring exports, then user-declared env (workspace → source → recipe → env-file). The process inherits the parent environment with these overlaid. User env may not define any PEKIT_* variable — doing so is reserved_env. The command runs with its working directory set to the source root (or the recipe root for a clean target).

Always set #

Exported for every target pekit runs.

VariableValue
PEKIT_RECIPE_ROOTDirectory containing the resolved recipe (pekit.toml).
PEKIT_SOURCE_ROOTRoot of the materialised source tree. Equals the recipe root for a non-delegated recipe.
PEKIT_LITERAL_ROOTRoot used to resolve @source: payload references.
PEKIT_ROOTPer-source pekit work base (staging lives beneath it).
PEKIT_OUT_BASEBase directory for this source's managed output.
PEKIT_OUTThis target's own stage directory (where it should write).
PEKIT_COMMANDThe command kind running the target (build, test, install, clean, gen, verify).
PEKIT_TARGETThe target's name.
PEKIT_BUILD_TIMESTAMPRun start time, Unix seconds.
PEKIT_SOURCE_TIMESTAMPSource provenance time, Unix seconds.
PEKIT_WORKSPACE_ROOTWorkspace root, or an empty string when not in a workspace (always set).
PEKIT_DEPENDENCIES_FILEPath to the JSON dependency payload pekit writes for this target.
PEKIT_DEPENDENCY_PROVIDERSelected env dependency provider (empty when none).
PEKIT_DEPENDENCIESResolved dependencies as name constraint lines, one per line (empty when none).

Set only when a version is selected #

Exported only when the run resolves a non-empty version.

VariableValue
PEKIT_VERSIONFull version string.
PEKIT_VERSION_MAJORMajor component.
PEKIT_VERSION_MINORMinor component.
PEKIT_VERSION_PATCHPatch component.
PEKIT_VERSION_PRERELEASEPre-release component (may be empty).
PEKIT_VERSION_BUILDMETABuild-metadata component (may be empty).

Per-dependency and per-keyring variables #

Variable patternWhen setValue
PEKIT_<NEED>_OUTOne per entry in the target's needsStage directory of that needed build target. <NEED> is the target name upper-cased with - and . replaced by _. Two needs mapping to the same name is env_collision.
PEKIT_KEYRING_<PATH>One per keyring leaf loaded via --keyring / --keyring.<path>=<value>The keyring value. <PATH> is the dotted keyring path upper-cased, with each run of non-alphanumeric characters collapsed to a single _ and trailing _ trimmed. For example --keyring.token=… exports PEKIT_KEYRING_TOKEN, and a nested [registry] token = … exports PEKIT_KEYRING_REGISTRY_TOKEN.

A keyring export that collides with a managed PEKIT_* variable or with a normal env variable is env_collision.

See also #

  • Invocation and flags — the narrative walk-through of the grammar.
  • Commands and targets — what each command selects and runs.
  • Environments and keyrings — the layers behind the environment contract.

Peios Learn — documentation for the Peios project.

Built with Trail.