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

Package management

Single-page view · as markdown

Package management

Peios / Using Peios / Package management

Use peipkg to install, update, remove and inspect package files. Use feat when you need to set up or turn on a feature supplied by a package. Both run as you, and neither gives you additional authority.

Where to start #

What you needStart here
Find, install or remove softwareInstalling and removing packages
Apply updates or reverse a committed changeKeeping a system current
Add or repair a package sourceRepositories and trust
Understand an unexpected plan or a separate approvalDependency resolution
Check files or identify their ownerInspecting and verifying
Recover after a failed or interrupted operationTransactions and recovery
Choose which package supplies a shared nameClaims
Work on an offline installation or initramfsNamed roots
Turn a feature on or remove its setupUsing feat
Assemble a new image's package treeComposing a root

For desktop work, open Package Manager from the launcher by typing package, or Feature Manager by typing feature.

What a package is #

A .peipkg file carries a package's identity, version, architecture, dependencies and payload. peipkg records the installed package and the files it owns, so it can inspect, replace, remove and verify those files.

Installing a package makes its files available. A feature performs additional setup through scripts you explicitly run, such as registering or enabling a service. Package installation does not run those scripts. Remove a feature's setup before removing the package that supplies it.

A package can also provide a claim, a shared filesystem name with one holder. That chooses a provider; it is not the same operation as enabling a feature. Archive and manifest internals belong in the technical reference.

Where packages come from #

Repositories supply package files and signed metadata. peipkg refresh updates the cached metadata used to plan installs and updates. Before adding a repository, obtain its signing-key fingerprint through a trusted channel; configuration alone is not trust.

A local .peipkg file can also be installed. Its format and payload hashes are checked, but there is no repository trust chain authenticating it. You are vouching for that file.

peipkg has no authority of its own #

peipkg is an ordinary program running under your token. It has no privileged daemon, setuid identity, service account or broker. The system checks your rights against the security descriptors on the paths it changes and the state it reads or writes.

As shipped, package administration is for Administrators. Granting write access to package destinations is broader than permission to install one approved package: peipkg cannot provide that narrower permission for you. A package's requested security descriptor cannot give its installer authority the installer did not already have. See the privilege model for the boundary.

Every change is a transaction #

peipkg shows a plan before applying a change. Review every dependency, removal, source, version and target root, then read the final outcome and warnings. --yes skips only the routine approval; it does not approve the separate questions for elevated actions or modified configuration files.

Use undo or downgrade for a completed change you want to reverse, and recover for a pending interrupted transaction. They do not restore feature setup, registry state or user data. The technical reference documents rollback failures and recovery limits, so do not treat an error or a rolled-back history entry as proof that every file was restored. Verify after recovery.

Every operation is audited #

Package operations and deliberate authorisations produce semantic events in the audit stream. There are documented exceptions: automatic recovery emits no peipkg event, and event emission can fail without stopping a package change. The audit reference describes these limits. The kernel's audit of the actual file operations is the security record; peipkg's events are a useful summary.

The command surface #

Every command is invoked as peipkg <command> [arguments].

CommandDoes
installInstall packages, with dependencies, from a repository or a local .peipkg file.
remove (alias uninstall)Remove installed packages.
upgradeUpdate installed packages under the available sources and constraints.
downgradeMove one package to a specific older version.
undoReverse the most recent committed transaction as a new change.
claimShow or change which package holds a claim — a shared name that exactly one package may own.
refreshUpdate the cached metadata of the configured repositories.
repoConfigure repositories — add, list, remove.
rootManage named roots — add, list, remove, show.
listList the installed packages.
infoShow one installed package's details.
filesList the files a package owns.
ownsReport which package owns a given path.
searchSearch the configured repositories for a package.
verifyCheck installed files against what was recorded at install.
historyShow the transaction log.
recoverRecover pending work; cross-root recovery may roll forward.
cleanDelete unused metadata-cache data; see the documented cache-scope limits.

One global option sits before the command: --root TARGET makes peipkg operate on a Peios installation other than the running system at /. TARGET can be a literal path — a Peios installation mounted at DIR, the form used by image builders and offline maintenance — or the name of a named root. Named roots are more than a convenience: they are how peipkg keeps components such as the initramfs current. See Named roots for the detail.

The producer side #

Building and signing packages is pekit's job; publishing repositories is peipkg-repo's. See the Producing packages reference for that work. peipkg-compose is a separate consumer-side binary for assembling a fresh root, not a peipkg subcommand or a live-system updater.

Package Manager

Peios / Using Peios / Package management

Package Manager is the desktop's window on peipkg. Open it from the launcher (type package). It has no authority of its own: it runs peipkg as you, so what you may install or remove is exactly what you could with the command, and every change is the same transaction, with the same history and audit record.

Sections #

SectionShowsDoesThe command
InstalledEvery installed package, searchable, with its version and where it came fromOpens one: its details, what it needs and provides, and its files. Check Files, Update to…, Remove…list, info, files, verify, upgrade <name>, remove
AvailableWhat the repositories offer that isn't installedInstall, and Install from File… for a .peipkg filesearch, install
UpdatesThe newer versions waiting, after refreshing the trusted repositoriesUpdate one, or Update Allrefresh, upgrade --dry-run, upgrade
HistoryEvery change, newest first, by what it didUndo… the most recent, and Resolve an interrupted onehistory, undo, recover
RepositoriesEach repository, its address, how many packages it offers, when it was refreshed, and whether it is out of date or not yet trustedRefresh All, Add Repository…, Trust…, Remove…repo list, refresh, repo add, repo remove
RolesEach claim, what holds it, and its linksChoosing another package moves the claim to itclaim
Installation RootsThe named rootsNothing: roots are added with peipkg rootroot list

Making a change #

Before removing a package that supplies a configured feature, inspect the feature in Feature Manager and use its Remove… action while the definition and scripts are still installed. Check that the feature reaches Not Installed, then return to Package Manager and review the package-removal plan below.

If teardown fails or is interrupted, keep the package installed, retain the script output and follow Recover an interrupted feature. If the feature's definition is already missing, follow When its package is removed first.

Every change goes the same way, on one page:

  1. The plan. What will be installed, updated, moved back or removed, with sizes, before anything happens.
  2. Each elevated action, on its own. A downgrade, a low-trust package filling a high-trust role, or a package taking over another's files needs your specific approval. Each is asked with Allow This, and Cancel is the keyboard's. Your approval is recorded in the audit stream, as it is at a terminal.
  3. The plan's own question: Install, Update, Remove or Undo. For a removal, Cancel is the keyboard's.
  4. Progress, through downloading and checking, preparing, putting the changes in place and recording them.
  5. The outcome, with anything peipkg warned of under Notes.

A removal can stop partway to ask about a configuration file you changed since it was installed: Keep It leaves it in place, belonging to no package; Delete It removes it and keeps a copy beside it; Stop Everything cancels the removal.

Once you approve the plan, the change finishes even if you close the window.

When peipkg refuses #

Some refusals have a way on, which Package Manager offers once:

  • A repository's information is out of date, and refreshing it brought nothing newer. Continue Anyway carries on with what this machine already has, as --allow-stale does, and is recorded. Package Manager remembers it for its update checks until you close it.
  • A file is in the way: one the change would install is already there and belongs to no package. For an install, Replace It authorises replacement and keeps the displaced file, as --overwrite-unowned does. Other commands have no overwrite option: move the conflicting file aside, then retry the operation.
  • This is updated another way: the package's publisher says it is updated by other means. Update Anyway is --bypass-alternate-upgrade.
  • A repository isn't trusted yet: it is set up on this machine, as an image sets up its own, but its trust has never been confirmed. Repositories takes you to it, where Trust… shows the signing key given for it and runs the trust ceremony when you confirm, as peipkg repo add <name> does.

Package Manager never confirms a repository's trust for you, including when it refreshes repositories to look for updates: it refreshes only those already trusted.

What you may do #

Package Manager asks the system rather than guessing. If you may not read peipkg's records, every section says so once and shows nothing more; as shipped, only Administrators may. If you may read them but not change them, everything is shown and nothing can be changed, with the reason said once under each section's heading.

Where to go next #

  • Installing and removing packages
  • Keeping a system current
  • Repositories and trust
  • The driven mode, for a program of your own that runs peipkg the same way

Installing and removing packages

Peios / Using Peios / Package management

Use peipkg for package files and feat for the setup a package's features perform. Installing a feature's package makes its scripts available; it does not turn the feature on. On the desktop, use Package Manager.

Before changing software #

  • Check that you are working on the intended root. The default is /; a package's default_root can redirect an install when you have not specified --root.
  • You need access to peipkg's records and authority to write the affected paths. peipkg runs as you and grants no additional rights.
  • Use a repository whose signing-key anchor you have verified through a trusted channel. See Repositories and trust.
  • Keep an independent copy of important local changes before replacing or removing files. Transaction recovery and version undo have documented limits.

Install and check a package #

For example, to install GNU Grep from a configured, trusted repository:

peipkg refresh
peipkg search grep
peipkg install org.gnu.grep --dry-run
peipkg install org.gnu.grep
peipkg info org.gnu.grep
peipkg verify org.gnu.grep

Review the source, versions, dependencies, removals, claims and target roots in the plan before approving. A dry run does not reserve its inputs: review the plan again when applying, especially after refreshing metadata or another package change. info shows the installed version and origin; verify checks files, not whether the application is running or configured.

If the operation fails or is interrupted, use the recovery procedure before treating the system as restored.

Installing packages #

peipkg install <package|file.peipkg>...

Each argument is either the name of a package to fetch from a configured repository, or the path of a local .peipkg file (recognised by its .peipkg suffix). You can mix the two in one command.

Canonical package names #

Use the complete canonical name returned by peipkg search. Current catalogue commands do not resolve a short final component such as grep to org.gnu.grep. Virtual capabilities remain unqualified and are a separate dependency mechanism.

Canonical names use reverse-DNS namespaces, for example org.gnu.bash, com.amd.amd-ucode and org.peios.peinit. Third-party software retains its upstream namespace; Peios-owned software uses org.peios. A name identifies the software, not who signed or endorsed the package. Check its repository origin separately.

The catalogue package-family reference retains the full name list, split-library and development packages, included commands, unsupported components, and build/trust qualifications.

Installing a package rarely means installing just that package. peipkg works out everything the request implies — the dependencies the package needs, and the dependencies of those in turn — and presents the whole set. How that set is computed is the subject of Dependency resolution; this page is about the flow around it.

The plan-and-confirm flow #

install, remove, and the commands on Keeping a system current all work the same way. peipkg first produces a plan — the ordered list of changes that satisfy your request — and prints it:

$ peipkg install nginx
the following changes will be made:
  install    pcre2 10.44
  install    zlib 1.3.1
  install    nginx 1.27.4
proceed? [y/N]

For repository packages, presenting this plan does not download or install their payloads. peipkg waits for an answer. Anything other than y or yes — including pressing Enter, or end-of-input — is a refusal, and the package plan is not applied. This is the package command's outcome; a wrapper such as plain upgrade-peios can still continue with its own registry-seed work.

Answer y and peipkg carries the plan out as a single transaction: it downloads and verifies every package, then applies the change. Read the outcome and any warnings; after an interruption, follow the recovery procedure.

OptionEffect
--dry-runProduce and print the plan, then stop before its approval questions and package application. Planning can still refresh repository metadata for trust-freshness checks.
--yes, -ySkip the proceed? prompt and apply the plan.
--no-claimInstall a provider without taking any claim it offers.
--allow-staleProceed although a repository's trust state exceeds its maximum trusted age. See Repositories and trust.
--claim <names>Comma-separated claims to force-claim, overriding the current holder(s).
--claim-allForce-claim every claim the installed packages provide, overriding incumbents.
--dangerously-bypass-path-restrictionsPermit packages that declare special_system_package to install outside the payload layout rules. Exempts nothing that has not declared itself special. The technical sources disagree on whether /lcl/policy remains protected under the bypass; do not rely on that exclusion (see the warning below). Needed only for the handful of packages whose job is to lay down the filesystem structure those rules protect.

--dry-run previews the package plan without applying it. In peipkg 8b588ae8, trust-freshness checks precede planning, and the dry-run return is before plan authorisation and execution. Do not treat it as a guarantee that cached metadata or all filesystem state is untouched. Check the source scope against the version in your image.

--yes is for scripts and unattended runs — but note that it skips only the routine prompt. A plan that contains an action needing deliberate authorisation will still stop and ask; --yes does not override that. See Elevated authorisation for which actions those are and why.

Warning

The installation validation reference says the special-package bypass skips the destination check entirely, including /lcl/policy; earlier operator guidance said that path was excluded. This documentation does not establish a narrower boundary. Only grant the bypass after reviewing the package's destinations and the authority of the account running it.

--claim-all cannot be combined with --claim or --no-claim. Claims — shared names exactly one package may hold — are covered in Claims.

An install can also target a root other than the current one — either explicitly with --root, or because a package declares its own default root. See Named roots for how roots are named and nested.

Installing from a local file #

When an argument is a path ending in .peipkg, peipkg installs that file directly:

$ peipkg install ./nginx-1.27.4.peipkg

This is a raw install, and it differs from a repository install in one specific way: it skips the repository trust layer. There is no signed index to check the file against, no signing key to verify it under, and none of the freshness or rollback protection a repository provides. You are vouching for the file yourself.

Everything else still happens. The package format is fully verified: the archive structure, the manifest, the integrity manifest, and the hash of every payload file are all checked before anything is staged. A corrupt or truncated .peipkg is rejected in the same way as one from a repository. The file's dependencies still resolve normally against your configured repositories — a locally-installed package can pull in repository packages to satisfy what it needs.

A package supplied as an explicit local file takes precedence over any repository's version of the same package, so install ./foo.peipkg installs that file even if a repository offers foo too. In the plan, a local-file operation is marked so the choice is visible:

  install    nginx 1.27.4  (local file)

There is currently no policy gate that refuses raw installs as a class. Format and hash checks establish integrity, not the file's publisher or trustworthiness.

Removing packages #

peipkg remove <package>...
peipkg uninstall <package>...

remove and uninstall are the same command. Each argument names an installed package; peipkg plans the removal — the files to take off disk — and runs the plan-and-confirm flow described above.

Before removing a package that supplies a feature, inspect and remove that feature with feat first. Removing the package does not undo the feature's setup and takes away the scripts needed to undo it. If that already happened, restore the feature definition before removing the feature.

Inspect the package and preview the removal:

peipkg info <package>
peipkg files <package>
peipkg remove <package> --dry-run
peipkg remove <package>
peipkg list
peipkg history

A removal releases the package's files and claims. Shared or non-empty directories remain; empty directories owned by no remaining package can be removed. If the package held a claim, no alternative is promoted automatically.

Keep or remove local changes #

For a modified configuration file under /usr/etc/ or the legacy /etc/, peipkg asks whether to remove it, keep it, or abort:

  • Remove: the prior content is kept at a sibling backup path.
  • Keep: the file stays but becomes unowned.
  • Abort: the removal stops. End-of-input also aborts.

--yes does not answer this per-file question. Binaries, libraries and data are not hashed at uninstall, so a hand-patched binary can be removed without a question. Use verify and preserve anything you need before approving. The uninstall reference describes these rules and database-integrity warnings.

Removing something that is depended on #

peipkg will not, by default, leave the system inconsistent. If you ask to remove a package that another installed package depends on, the plan is refused: peipkg tells you what still needs it, and stops.

OptionEffect
--cascadeAlso remove every installed package that depends on the ones named.
--dry-runPrint the plan and stop.
--yes, -ySkip the proceed? prompt.

--cascade turns that refusal into a wider plan: peipkg computes the full set of packages that would be left with a broken dependency and adds them to the removal. The plan then shows everything that will be removed. Review it before approving, because a cascade can reach further than expected. There is no implemented system-critical-package guard: even removing the package manager is an ordinary removal. Do not use a cascade as a shortcut around a dependency you have not identified.

$ peipkg remove --cascade libfoo
the following changes will be made:
  remove     toolA 2.1
  remove     toolB 1.0
  remove     libfoo 3.3
proceed? [y/N]

Exit status #

CodeMeaning
0The operation succeeded — or, for --dry-run, the plan was produced. A declined prompt is also 0: nothing failed.
1The operation failed — a package was not found, a dependency could not be satisfied, a download or verification failed, or a file operation was denied.
2A usage error — an unknown command or a malformed option.

Keeping a system current

Peios / Using Peios / Package management

For routine maintenance, refresh, review the upgrade plan, apply it, then check the outcome. First choose the intended root and preserve important local changes. upgrade can also update nested roots, so review the whole plan and check every participating root afterwards.

peipkg refresh
peipkg upgrade --dry-run
peipkg upgrade
peipkg history
peipkg verify

Read refresh failures before proceeding: a dry run and the final invocation can use different inputs if metadata or installed packages changed in between. After the change, review verification differences and configuration-file warnings, and check the affected software separately. A file check does not establish that a service or feature is working. For kernel and service updates, follow Check what an update has activated to distinguish installed payloads, boot artifacts and running code.

Your situationCommand
Apply available updatesupgrade, after refresh and plan review
Reverse the most recent committed transactionundo, after checking history and its dry run
Restore one package to a particular older versiondowngrade <package> <version>
An operation was interrupted and is pendingrecover, then verify

Version reversal changes package payloads. It does not reverse registry state, runtime or user data, or setup performed by feature scripts.

For the Peios edition itself, use Upgrading Peios. Its plain CLI also reconciles release seeds after a no-op or declined package request; peipkg's exit 0 is not a cancellation signal to that wrapper. Use upgrade-peios --check to preview without staging or applying seeds.

Refreshing repository metadata #

peipkg refresh [repository]...

peipkg plans against a local, verified copy of each repository's metadata. That copy does not update itself. peipkg refresh is what updates it: for every configured repository — or just the ones you name — peipkg fetches the current signed descriptor and index, verifies them, and replaces the cached copy.

$ peipkg refresh
refreshed "official"
refreshed "internal"

Refresh has two properties worth knowing:

  • Repositories are independent. If one repository is unreachable or fails verification, peipkg reports it and carries on with the rest. One bad repository never blocks a refresh of the others.
  • A failed refresh changes nothing. If a repository cannot be refreshed, peipkg keeps the metadata it already had. It never falls back to unverified or stale-but-unchecked content. The worst a failed refresh does is leave you on yesterday's view.

What "verified" means here — signing keys, freshness floors, the handling of an unsigned repository — is the subject of Repositories and trust.

Run refresh before an upgrade. An upgrade plans against cached metadata, so without a recent refresh it cannot consider newer releases that the cache does not contain. The selected version still depends on the request, source preferences and constraints.

Skip it for long enough and peipkg stops waiting for you: an install, upgrade, or downgrade against a repository whose trust state has passed its maximum trusted age (30 days by default) refreshes that repository itself, and refuses to proceed against one that stays stale — unreachable, or frozen on an unchanging index — unless you pass --allow-stale. The bound and its configuration are covered in Repositories and trust.

Upgrading #

peipkg upgrade [package]...

With no arguments, upgrade considers every installed package in the current root and its reachable named roots for a newer version that satisfies resolution. With package names, it considers those packages in each reachable root where they are installed, including any new dependencies they need. --no-recurse confines resolution and execution to the current root.

$ peipkg refresh && peipkg upgrade
the following changes will be made:
  upgrade    zlib 1.3.1 -> 1.3.2
  upgrade    nginx 1.27.4 -> 1.27.5
proceed? [y/N]

upgrade uses the same plan-and-confirm flow as install — peipkg shows the full set of changes and waits for approval — and the same options:

OptionEffect
--dry-runPrint the plan and stop. A good way to preview what an upgrade would move.
--yes, -ySkip the proceed? prompt.
--no-recurseConfine the upgrade to the current root only — disable the cascade into nested named roots.
--allow-staleProceed although a repository's trust state exceeds its maximum trusted age. Warned and audited.

By default, upgrade resolves one combined plan over the current root and its reachable named roots. One approval covers that plan. If it changes more than one root, peipkg coordinates the participants as a cross-root transaction, not independent per-root upgrades. Preparation failure attempts rollback; a later commit failure can leave some roots committed and others pending. Read the named-root transaction boundaries and cross-root recovery before treating a failed operation as unchanged. This describes peipkg 8b588ae8's upgrade entry and single approval/executor selection; check the revision supplied by your image.

If the plan contains no updates, peipkg says so and exits. That is a result against the available metadata and constraints, not proof that every upstream release is installed.

Downgrading #

peipkg downgrade <package> <version>

downgrade moves one package to a specific older version. You name the package and the exact version you want:

$ peipkg downgrade nginx 1.27.4

Older versions are not in a repository's active index — that index lists current versions only. They live in its archive index, which peipkg fetches on demand when you ask for a version that is not current. The package must still exist in some configured repository's archive for the downgrade to be possible.

A downgrade is treated as a deliberate move. Going backward — onto a version that may have known issues a newer one fixed — is one of the actions peipkg flags for explicit authorisation: beyond the routine proceed? prompt, peipkg asks you to authorise the specific downgrade, and --yes does not stand in for that answer. See Elevated authorisation for the full set of actions that work this way.

downgrade accepts --dry-run, --yes, -y, and --allow-stale with the same meaning as elsewhere.

Undoing the last change #

peipkg undo

Before applying undo, identify the most recent committed transaction:

peipkg history
peipkg undo --dry-run

undo reverses the most recent committed transaction. If that transaction installed a package, undo removes it; if it upgraded, downgraded, or removed packages, undo restores each one to the version it had before.

$ peipkg undo
undoing transaction 47 (upgrade nginx, zlib)
the following changes will be made:
  downgrade  nginx 1.27.5 -> 1.27.4
  downgrade  zlib 1.3.2 -> 1.3.1
proceed? [y/N]

Check that the old packages are available before relying on undo. The technical reference describes archive-based resolution requiring a reachable repository or usable cache; it does not promise retained transaction backups. Even where undo is exempt from freshness gating, that does not guarantee it can fetch an old version offline.

Be precise about what undo is. It is not a rollback of committed state — the previous transaction happened and stays in the history. undo computes the inverse of that transaction and applies it as a new transaction of its own. The history grows; it does not rewind. That new transaction can itself be undone, and so on.

Because restoring an older version is a backward move, undo carries the same explicit-authorisation requirement as downgrade for any package it walks back. It accepts --dry-run and --yes, -y.

To undo something other than the most recent transaction, or to see the history undo works against, use peipkg history — and revert a specific package directly with downgrade.

The routine cycle #

For day-to-day maintenance the loop is:

$ peipkg refresh        # learn what is available
$ peipkg upgrade --dry-run   # preview the move
$ peipkg upgrade        # apply it

downgrade and undo are the recovery commands — use them when an upgrade has brought in a change you want to remove. They apply a new package transaction. Read the recovery limits before relying on another reversal, and verify after a failed or interrupted attempt.

Exit status #

CodeMeaning
0The operation succeeded — including a dry run, a plan with nothing to do, and a declined prompt or authorisation (nothing failed).
1The operation failed — a repository could not be refreshed, a repository's trust state exceeded its maximum age and a forced refresh could not clear it, resolution or a download or verification failed, cross-root preparation, commit or recovery failed, there is no committed transaction to undo, or a command's arguments were wrong (downgrade without a package and version, an unparsable version, a malformed option).
2A usage error before any command ran — no command, an unknown command, or a malformed global option (including a --root reference that does not resolve).

Repositories and trust

Peios / Using Peios / Package management

Before adding a repository, obtain its base URL and signing-key fingerprint through a channel you trust. The fingerprint is your trust decision; peipkg cannot tell you whether the publisher deserves that trust.

Choose the repository task #

TaskCommand or check
See configured sourcespeipkg repo list
Inspect trust and freshnesspeipkg repo list --json
Add a new sourcepeipkg repo add <name> <base-url> --anchor <fingerprint>
Trust configuration already supplied by an imagepeipkg repo add <name> after checking that configuration
Fetch current metadatapeipkg refresh <name>
Stop using a sourcepeipkg repo remove <name>; installed packages remain

After an add or refresh, check the result and preview the package operation that will use it. Check package origin in the plan; a trusted signature authenticates a publisher's content, not its safety or suitability.

When a repository blocks work #

  • Not trusted: inspect the configured URL and anchors, then deliberately run repo add <name> if they are the source you intend to trust.
  • Signature or anchor mismatch: verify the URL and fingerprint through the publisher's trusted channel. Do not switch to unsigned mode to make the error disappear.
  • Unreachable or stale: retry refresh when the source is reachable. Only use --allow-stale when you deliberately accept older metadata; it is warned and audited.
  • Unusable cached metadata: the cache reference says to refresh or remove the repository from configuration. Another failure chapter describes warning and continuing without it. Because the sources disagree, inspect the resulting plan's repositories instead of assuming the same candidate set was used. See The index cache and An unreachable repository.

Removing a repository is a deliberate source change, not a neutral retry: it removes trust state and the recorded freshness floor as well as the configuration. Do not remove and re-add it merely to bypass a rollback warning.

What a repository serves #

At its base URL a repository serves three signed documents:

  • A descriptor — the repository's identity: the set of signing keys it uses, each with a status. This is the root of trust for everything else.
  • An active index — the catalog of current package versions: names, versions, dependencies, hashes, and download locations.
  • An archive index — the catalog of older versions, kept so a downgrade can reach them. peipkg fetches it only on demand.

The descriptor and the indexes each carry a detached signature. peipkg verifies all of them; an unverifiable document is discarded, never used.

Adding a repository #

peipkg repo add <name> <base-url> --anchor <fingerprint>

repo add does two things: it records the repository in your configuration, and it runs the trust ceremony that anchors it.

$ peipkg repo add official https://pkgs.peios.org \
    --anchor ef86709c4b1d8a02e5f3c719d640aa8b7c2e9105f8d3b6470a1c2e9d8b5f3a04
added repository "official"

The --anchor value is a signing-key fingerprint, and it is the part of the command that establishes trust. peipkg cannot tell you whether a repository is genuine; only you can, by obtaining its fingerprint through a channel you trust (the project's website over HTTPS, a colleague, a printed reference) and supplying it here. The anchor is your out-of-band statement that the key is trusted.

Given an anchor, the ceremony runs:

  1. peipkg fetches the repository's descriptor and its signing keys.
  2. It checks the fetched keys against the anchors you supplied — a fingerprint must match bit for bit.
  3. Only if the descriptor's signature verifies against an anchored key does peipkg accept it and record the trust state.

If the fetched descriptor does not verify against any anchor, repo add fails and leaves nothing behind — no half-added repository, no configuration file. You can supply --anchor more than once to trust several keys at first contact.

OptionDefaultEffect
--anchor FINGERPRINT—A signing-key fingerprint to trust. Repeatable.
--priority N50Resolution priority — a lower number wins. See below.
--policy required|optionalrequiredWhether a valid signature is required. See Signature policy.
--min-index-version N0A freshness floor for the index. See Freshness.
--max-trusted-age-days N30How stale the repository's trust state may grow before operations force a refresh. See Freshness.
--max-index-staleness-days N90How old the index's own generated_at may be before operations force a refresh. See Freshness.
--insecureoffPermit a plain http:// base URL.

--insecure exists for a repository on a trusted local network with no TLS. It lowers transport confidentiality and integrity; the package signatures still protect authenticity, but prefer https:// whenever it is available.

Adding a repository your system already carries #

An image can ship a repository's configuration — its base URL, its policy, and its trust anchors — as a .repo file in /conf/peipkg/ (stored at /lcl/conf/peipkg/, which is what that view exposes). A Peios installation medium does exactly that for the offline repository it carries.

Configuration alone is not trust. peipkg does not trust a configured repository on sight: until the ceremony has run there is no recorded trust state, and peipkg neither uses the repository nor carries on without it. An install, upgrade or removal is refused, naming the repository, until you add it or remove it from the configuration: going on with the others would let a lower-priority repository's package quietly take the place of one this repository was meant to supply. The decision to trust a key is yours, never a default.

What a baked-in .repo file changes is only where the anchor comes from. It arrived with the image rather than being typed at the prompt, which is still out-of-band — it did not come from the repository it authenticates. So the ceremony can run against it without you retyping a 64-character fingerprint that is already on disk:

$ peipkg repo add peios-medium
added repository "peios-medium"

Given a name and nothing else, repo add reads that repository's existing configuration and runs the same ceremony as the two-argument form. It is still an explicit act — nothing here happens on its own.

Two differences from the full form are worth knowing:

  • It refuses a configuration with no trust_anchors under the required policy, because there would be nothing to verify the descriptor against.
  • A failed ceremony leaves the .repo file alone. The two-argument form wrote that file and removes it again on failure; here it came from somewhere else, and deleting another party's configuration because a ceremony failed would turn a retryable problem — a medium not mounted yet, a repository not published — into lost settings.

How trust survives key rotation #

A repository operator will, over time, rotate signing keys — retiring an old one, bringing a new one into use. If trust were pinned permanently to the fingerprint you first anchored, every rotation would break every consumer.

Rotation does not break trust, because the descriptor carries the keys. Each key in the descriptor has a status:

StatusMeaning
activeIn current use for signing.
transitioningBeing phased in or out — honoured until its stated expiry.
revokedWithdrawn — never honoured again.

Each time peipkg refreshes a repository, it verifies the new descriptor against the keys in the descriptor it already trusts, and then adopts the new descriptor's key set as the current trust state. A rotation that is itself signed by a still-trusted key propagates automatically: you anchored once, and the chain carries forward from there without you re-anchoring. A revoked key is dropped and never accepted again.

The anchor therefore matters only at first contact. After that, trust is a chain, and each refresh extends it.

Signature policy #

--policy chooses how strict peipkg is about signatures for this repository.

  • required (the default) — every descriptor and index must carry a valid signature from a trusted key. An unverifiable document is rejected. This is the correct setting for any repository reached over a network.
  • optional — combined with no trust anchors, this puts the repository into unsigned mode: peipkg fetches and uses its metadata and packages without cryptographic verification.

Unsigned mode is a deliberate escape hatch for a scratch repository on a build host, or a local mirror you fully control. Every operation that touches an unsigned repository prints a warning:

peipkg: warning: repository "scratch" is unsigned — its metadata and
packages are not cryptographically verified

In unsigned mode, only the transport stands between you and a substituted package. Do not use it for anything reachable from an untrusted network.

Priority #

--priority is a number: lower is stronger, and the default is 50 for all repositories, including the official one. Set it deliberately when you configure multiple sources. It is a selection preference and a comparison used by trust guards; it is not a restriction on what a repository can publish.

The detailed candidate-selection rules place repository priority before version in their ordered preferences; other operator descriptions previously presented version first. Review the actual plan rather than assuming either the newest build or one particular source must win.

A lower-priority repository using replaces to displace a package from a higher-priority one needs explicit authorisation. The resolver also guards certain low-trust virtual providers. See Elevated authorisation for the separate questions and their limits.

Freshness — defeating a stale-repository attack #

A signature proves a document is authentic; it does not prove the document is current. An attacker who cannot forge a signature can still serve you a genuine, correctly-signed, but old index — one from before a security fix was published — and a naive consumer would accept it.

peipkg closes that gap by tracking, per repository, the highest index version it has ever seen, and refusing any index that goes backward. Once you have seen version 5 of an index, version 4 — however well signed — is rejected. A repository can only ever move forward.

--min-index-version N lets you set that floor explicitly, out of band: if you know the current index is at least version N, anchoring that number means peipkg will reject anything older even on the very first fetch, before it has a history to compare against.

Going backward is one half of the attack; standing still is the other. A frozen repository — one that serves the same, correctly-signed index forever — never trips the rollback check, yet it can hold you on a view from before a security fix indefinitely. Against that, peipkg tracks when each repository last refreshed with progress and enforces a maximum trusted age: 30 days by default, tunable per repository with max_trusted_age_days in its .repo file or --max-trusted-age-days at add time.

When an install, upgrade, or downgrade finds a repository's trust state older than its maximum, peipkg refreshes that repository first. If it cannot — the repository is unreachable, or it refreshes without progressing — the operation is refused rather than planned against outdated metadata. Passing --allow-stale to the operation overrides the refusal; the override is warned about and recorded in the audit stream. A maximum above 180 days draws a warning on every operation, because a bound that loose effectively disables the check.

The operator interface also documents a maximum index staleness measured from the index's generated_at: 90 days by default, configured with max_index_staleness_days in the .repo file or --max-index-staleness-days at add time. This is intended to catch a publisher advancing the version while retaining an ancient generation timestamp.

Warning

The refresh reference says this additional index-age check is not implemented, in conflict with the documented option here. This guide preserves the option and its stated limits, but does not establish which behaviour your build implements. Inspect index_generated and the publisher's current metadata rather than relying on that bound alone.

The two documented settings are independent: raising one does not widen the other — a max_trusted_age_days of 180 still leaves the 90-day staleness window in place. The operator interface describes a forced refresh for an index past the window, the same warned and audited --allow-stale override, and a warning on every operation when the staleness bound is above 365 days. The implementation conflict above applies to those expectations too.

Listing and removing repositories #

peipkg repo list
peipkg repo remove <name>

repo list prints the configured repositories — name, base URL, priority, signature policy. With --json it also gives each repository's trust state: name, base_url, priority, signature_policy, trust_anchors, allow_insecure_transport, trusted (whether the trust ceremony has run), last_refresh and index_generated as RFC 3339 times, stale (whether either is past its maximum age), and packages, the number its cached index offers.

$ peipkg repo list
official  https://pkgs.peios.org  priority=10  required
internal  https://pkg.corp.example  priority=20  required

repo remove deletes a repository's configuration and the trust state peipkg recorded for it. Packages already installed from that repository stay installed — removing a repository controls where future packages come from; it does not remove past ones. Check their origin with list and info, and review any future source substitution. The cross-repository guard reference describes packages whose origin has disappeared as orphans, including named upgrade refusal when no configured source offers them. Do not infer that removing a source also removed or replaced its packages.

Where the configuration lives #

Each repository is one file: /lcl/conf/peipkg/<name>.repo, in flat TOML.

base_url         = "https://pkgs.peios.org"
priority         = 10
signature_policy = "required"
trust_anchors    = ["ef86709c4b1d8a02e5f3c719d640aa8b7c2e9105f8d3b6470a1c2e9d8b5f3a04"]

The files are hand-editable, and editing one is a legitimate way to configure a repository — a trust anchor written into the file is you supplying that anchor out of band. peipkg repo add is the convenient front end: it runs the ceremony and writes the file for you. Who may edit these files is, like everything else on Peios, the security descriptor on /lcl/conf/peipkg/.

A repository's allow_sd_overrides setting defaults to false. The configuration reference documents refusal of packages declaring file security descriptors unless you explicitly permit that repository to supply them. This permission is a separate trust decision; it does not give peipkg rights beyond your account.

Exit status #

CodeMeaning
0The operation succeeded — the repository was added, listed, or removed.
1The operation failed — the trust ceremony failed (a newly created configuration is removed; a pre-existing .repo file is retained), or the subcommand was missing, unknown, or given the wrong arguments (a missing repo add <name> <base-url>, a malformed option). Removing a name that is not configured is not a failure.
2A usage error before any command ran — no command, an unknown command, or a malformed global option.

Transactions and recovery

Peios / Using Peios / Package management

After an interrupted or failed package operation, inspect its history, recover pending work, and verify what is on disk before making another change. Use undo or downgrade only when you mean to reverse a committed change.

What you seeWhat to do
A pending transaction after an interruptionRun peipkg recover, then inspect history and verify files
A committed update you want to reverseReview undo or downgrade
A rolled-back record but damaged or missing filesFollow failed-rollback checks; history alone is not proof of restoration
A committed change with a maintenance warningRead the warning; a failed post-commit side effect does not undo the package change
An interrupted feature scriptUse feat recovery; package recovery does not reverse feature setup

Warning

The failed-rollback reference retains an earlier closed-journal failure description: history could say rolled-back even though files were not restored. The source-verified cross-root path below instead reports failed preparation rollback and retains the affected pending journal. Inspect the actual state rather than applying either description to every version. Filesystem visibility also differs from database durability: files can change before package state commits. Do not assume a failed operation left the system unchanged or restored.

Recovering an interrupted transaction #

Use the same root as the failed operation. If it was an offline or named root, put --root TARGET before each command below.

peipkg history
peipkg recover
peipkg history
peipkg verify
  1. Record the transaction id, state, affected packages and error. Do not delete staged files or backups before recovery has had a chance to use them.
  2. Run recover. A pending single-root transaction is rolled back; if none is pending, the command says so. A committed transaction has only cleanup left and is not reversed by recover.
  3. Read the result and inspect history again. Recovery can itself fail.
  4. Verify the affected packages, or all packages as above. Review each difference against intentional edits and any .peipkg-new file.

Ordinary install, upgrade and removal commands attempt recovery of pending single-root work before starting new work. Explicit recover lets you deal with the interruption first and produces a recovery audit event; the automatic path does not. Read-only package queries remain available without waiting for the transaction lock. A second writer reports transaction in progress; wait for that operation to finish before trying another change.

Across more than one root #

A multi-root upgrade has one combined plan and coordinated execution; it is not a series of independent per-root upgrades. See Named roots for the preparation and sequential-commit boundaries. Preparation rollback can fail; in the reviewed source that path retains a pending journal. Do not treat it as the older closed-journal failure described elsewhere on this page.

For recovery:

  1. Retain the original named-root topology and make every participating root present and reachable. Run peipkg recover explicitly from the same system anchor, using the original --root TARGET if one was selected. An ordinary single-root operation refuses pending cross-root work in isolation. Recovery discovers reachable roots; it does not prove that a missing or unregistered participant has been found.
  2. Read the outcome. If no participant committed, pending participants are rolled back. If a participant committed, pending participants are rolled forward using persisted completion data. Missing or malformed data causes refusal; a later recovery step can fail after another root was reconciled.
  3. Inspect history and verify files in every participant. In this source, the CLI's rolled back cross-root transaction line is printed after either backward or forward reconciliation. It is not proof of rollback; the forward path records rolled forward: cross-root recovery in the transaction. Keep the diagnostics and treat any refused or unreachable root as unresolved.
  4. Review maintenance separately. Roll-forward recovery applies persisted package metadata and attempts backup cleanup, but does not invoke the normal post-commit side-effect runner. Do not assume it re-ran depmod or man-db. Use the invocation reference to identify the affected root and kernel release, and verify the needed maintenance before relying on that system.

This follows peipkg 8b588ae8's CLI root discovery and report, coordinated recovery decision, and roll-forward implementation. It is a source-level contract, not evidence that recovery or maintenance succeeded on a particular machine. Check the revision supplied by the image. The technical recovery chapter keeps the implementation context.

When recovery does not restore the files #

The failure reference retains an earlier description of a closed-journal failure after I/O errors, a filesystem becoming read-only, or permissions changing. If that is the state you actually find, another recover can find nothing to retry. Do not assume all failed rollbacks have that outcome: the reviewed cross-root preparation rollback reports errors and retains the affected pending journal. Inspect the transaction and tool version before choosing the recovery path.

  • Use peipkg verify to identify recorded files that differ or are missing.
  • Inspect the affected paths for staged or backup siblings, and keep the evidence while deciding whether the old or new content is wanted.
  • Establish why writes or restoration failed before attempting repair.
  • Reconcile affected files and package records manually where necessary. There is no reinstall verb: the reference describes removal and installation as two transactions, or a version change and change back. Review dependency, feature and claim consequences before removing anything.
  • Verify again after repair. A clean file check does not test services, feature scripts, registry state or user data.

The indeterminate-state reference describes proposed handling beyond the available history, recovery and file verification tools; it does not establish a forensic-report command or a recover option to accept the current filesystem as authoritative. Its older blanket recovery wording must not override the coordinated cross-root path above. A closed journal and a pending cross-root journal require different responses.

The transaction log #

peipkg history

history prints the transactions peipkg has carried out, most recent first — each with an id, a timestamp, its state (committed, rolled-back, or pending), and a short summary.

$ peipkg history
49  2026-05-19T14:02:10Z  committed    upgrade nginx, zlib
48  2026-05-19T09:31:55Z  committed    install nginx
47  2026-05-18T22:14:03Z  rolled-back  install brokenpkg
OptionEffect
-n NShow at most N transactions. -n 0 shows all of them. The default is 20.
--jsonEmit JSON: for each transaction its id, state, started_at, summary, and operations, each an action (install, upgrade, downgrade, remove or claim), the package name, and its from and to versions where they apply.

The history is what undo reads to find the most recent transaction, and what ties a stray *.peipkg-backup-<id> file back to the operation that created it.

What an interruption leaves behind #

NameMeaning
<name>.peipkg-staged-<id>Incoming content staged beside its destination
<name>.peipkg-backup-<id>The displaced original, renamed beside its destination

The names have no leading dot and carry the transaction id shown by history. They are recovery material, not ordinary cache files. Do not remove them just to tidy a directory after an interruption. A committed transaction can also leave cleanup behind after a crash; use its recorded state and the recovery reference to distinguish the cases.

Configuration files on upgrade #

Package defaults live under /usr/etc/; /etc/ is the merged configuration view. Modified-file protection also recognises legacy package paths under /etc/. It does not make /etc/ a current package destination.

When a default is unchanged since installation, an upgrade can replace it. When you edited it, peipkg preserves your file and writes the new default as <name>.peipkg-new, with a warning. Compare and merge the changes deliberately. The new sibling is unowned and is not removed by uninstall. The original path can continue to appear in verify because its recorded hash is now the new package's hash; that report alone does not mean your preserved edit is corruption. See Configuration files.

Side effects run after commit #

A package can request a standard maintenance operation from a closed set; it cannot supply arbitrary install-time scripts. Those maintenance steps run after commit. A failure is reported as a warning, and the package change stands. Read which step and root failed, then use the invocation and retry rules for that step. A later transaction that requests it can run it again; a warning does not mean the cache was already repaired.

The three phases #

For operating the system, distinguish the plan you can decline, the work in progress, and the reported outcome. A dry run previews a request without applying package changes; it does not reserve the metadata or installed state for a later invocation. The commit procedure and journal describe the implementation.

The one instant that matters #

For a single-root transaction, the database commit is the durability boundary: pending work is recovered by rollback, while committed work is not reversed by recover. This boundary does not remove the failure and visibility limits above. See Atomicity and Crash recovery.

Backups make rollback free #

Renaming displaced files avoids copying their contents for rollback; it does not make restoration infallible or promise a lasting backup. The backup reference says ordinary transaction backups are discarded at commit, while earlier operator guidance described a retention window. Do not rely on transaction backups for a later undo. The undo reference describes resolving from archive metadata and requiring a reachable repository or usable cache. Keep independent backups for data you must recover.

Why this shape #

Planning, journalling and a database commit support recovery; they are not a whole-system snapshot. Package-version reversal affects the package payload, not registry state, runtime data, user data or feature setup. The transaction and rollback chapters hold the implementation detail and its limits.

Where to go next #

  • Reverse a completed update
  • Inspect files and ownership
  • Check named roots
  • Understand package audit records

Dependency resolution

Peios / Using Peios / Package management

A request to install one package can add dependencies, replace another package or affect another root. The plan is the full change you are approving. Use --dry-run on the intended install, upgrade, downgrade or removal to inspect it before applying anything.

The plan #

Review these points before answering proceed?:

  • Are all named packages and dependencies expected?
  • Which versions and repositories will supply them? Does a local-file marker indicate that you are vouching for a file directly?
  • Is a virtual name being satisfied by a different package than you expected?
  • Will replaces or --cascade remove something you still need?
  • Will claims move, or entries tagged -> <root> change another root?
  • Does a downgrade or cross-repository takeover need a separate approval?

Decline the plan if any change is unexpected. Check repository configuration and package details, change the request, and preview again. Do not assume that the package named on the command line is the only package affected.

When resolution refuses the request #

Resolution rejects an unsatisfied dependency, conflicting packages, an architecture mismatch, an ordering cycle or a graph that exhausts its work budget. Read the named packages and constraints before retrying.

  • Refresh and search the intended repositories if a package is unavailable.
  • Correct a missing or untrusted repository rather than silently accepting another source.
  • For a blocked removal, inspect the dependents. Use --cascade only if you intend to remove all of them.
  • For a conflict, choose a compatible package set; there is no automatic conflict-removal approval that makes the rejected plan safe.
  • For root-related errors, inspect the root references and preview each affected target.

The failure conditions and removal-cascade reference document what is checked. Optional dependencies are not installed automatically; name a wanted companion explicitly.

Elevated authorisation #

Most of a plan is routine, and the single proceed? prompt covers it. A plan can also contain an action that a routine yes should not cover — one you must review and approve individually, on its own.

peipkg detects three such actions and, for each one in a plan, asks a separate question:

this operation requires elevated authorisation:
  nginx would move backward from 1.27.5 to 1.27.4
type "authorise downgrade nginx" to authorise this action (anything else refuses):

The three actions that trigger it:

ActionWhy it is elevated
A downgradeMoving a package backward can reintroduce a problem a newer version fixed. It should be a conscious choice, never a side effect of some larger plan.
A foreign replacesA package from a lower-priority repository using replaces to displace a package you installed from a higher-priority one. Left unguarded, a low-trust repository could quietly take over a package you trusted a better source for.
A low-trust providesA package from a low-trust repository advertising a virtual capability in a way that shadows a package from a more-trusted source — satisfying a dependency with its own build instead of the one you would expect.

Two things make this prompt different from the routine one:

  • --yes does not satisfy it. --yes skips the routine proceed? prompt; it has no effect on an elevated-authorisation question.
  • Name the action and package. The text prompt requires its displayed phrase, such as authorise downgrade widget, followed by a newline. Generic y or yes, closed input, and an unterminated phrase refuse the action. Scripts can supply the specific phrase; see the authorisation reference for the text and structured interface contracts.

Each elevated action is presented and authorised on its own; approving one does not approve the next. And the authorising act is itself written to the audit stream — the record shows not just what was done, but that it was specifically authorised and what was authorised.

If an elevated action is not one you want, the plan as a whole is the thing to reconsider: where the package is coming from, whether the repository priorities are right, whether the downgrade is really what you meant.

The relationships between packages #

A package's manifest declares how it relates to others. Four relationships drive resolution.

Dependencies. A package can require other packages, each by a version range. peipkg pulls every dependency into the plan, and their dependencies in turn, until the request is closed.

Conflicts. A package can declare that it cannot coexist with another. If a plan would put two conflicting packages on the system at once, resolution rejects it.

Provides. Several packages can advertise the same capability — a virtual name that is not itself a package. A dependency written against that name is satisfied by any package that provides it. This is how "needs a mail transport agent" can be met by whichever one you actually install.

You can ask for a virtual name directly, too: peipkg install coreutils works even when nothing is named coreutils, and installs whichever package provides it. Because you asked for one name and got a package with another, peipkg says which one it chose. Upgrades and removals are the exception — those name a package you already have, so they match by name only and never substitute one package for another.

Replaces. A package can declare that it supersedes another — the usual case being a rename, or a merge of two packages into one. Installing a package that replaces another causes the replaced package to be removed as part of the same plan.

provides has a stronger cousin: a claim, a single shared name that exactly one package may hold at a time. Where any number of packages can advertise the same provides name at once, a claim has one holder — see Claims.

A dependency can also be routed into a different root, written Depends: foo IN <root>, so that a whole root can be composed through the dependency graph — see Named roots.

Choosing a version #

Selection depends on version constraints, architecture, repository origin and priority, and whether a name is a concrete package or a virtual capability. For upgrades, the installed version is the starting point; a plan with no update may reflect its constraints and available sources.

Repository preferences apply before version within one package name. An architecture change from x86_64 to noarch does not by itself keep a package on an older release: architecture only breaks a remaining version tie within that name. Between distinct providers of a capability, the primary architecture remains preferred. The detailed candidate-selection reference sets out the ordering and virtual-version handling.

Resolution is a pure calculation #

Resolution uses metadata rather than downloading package payloads. It produces a plan or explains why no plan was found. A dry run is a preview of those inputs, not a reservation: refreshing a repository or changing the installed set before applying can change the result. Review the final plan too.

See Inputs and outputs for the resolver's inputs, ordering and determinism discussion, and Candidate selection for its selection and tie-breaking rules. The implementation is bounded and can return a too complex rejection rather than continue indefinitely.

Where to go next #

  • Apply an install or removal
  • Check repository trust and priority
  • Choose a claim holder

Claims

Peios / Using Peios / Package management

Use peipkg claim when several installed packages can supply the same shared name and you need to check or choose its holder. This does not enable a feature or restart a service.

peipkg claim registryd
peipkg claim registryd grant org.example.altregd
peipkg claim registryd

In this example, grant only after the first command lists org.example.altregd as an installed eligible provider. Review the affected links, approve the change, and inspect the holder again. Revoking a claim or removing its holder leaves it unheld; another provider is not chosen for you.

Some filesystem names can be provided by more than one package. Two registry daemons — dev.peios.loregd and an alternative — both install a working binary, but only one of them can own /usr/sbin/registryd. peipkg calls that shared name a claim, and it owns the machinery that decides which package holds it.

Three words are used precisely on this page:

  • A claim is a single shared filesystem name that many installed packages may be able to answer, but exactly one may hold at a time.
  • An eligible provider is an installed package able to answer a given claim.
  • The holder is the one package that currently answers it.

(This is unrelated to token claims — the security-token attribute concept in the identity docs. The same word names a different subsystem.)

What a claim is #

A claim materialises as a symlink on disk. The link lives at the claim path — the shared name, such as /usr/sbin/registryd — and points at a file inside the holder's payload, the target, such as /usr/sbin/loregd. Ask the system for the shared name and you reach whichever provider currently holds it.

/usr/sbin/registryd -> /usr/sbin/loregd

The claim symlink is owned and managed by peipkg. It is not shipped inside any package's payload; no provider installs it, and removing a provider does not remove it out from under peipkg. peipkg creates, repoints, and tears down the link as part of the transactions that install, remove, grant, and revoke.

Package declarations determine which paths a claim exposes and which target each provider supplies. The role model, eligibility checks and materialisation reference describe those internals. Claim links are relative on disk so roots remain relocatable; the status output shows logical paths and targets.

Claims and provides/replaces #

A claim is the "exactly one owner of a shared name" extension of the provides and replaces relationships covered in Dependency resolution. There, several packages can advertise the same virtual name and any one of them satisfies a dependency written against it — that mechanism answers "is something here that provides this?". A claim goes one step further: it answers "which single provider owns this concrete filesystem name right now?", and enforces that the answer is never more than one. Provides establishes that a name can be answered; a claim decides which package answers it on disk.

Auto-claim on install #

Installing an eligible provider auto-claims every claim it provides that is currently unheld. If no package yet holds registryd, installing dev.peios.loregd makes it the holder and materialises the link as part of the same transaction — you get a working shared name without a second step.

The rule is strictly unheld-only. Auto-claim never overrides a claim that is already held by another package. Install a second registry daemon while dev.peios.loregd holds registryd and the newcomer is installed as an eligible provider but takes nothing; dev.peios.loregd stays the holder. Reassigning a held claim is always a deliberate act — see the claim command below, or the install flags that force it.

Install flags #

peipkg install accepts flags that override the default auto-claim behaviour for the packages in that install:

OptionEffect
--no-claimClaim nothing. Install the provider(s) without taking any claim, even ones that are currently unheld.
--claim <names>Force-claim the named claims (comma-separated), overriding the current holder of each.
--claim-allForce-claim every claim the installed packages provide, overriding incumbents.

Two combinations are hard errors:

  • --claim-all together with --claim.
  • --claim-all together with --no-claim.

--claim and --claim-all are how you take a claim that is already held during an install; without them, an install only ever fills claims that are empty.

The claim command #

peipkg claim inspects a claim and reassigns its holder. It has three forms, and a fourth that lists every claim.

peipkg claim <claim>
peipkg claim <claim> grant <package>
peipkg claim <claim> revoke
peipkg claim --json

List every claim. --json with no claim name emits every claim that an installed package can fill or a package holds, as an array. Each has role, holder when it is held, links (each a path and target), and providers, the installed eligible providers.

Report status. With just a claim name, peipkg prints the current state of the claim: the current holder, the materialised links shown as path -> target, and the installed eligible providers.

$ peipkg claim registryd
holder: dev.peios.loregd
links:
  /usr/sbin/registryd -> /usr/sbin/loregd
eligible providers:
  dev.peios.loregd
  org.example.altregd

Grant. grant <package> makes an installed eligible provider the holder. peipkg repoints the claim's links to that package's targets in one transaction. Review the transaction and recovery limits before treating that as a whole-system snapshot. The named package must be an installed eligible provider for the claim.

$ peipkg claim registryd grant org.example.altregd

Revoke. revoke removes the grant. The claim becomes unheld and its links are torn down. peipkg does not automatically promote another provider — a revoked claim has no holder until you grant one.

OptionEffect
--yes, -ySkip the confirmation prompt. Applies to grant and revoke.

grant and revoke each run as a standalone transaction, appear in history, and can be reversed with undo. They also emit a claim-change event under the system's emission policy; the audit reference describes the policy and emission-failure limits. Use the claim command rather than silently editing its symlinks by hand.

What happens on uninstall #

Uninstalling the current holder auto-withdraws the claim. The holder is going away, so peipkg tears down its links and the claim becomes unheld as part of the removal.

peipkg does not auto-promote another provider in its place — an automatic promotion would be the kind of silent reassignment claims exist to prevent. Instead it surfaces the remaining eligible providers and hands you a ready-to-run command to reassign the claim yourself:

$ peipkg remove dev.peios.loregd
...
claim 'registryd' is now unheld. eligible providers: org.example.altregd
to reassign it, run:
  peipkg claim registryd grant org.example.altregd

If the holder was the only eligible provider, the claim is left unheld with nothing to promote, and any consumer relying on the shared name will find it absent until a new provider is installed.

If a claimed name is missing or wrong #

Inspect peipkg claim <claim> first. An unheld claim needs an explicit grant to an eligible provider. A held role can legitimately have no link if no package declares a path for it. For a missing target or a disagreement between reported links and disk, check the holder's files with peipkg verify and consult A broken claim. Do not assume that a normal unrelated transaction will repair a link whose database record already matches the desired state.

Exit status #

CodeMeaning
0The operation succeeded — the status was reported, or the grant or revoke was applied (a declined prompt is also 0: nothing failed).
1The operation failed — the claim has no eligible provider, the named package is not an eligible provider, or a named package or claim is not installed.
2A usage error — an unknown subcommand (the only subcommands accepted after the claim name are grant and revoke) or a malformed option.

Inspecting and verifying

Peios / Using Peios / Package management

Use the query commands to identify installed software and its source, or verify to compare installed files with peipkg's records. These checks are read-only. The optional clean command below does delete cache data.

Run them against the same root as the change you are checking, with peipkg --root TARGET ... when that is not /.

QuestionCommand
What is installed?peipkg list
Which version and source supplied a package?peipkg info <package>
Which files does it own?peipkg files <package>
Who owns this path?peipkg owns <path>
What can my repositories install?peipkg search <term>
Did files change or disappear?peipkg verify [package]...
What operation ran?peipkg history

A successful file check does not test service health, feature state or user data. After a failed transaction, read the recovery limits even if history says rolled-back.

Listing what is installed #

peipkg list

list prints every installed package — name, version, and architecture.

$ peipkg list
nginx  1.27.5  x86_64
pcre2  10.44   x86_64
zlib   1.3.2   x86_64

With --json it emits the same set as a JSON array. Each package has name, version, architecture, origin (the repository it came from, empty for a local file), orphaned, installed_at, size_installed in bytes, and description where its manifest has one.

Showing one package's details #

peipkg info <package>

info prints the full record of one installed package: its version and architecture, the repository it came from (or (local file) if it was installed from a .peipkg directly), when it was installed, and — from its manifest — its description, license, and homepage.

$ peipkg info nginx
name:         nginx
version:      1.27.5
architecture: x86_64
origin:       official
installed:    2026-05-19T14:02:10Z
description:  HTTP and reverse proxy server
license:      BSD-2-Clause
homepage:     https://nginx.org

With --json it emits the same record as one JSON object: the members list --json gives, and — where the manifest has them — license, license_class, homepage, alternate_upgrade, dependencies (each a name and any version constraint) and provides.

Listing the files a package owns #

peipkg files <package>

files prints every filesystem object the package owns — the files, directories, and symlinks that were placed by its install and are tracked against it. This is the record peipkg uses to remove the package cleanly and to verify it.

$ peipkg files zlib
/usr/lib/libz.so.1
/usr/lib/libz.so.1.3.2
/usr/include/zlib.h

With --json it emits an array of objects, each with path, type (file, dir or symlink) and, for a symlink, target.

Finding which package owns a path #

peipkg owns <path>

owns is the reverse lookup: given a path, it reports which installed package placed it.

$ peipkg owns /usr/lib/libz.so.1
zlib

If no installed package owns the path, owns says so and exits non-zero. A path that nothing owns is either not part of any package or was created outside peipkg. With --json it emits the owners' names as an array, empty when nothing owns the path.

Searching the repositories #

peipkg search <term>

search looks through the configured repositories' active indexes for packages whose name or description contains the term, case-insensitively. It searches what is available, not what is installed — it is how you find a package before installing it.

$ peipkg search proxy
nginx     1.27.5  [official]  HTTP and reverse proxy server
haproxy   2.9.7   [official]  Reliable, high-performance TCP/HTTP load balancer

search reads the cached repository metadata, so run peipkg refresh first if you want it to reflect the latest catalog. A repository with no usable cached metadata is skipped with a warning rather than failing the search. --json emits the matches as an array, empty when nothing matches. Each has name, version, architecture, repository, size_download and size_installed in bytes, and description, license and homepage where the repository gives them.

Verifying installed files #

peipkg verify [package]...

verify checks that what is on disk still matches what was recorded when each package was installed. For every file a package owns it checks:

  • a regular file — that it is present and its content still hashes to the recorded value;
  • a symlink — that it is present and still points where it was recorded to point;
  • a directory — that it is still present and still a directory.

With no arguments, verify checks every installed package. With package names, it checks only those.

$ peipkg verify nginx
nginx: /etc/nginx/nginx.conf has been modified since install
verify: 1 problem(s) found

A reported file is not necessarily a fault. A configuration file you edited on purpose will show up — verify is telling you the file diverged from the package's version. verify reports; it does not judge intent and it does not change anything.

If every checked file is intact, verify says so and exits 0. If anything diverged, it lists each problem and exits non-zero — which makes it usable as a check in a monitoring script.

With --json it emits the problems as an array, each a package and a problem, and exits 0 whether or not it found any: an empty array means every checked file is intact.

Investigate an unexpected verification result #

  1. Use info and files to establish the package, version and affected paths.
  2. Compare the result with intended edits. For configuration under /usr/etc/ or legacy /etc/, a .peipkg-new sibling can mean an upgrade preserved your edit and wrote a new default beside it. That original path can continue to report a mismatch against the new recorded hash.
  3. If the difference is unexpected, consider an interrupted rollback, storage corruption or tampering; a hash mismatch alone does not distinguish them. Preserve files you need before attempting replacement.
  4. Repair deliberately, then verify again. There is no reinstall command; the file-mismatch reference describes removal and installation as separate transactions or a version change and change back. Preview dependency and removal consequences first.

verify does not repair anything, and an unowned .peipkg-new or backup is not proof that the current package's recorded files are intact.

Cleaning the metadata cache #

peipkg clean

clean deletes unused metadata-cache data; it is a write operation, not a package verification or recovery command. The cache reference describes content-addressed objects, with pointers naming each repository's current metadata, and collection of objects no pointer references.

$ peipkg clean
removed 2 orphaned cache file(s)

Earlier operator guidance described deletion solely by removed repository; the technical reference instead describes unreferenced-object collection, which can also cover superseded objects from a still-configured source. Do not rely on a repository being configured to preserve every old cached object. This guide does not establish which cache layout your tool version uses. Cleaning is optional housekeeping, not a way to recover a pending transaction; it does not replace reviewing history, running recover or checking files.

Exit status #

CodeMeaning
0The command succeeded. For plain verify, every checked file was intact. verify --json also exits 0 when it finds problems: inspect the array.
1The command failed — a named package is not installed, a path is owned by nothing, or, for plain verify, at least one file had diverged.
2A usage error — an unknown command or a malformed option.

Named roots

Peios / Using Peios / Package management

Before changing an offline installation or an initramfs tree, check which root peipkg will use. With no --root, the default is the running system at /; install can also follow a package's default_root.

Check the target before changing it #

peipkg root list --tree
peipkg root show initramfs
peipkg --root initramfs list

Use a registered name such as initramfs, or a literal path containing /, such as /mnt/image or ./tree. Put the global option before the command:

peipkg --root /mnt/image list
peipkg --root /mnt/image upgrade --dry-run --no-recurse

Read the resolved path and status before applying changes. A dangling root has a binding but no target tree; repeating an install does not make that a valid target. An unregistered name or a resolution cycle is an error.

An upgrade normally resolves the selected root and its reachable named roots together. Review every target in its combined plan. Use --no-recurse when you intend to update only the selected root. After a cross-root failure, preserve all participating roots and recover from the same system anchor; check history and files in each participant afterwards.

What a named root is #

A named root is a registered name for a subtree, recorded in the registry of the root it lives under. The name initramfs bound to the path boot/initramfs is a named root of /: it says "within this root, the name initramfs means the subtree at boot/initramfs, and that subtree is itself a root."

Two ideas follow from that.

Every root has its own registry. The bindings a root knows about are its own. / may register initramfs; what initramfs registers is recorded inside initramfs, not in /. Paths in a registry are stored relative to the root that owns them, so a registry travels with its tree — an image built in one place and mounted in another resolves the same way.

References are resolved from the current root. The current root is whatever --root points at (or / when it is omitted). A reference is resolved by starting there and walking its registry.

Referencing by name #

Once initramfs is registered under /, these two commands target the same tree:

$ peipkg --root boot/initramfs install live-boot
$ peipkg --root initramfs install live-boot

The second names the root instead of spelling out its path. The name is looked up in the current root's registry and resolved to the bound path.

Dotted composition #

Names compose by dotting. initramfs.subroot resolves left to right: from the current root, walk into initramfs's registry, then from there into subroot. Each dot is one more step outward through one more registry.

$ peipkg --root initramfs.subroot install ...

Every dotted segment must match the grammar [a-z0-9][a-z0-9_-]* — a lowercase-alphanumeric lead followed by lowercase alphanumerics, hyphens, and underscores. A segment that names no registered root is a hard error, not a silent miss. A registry arrangement that loops back on itself is detected and reported as a resolution cycle rather than followed forever.

Path or name: how --root decides #

--root accepts either form and tells them apart by a single rule:

  • Any value containing / is a filesystem path and is used exactly as before — --root boot/initramfs, --root /mnt/image, --root ./tree. This is unchanged behaviour.
  • A bare dotted identifier is a named reference — --root initramfs, --root initramfs.subroot — resolved through the registries as above.

The / is the discriminator. If you mean a literal directory, include a slash (even ./name); if you mean a registered name, use the bare identifier.

The root command #

root manages named roots. Every subcommand operates on the current root's registry — the registry of whatever --root points at. To manage the initramfs's own named roots, aim --root at the initramfs first.

peipkg root add <name> <path>
peipkg root remove <name> [--purge]
peipkg root list [--json] [--tree]
peipkg root show <reference> [--json]

root with no subcommand is a usage error — a subcommand is required (add, remove, list, show). An unknown subcommand is a usage error too.

root add #

peipkg root add <name> <path>

Register a named root in the current root's registry. <name> is a single root segment — it must match [a-z0-9][a-z0-9_-]* and must not contain a dot; you register one name at a time, not a dotted chain. <path> is stored relative to the current root. There are no flags.

$ peipkg root add initramfs boot/initramfs

root remove #

peipkg root remove <name> [--purge]

Unregister a named root. By default this removes only the registry entry — the files on disk are left in place, so the subtree survives and can be re-registered. --purge additionally deletes the named root's filesystem tree.

OptionEffect
--purgeAlso delete the named root's filesystem tree, not just its registry entry.

root list #

peipkg root list [--json] [--tree]

Print the current root's registry. Plain output lists the names registered directly under the current root.

OptionEffect
--jsonEmit JSON — for each entry its name, its stored path, its resolved_path, its status, and its children.
--treeRecurse into each child's registry and print the whole nested arrangement. The recursion is cycle-guarded, so a registry that loops is reported rather than followed endlessly.

root show #

peipkg root show <reference> [--json]

Resolve one reference and report on it. <reference> is either a dotted named-root reference or a path — the same path-or-name rule as --root. show reports the reference's resolved path, its status, and the number of packages installed in it.

The status is one of two words:

  • present — the resolved path exists on disk and is a real root.
  • dangling — the reference resolves through the registries, but the path it names is not there. The binding exists; the tree it points at does not.
OptionEffect
--jsonEmit the same report as JSON.

present and dangling are the vocabulary the rest of this page uses for "the binding points at something real" versus "the binding is valid but its target is missing".

Automatic target selection with default_root #

A package's manifest can declare the root a top-level install of it should land in — its default_root. When you install such a package without saying where, peipkg reads that field and re-roots the install onto it for you.

$ peipkg install live-boot

If live-boot's manifest sets default_root: initramfs, this installs into the initramfs root even though no --root was given. The package's manifest records where it belongs, so you do not have to specify it.

Two rules keep this predictable:

  • It applies only when no explicit --root was given. Passing --root with any value, including a named one, states the target outright and suppresses default_root re-rooting entirely. An explicit root always wins.
  • The packages in one command must agree. If the packages named in a single install declare two or more distinct default roots, peipkg cannot pick one and does not guess — the command is rejected as an error. Split it into one command per target.

default_root only chooses a target; it does not create or resolve anything the registry could not already reach. The chosen root is resolved through the registries like any other named reference.

Cascading upgrade #

By default peipkg upgrade resolves one combined plan over the current root and its reachable named roots. With no package names, it considers the installed packages in that combined world; a named upgrade considers that name in every reachable root where it is installed. On a dynamic-initramfs system, this lets / and boot/initramfs participate in the same plan. Review the whole plan before approving it.

If the plan changes more than one root, peipkg uses a coordinated cross-root transaction. A plan changing only one root uses the single-root executor. The multi-root sequence is:

  1. Lock the participating roots and check pending recovery, then fetch and verify the packages for all participants before extracting any.
  2. Prepare each root. If preparation fails, stop and attempt to roll back prepared changes. Rollback can itself fail; the affected journal remains pending and the installation must not be treated as restored.
  3. Commit package state root by root. A commit failure does not begin an independent upgrade in the next root: peipkg continues the coordinated commit attempts and reports a partial-commit error. Roots already committed remain committed; use cross-root recovery for the remaining work.

This is not an instantaneous all-files/all-roots switch or a whole-system snapshot. Files can become visible during preparation, and one root's post-commit maintenance can run before another root's commit is attempted. An error is not proof that every participant stayed unchanged.

This sequence follows peipkg 8b588ae8's upgrade scope, reachable-root planning, and coordinated executor. The prepare/commit boundary and rollback-failure handling set its limits. These are source-backed behaviours, not a runtime test or proof that a particular released image contains that revision.

OptionEffect
--no-recurseConfine the upgrade to the current root only. The cascade into nested roots is disabled.

Use --no-recurse when you deliberately want to move just one root — for example to upgrade the initramfs on its own without touching /, by pointing --root at it and disabling the cascade.

Cross-root dependencies #

Installing a package can pull dependencies into another registered root. The default upgrade also resolves across reachable roots, as described above. Review every target before approving: the operation can affect more than the root you selected.

The cross-root dependency reference keeps the manifest syntax and routing details, including the separately documented limits for other verbs and cross-root autoremoval. Removing the last dependent does not automatically remove its dependency from another root. Inspect the relevant roots rather than assuming every later operation repeats the install's routing or cleans up another tree.

A plan that reaches beyond the current root announces it before you approve:

  • a note: this also changes other roots: ... line naming the other roots the plan touches, and
  • a per-line -> <root> tag on each plan entry that lands outside the current root, so you can see at a glance which change goes where.

You are never taken across a root boundary silently; a cross-root plan always shows its routing.

Cross-root undo and recovery #

The operator interface describes undo of a cross-root transaction as reversing all participating roots together; there is no supported partial-root undo of that transaction. Check the history and preview the inverse plan before applying it.

For an interrupted operation, retain the named-root topology, make every participant present and reachable, and run peipkg recover from the same anchor, using the original --root TARGET if one was selected. The command discovers reachable roots; it does not establish that missing or unregistered participants have been found. An ordinary single-root operation refuses pending cross-root work in isolation.

Recovery rolls pending participants back if none committed. If a participant has committed, it rolls pending participants forward using their persisted completion data. Missing or malformed data causes refusal; further recovery can also fail after another participant has been reconciled. Do not rely on an interruption leaving all roots at the same version.

In the reviewed source, the CLI can print rolled back cross-root transaction for either recovery direction. That wording is not proof of rollback. Inspect per-root history and verify files, and treat refused or unreachable roots as unresolved. Roll-forward recovery also does not invoke the normal post-commit side-effect runner: do not assume depmod or man-db was retried. Follow Transactions and recovery for the pinned recovery sources, outcome checks and maintenance limits.

Exit status #

CodeMeaning
0Success.
1Failure — an unregistered name, a dangling or otherwise invalid reference, a resolution cycle, a failed preparation or rollback, or a cross-root commit or recovery failure.
2Usage error — a missing or unknown root subcommand, a malformed option, or the wrong number of positional arguments.

Composing a root

Peios / Using Peios / Package management

Use the separate peipkg-compose binary when you need a new package-owned root directory for an image. Use peipkg to maintain a root that already exists. Compose does not produce a bootable image by itself or run feature setup scripts.

Build and check a new tree #

  1. Prepare a manifest with the intended architecture, timestamp, repositories, package set and any named roots. Verify the repository anchors through a trusted channel. The manifest reference gives every field and a complete example.

  2. Choose an output directory that does not exist. Compose refuses an existing tree; do not point it at a live installation.

  3. Resolve and record the package set, then build from that lock:

    peipkg-compose lock image.toml
    peipkg-compose build image.toml --out ./root --locked
    
  4. Read the build outcome and warnings. If the build fails or is interrupted, the staging directory is left for inspection; it is not the finished root.

  5. Inspect the successful output's package records and files, for example with peipkg --root ./root list and peipkg --root ./root verify where peipkg is available. Check security metadata required by the image separately.

A fresh tree includes package files, a package database and repository configuration. It does not include recorded repository trust from the build. On the booted system, check that configuration and deliberately run peipkg repo add <name> for each source you choose to trust. Configuration is not a completed trust ceremony.

The mental model #

lock resolves the package set; build assembles it. Keeping the same manifest, lock and package bytes makes the selected inputs repeatable. For the resolution, verification and assembly internals, see the composition reference.

peipkg-compose lock #

peipkg-compose lock <manifest> [-o <lock>]

Creates a lock without building a tree. image.toml defaults to image.lock.toml. Only -o selects another lock path; there is no --out for this verb. See the full lock interface.

peipkg-compose build #

peipkg-compose build <manifest> --out <dir> [--locked | --update]
                     [--dangerously-bypass-path-restrictions]
                     [--record-xattrs <file>] [--no-dependencies]
  • Use --locked to require an existing matching lock and do no metadata resolution. Package bytes must still be available.
  • Use --update to resolve again and overwrite the lock before building.
  • With neither flag, a missing lock is created; an existing lock must match the manifest. --locked and --update cannot be combined.
  • --out is required, and its directory must not already exist.

The complete build options explain the explicit path-restriction grant, recording security attributes, and --no-dependencies. That last option deliberately creates a tree that is not self-sufficient; it leaves signature and hash verification enabled and requires a lock made in the same mode. Do not add it merely to get past a missing dependency.

The manifest #

The manifest is strict TOML: unknown keys, an empty package set, undeclared repository or root references, and duplicate (root, name) requests are errors. The complete schema and sample are in the manifest reference.

When preparing the manifest:

  • Check the top-level fields for architecture, timestamp and the requested package set.
  • Repository entries also become .repo files in the image; those files do not establish trust.
  • Package entries choose versions, sources and target roots. There is no manifest-level default_root; packages can carry their own default placement.
  • Named-root paths must be relative, must not escape through .., and cannot be ..

The lock file #

The generated lock pins the package set. Do not hand-edit it or use --update just to suppress a mismatch without reviewing the changed inputs. See the lock contract for digest, architecture and timestamp checks. Elevated actions that would prompt in a live install are emitted as warnings during unattended composition, so read them before using the image.

Named roots and claims in a composed image #

All roots are built under one output tree. The composer records named roots and selects claim holders for the fresh package set; no installed incumbents exist to preserve. See the detailed rules for deterministic holder selection and relocatable links.

What ends up in the root #

The output contains payloads, var/lib/peipkg/db.sqlite and repository files under lcl/conf/peipkg/. A successful build renames the whole staged tree into place. See the output contract before treating an interrupted build's staging directory as a usable artifact.

What compose does not do #

The outer image builder is responsible for boot integration, runtime caches and feature setup. Compose runs no post-install maintenance steps and emits no audit events. It does not build or sign the packages it consumes.

The references disagree on security-descriptor materialisation; the CLI interface documents --record-xattrs, while an implementation summary says no descriptors are materialised. Inspect the attributes or recorded output needed by your image. The full limitations and interface differences retain those details rather than assuming either behaviour is universal.

Exit status #

CodeMeaning
0Success, or help output (-h, --help, help)
1Resolution, verification, fetch, lock or filesystem failure
2Usage error, including a missing manifest or --out, or conflicting flags

For exact cases and the absence of environment-variable and --version interfaces, see the complete command reference.

Peios Learn — documentation for the Peios project.

Built with Trail.