The Ur Project

Documenting the craft of stacking things so they remain stacked. For a very long time.

pkg(1)

NAME

pkg - Ur Project package and composition CLI

SYNOPSIS

pkg [global-options] command [command-options] [args...]

DESCRIPTION

pkg is the one program you talk to on an Ur Project system. It installs, removes, upgrades, inspects and verifies packages. It manages the repositories those packages come from. And it turns a set of packages into something you can deploy: a filesystem tree, an immutable slice, an OCI image or a bootable disk image.

It's also a thin layer. Resolving dependencies, applying transactions, tracking state, checking signatures and composing images all live in library crates. Every rule in these pages is enforced there, not in the argument parser, so the same rules hold no matter what is calling them.

This page is the map. Each command has its own page, listed under COMMANDS, and the ideas that run through all of them have pages in section 7. Three of those ideas are worth reading before anything else, because they're where pkg behaves differently from what you might expect.

The chain, not the version

Repositories are stacked in a precedence order. The first repository in the chain that pins a name answers for that name. Full stop. Nothing anywhere in pkg picks a package by comparing version numbers, so a stale copy in some lower repository can never win by being "newer". See pkg-chain(7).

Convergence, not comparison

pkg upgrade doesn't ask whether something is newer. It asks whether the installed artifact is the artifact the pin names, by comparing payload digests. So moving between repositories at an identical version is real work, a rebuild that came out byte-for-byte identical is no work at all, and a downgrade needs no special case. See pkg-upgrade(1).

Selection roots, not a manual flag

What you explicitly asked for is recorded as a selection root. Everything installed that no root reaches is a dependency, and pkg-autoremove(1) sweeps it. A root records your intent, a bare name or a bare set name, never a frozen list of what that expanded to on the day. See pkg-sets(7).

SPECS

How do I name a package?

Every command that takes package names accepts the same grammar.

name
The package's runtime content: every subpackage the winning repository carries for that name, minus build-derived output, development content, server daemons and interpreter-bound scripts. There's no main subpackage to fall back to. See pkg-subpkgs(7).
name/subpkg
One subpackage, checked against the repository. A subpackage the winning repository doesn't carry is refused by name, with a list of what's on offer.
name=version
An explicit version. This is the only form that looks at superseded builds, so it can name an artifact that's no longer current.
+set
A set: a named, unversioned member list, resolved across the whole chain. See pkg-sets(7).

No spec takes an @repo suffix. The chain decides which repository answers for a name, not you, and not the command line.

Commands that produce an artifact, pkg-compose(1) and pkg bundle create, refuse name=version, because an artifact is built from the chain's current set. pkg build --with takes only name and name/subpkg, and reads a bare name by the buildroot rules of pkg-build(1), not as runtime content.

COMMANDS

Managing the installed system

pkg-install(1)
Install one or more packages or sets. Alias: add.
pkg-remove(1)
Remove installed packages, or drop a set root. Aliases: delete, uninstall, erase.
pkg-autoremove(1)
Remove installed packages that no selection root reaches any more.
pkg-upgrade(1)
Converge the installed set onto what the repository chain pins, or with --to, onto one published build's pins file or image.json.
pkg-check-update(1)
Report whether updates are available, changing nothing. Cheap enough for a timer, and exits 100 when there are. Alias: check-updates.
pkg-stage-update(1)
Download the payloads an upgrade would install ahead of time, so the upgrade itself is fast and needs no network.
pkg-repair(1)
Restore an installed package's drifted files from its payload.
pkg-triggers(1)
Run or list the system trigger ruleset: cache and preset regeneration.
pkg-rollback(1)
Undo the newest transaction, or return to the state after an earlier one, and pin what moved.
pkg-pin(1)
Hold packages at their installed build, list local pins, or release them again with pkg unpin.

Inspecting

pkg-history(1)
The transactions this machine committed, numbered the way /var/log/pkg.log numbers them. One in detail, or exported as a pins file.
pkg-search(1)
Search the chain by name, capability, or shipped file. Alias: find.
pkg-list(1)
List installed packages, or every package the chain makes current. By default, everything a selection root reaches folds into that root's row; --collapse walks down from there to sets, main and none.
pkg-query(1)
Print details of an installed package: files, provides, requires, conflicts. Alias: show.
pkg-info(1)
Print details of a manifest, by name or by path.
pkg-license(1)
Report what the installed set, or one package, is licensed under, and print the license text it ships.
pkg-sbom(1)
Emit a software bill of materials for the installed set, in SPDX or CycloneDX.
pkg-verify(1)
Check the files of installed packages, a set, or everything installed, on disk against their manifests. Alias: check.
pkg-list-sets(1)
List installed sets, or every set in the chain.
pkg-set(1)
Inspect sets published across the chain: membership, contributors, resolution.

Configuring repositories

pkg-repo(1)
Add, remove, enable, disable and list configured repositories.

Provenance and signatures

pkg-provenance(1)
Verify a published package's buildroot-attestation record, reproduce the build offline, or download the upstream sources it was built from.
pkg-sign(1)
Sign a manifest with an Ed25519 key.
pkg-verify-sig(1)
Verify the signature on a manifest.

Producing artifacts

pkg-compose(1)
Compose a closure into a filesystem root, an immutable slice, an OCI image, or a bootable disk image.
pkg-bundle(1)
Produce or install an offline .pkgtar bundle.
pkg-offline(1)
Update an air-gapped system by hand-carried bundle: export its pins, build a delta bundle for it elsewhere, apply that bundle.

Building packages

pkg-build(1)
Build a recipe directory on this machine into a signed local repository, with a buildroot resolved from the chain.
pkg-keygen(1)
Create the key pkg build signs with.

Shell integration

pkg-completion(1)
Print a shell completion script.

GLOBAL OPTIONS

These go before or after the subcommand, and apply to every command.

--json
Emit JSON on stdout instead of human text. Errors become "{"ok": false, "error": "..."}" on stderr. The schemas are documented per command and in pkg-cli/JSON.md. A --json run can never answer a confirmation prompt, so commands that change the system also need --yes.
-y, --yes
Assume yes, and skip the confirmation prompt for operations that change the system. What gets a prompt is set by the top-level confirm key in pkg.toml(5): none, dangerous, destructive or all, default destructive. --yes is also how a script or a --json run gets through without a terminal, since nobody is there to answer the prompt. It does not get you past the trust-on-first-use prompt in pkg-repo(1).
--collapse=LEVEL

How far a human listing folds up the subpackages a package was split into. (bin, lib, data, boot, mod and the rest are content classes that the classifier assigns, not something you name -- see pkg-subpkgs(7).)

  • main: each package's runtime subpackages fold into one row, with the classes in brackets: "name V-R [/bin, /lib]". Non-runtime ones (dev, man, doc, lang, server, debug, debugsrc, source, and the interpreter classes) stay on their own name/subpkg row. This is the default everywhere except pkg-list(1).
  • none: one row per subpackage.
  • sets, roots: fold further, onto what you selected instead of onto the package name. Only pkg-list(1) can answer these, because the question is about an installed set and the selection roots that produced it. roots is its default for the installed set. Ask anything else for them, or ask pkg list --all for roots, and you get refused by name rather than quietly handed main.

It applies to the transaction plan (pkg-install(1), pkg-upgrade(1), pkg-remove(1), pkg-autoremove(1), pkg-offline(1)), the closure pkg-compose(1) prints, the name and capability hits of pkg-search(1), and the listing of pkg-list(1). It's presentation only. It changes no behaviour, and --json always gets the flat per-subpackage list.

-v, -vv, -vvv

Show what pkg is doing, on stderr. stdout stays clean, so --json | jq works at any level. Each level adds to the one below:

  • -v: outcome. One line per phase, with how long it took and the counts it produced.
  • -vv: progress. The same phases announced as they start, plus per-repo and per-package step detail.
  • -vvv: per-item waterfall. Every manifest read, goal, closure member, blob and fetch URL. It's a lot, on purpose. Network lines start with net:: whether a repository is reached directly or through a proxy (and which variable or file named it), every address tried, every request, response and retry. A long gap between two connecting to lines for one host is a connection attempt that timed out.

Anything past -vvv behaves as -vvv. Verbosity never changes behaviour, exit codes or the bytes on stdout. The full contract and a per-command map live in pkg-cli/VERBOSITY.md.

Byte counts are printed as magnitudes (152.4 MiB). From -vv up, the exact count rides along with each one (152.4 MiB (159771259 bytes)), for when you need to reconcile a number against something else.

--no-progress

Don't draw the download progress line. While pkg waits on the network, during the per-manifest metadata walk and each payload fetch, it repaints a single line on stderr saying what's in flight, how far along it is and how fast:

metadata Common [ 145/1674] fetch [ 2/9] gcc/lib 86.4 MiB / 92.8 MiB 93% 13.4 MiB/s

That line is already off whenever stderr isn't a terminal, or --json was given, so a redirected log, a timer and a pipeline get nothing to filter out. This flag is for the interactive run that wants silence anyway. Progress is never drawn for a local repository directory, or for payloads pkg-stage-update(1) already staged. Those are file reads, not downloads.

--state-root DIR
The root directory of the client state database. Default /var/lib/pkg. See pkg-state(5).
--repo DIR
A single local repository. Default /var/lib/pkg/repo. Used when no repositories are configured and no --repo-url is given. See pkg-repository-layout(7).
--payload-dir DIR
Where payload blobs live. The canonical layout is <dir>/data/<sha[0:2]>/<sha[2:4]>/<sha>; a flat development layout (<dir>/<sha>.pkg-data) is tried as a fallback. Defaults to --repo, so one canonical-layout root works without passing both.
--repos-dir DIR
The directory of drop-in repository definitions. Default /etc/pkg/repos.d. This is the normal way to configure repositories. A missing directory just means no configured repositories. See pkg-repos.d(5).
--pins-dir DIR
The directory of local pins. Default /etc/pkg/pins.d. A missing directory means no local pins. See pkg-pins.d(5).
--repo-url URL
The base URL of a remote repository. Can be repeated. Together with --tuf-root, it fetches and verifies signed metadata from <url>/metadata/. The Nth --repo-url is verified by the Nth --tuf-root, highest precedence first. Replaces --repos-dir entirely.
--tuf-root FILE
A TUF 1.root.json holding the trust anchor for the --repo-url in the same position. Get it out-of-band, never over the same URL the metadata lives at, or it proves nothing.
--config FILE
The system client configuration. Default /etc/pkg.toml. A missing file means the defaults. See pkg.toml(5).
-h, --help
Print help. Works per subcommand too: pkg install --help.
-V, --version
Print the version, taken from the git tag at build time.

EXAMPLES

First contact with a machine

What's configured, what's installed, and what the chain offers:

pkg repo list pkg list pkg list --all pkg list-sets --all

pkg list already reads through this machine's selection roots: one row per root, then only what no root explains. To credit a set with only what it literally names, instead of everything its closure pulled in, or to drop the overlay entirely:

pkg list --collapse=sets pkg list --collapse=main

Adding a repository and installing from it

pkg repo add http://repo.example:8082/Base \ --fingerprint 4f3c...9a1b pkg repo add http://repo.example:8082/Common \ --fingerprint 71de...02c4 pkg install vim git

The everyday loop

pkg upgrade --dry-run pkg upgrade pkg autoremove --dry-run pkg autoremove

Ask cheaply, download on your own schedule, apply in a window:

pkg check-update pkg stage-update pkg upgrade

Finding things

pkg search compress pkg search --provides soname:libz.so.1 pkg search --file /usr/bin/gzip pkg search --file '*.so.6'

Asking what a package is, and what it owns

pkg info gzip pkg query gzip --files pkg query gzip --requires --provides

Checking that a machine hasn't drifted

pkg verify --all --quiet pkg verify coreutils --quiet pkg repair coreutils --dry-run pkg repair coreutils

Installing more than the runtime content

Manual pages and headers aren't part of a bare name, and neither is a daemon:

pkg install vim/man pkg install openssl/dev pkg install openssh/server

To opt a whole machine in, use [install] include_subpkgs in pkg.toml(5).

Scripting

Every command takes --json, and the ones that change the system also need --yes:

pkg --json list | jq -r '.packages[].name' | sort -u pkg --json --yes install vim | jq '.installed_count' pkg --json upgrade --dry-run | jq '.upgrade[] | .from.name'

Working against a repository that isn't configured

Explicit flags replace repos.d entirely. That's handy for testing a staging repository without touching the machine's configuration:

pkg --repo /srv/mirror/Common --repos-dir /nonexistent list --all pkg --repo-url http://staging:8082/Common \ --tuf-root /tmp/common.root.json search vim

Composing an artifact instead of installing

pkg compose root --output /srv/roots/minimal \ --closure +console --trust-key "$PUBKEY" pkg compose image --output /srv/images/ur.raw \ --closure +console --trust-key "$PUBKEY"

Building a package and handing it to someone

Make the key once per user, then build each recipe. --from-repo-dir puts the output repository on top of the configured chain for the bundle, so the bundle carries the dependencies too:

pkg keygen pkg build ./which --out ~/work/out pkg bundle create --from-repo-dir ~/work/out --output which.pkgtar

Seeing where the time goes

pkg -v upgrade --dry-run pkg -vv install vim pkg -vvv install vim 2> /tmp/pkg-trace.log

Auditing what you're actually running

pkg provenance verify openssl pkg provenance download-sources openssl --out /tmp/src pkg provenance rebuild openssl

EXIT STATUS

pkg exits 0 on success and 1 on any failure. The message on stderr tells you which failure. A command line that doesn't parse exits 2; under --json its message comes in the same error envelope as any other failure.

Some commands treat a non-empty result as a failure, on purpose: pkg verify exits non-zero when any file fails verification, pkg triggers run when any trigger failed, pkg provenance rebuild when the rebuild diverged, and pkg license --check when anything installed violates the [license] policy.

One command carries its answer in the exit code. pkg-check-update(1) exits 0 when nothing is available, 100 when updates are, and 1 when it couldn't tell. That's the dnf convention, so an existing timer or monitoring check works unchanged. 100 is the only exit code outside 0, 1 and 2 that pkg produces.

FILES

/etc/pkg.toml
System client configuration: confirm, [install] include_subpkgs, [license], [history] and [network]. See pkg.toml(5).
/etc/pkg/repos.d/*.toml
Repository drop-ins, one file per repository. See pkg-repos.d(5).
/etc/pkg/pins.d/*.toml
Local pins, above every repository. See pkg-pins.d(5).
/etc/pkg/*.root.json
TUF trust anchors, one per remote repository, named after the repository in lowercase. Written by pkg repo add.
/var/lib/pkg/
The client state root: installed/, installed.staging/, index/, roots.toml, history/, .lock. See pkg-state(5).
/var/log/pkg.log
One line per committed transaction, starting with its time and #number. See pkg-history(1).
/var/lib/pkg/repo/
The default single local repository, used when nothing is configured.
~/.cache/pkg/metadata/
Verified TUF metadata, per user. A repeated query then costs one timestamp.json request per repository instead of a full refresh, and pkg-list(1) can still answer when a repository is unreachable.
~/.cache/pkg/targets/
Fetched pkg-meta files, addressed by the sha256 the verified metadata names for them. A published pkg-meta never changes, so a cached copy is reused across snapshot rolls. It's re-hashed against the freshly verified metadata before it's parsed.
~/.cache/pkg/index/
The parsed index of each repository, so the next command doesn't parse the same manifests again. One entry per repository, overwritten when the repository rolls. A local repository without targets.json is never cached.
~/.cache/pkg/provenance/
Provenance records and blobs that pkg-provenance(1) fetched on demand.
~/.cache/pkg/rebuild/
Scratch space for pkg provenance rebuild.
~/.cache/pkg/build/
Scratch space for pkg build, one directory per N-V-R.
~/.config/pkg/build.key
The key pkg build signs with. Created by pkg-keygen(1).
/usr/share/bash-completion/completions/pkg
Where a generated bash completion script belongs.

Every ~/.cache/pkg path honours $XDG_CACHE_HOME, and lives under /var/cache/pkg/ instead when running as root, whatever the environment says. See ENVIRONMENT.

ENVIRONMENT

XDG_CACHE_HOME
The base for every per-user cache above. Ignored when running as root, which always uses /var/cache/pkg: a cache there is trusted without being verified again, so it has to be root's own. Falls back to ~/.cache, and to .pkg-cache in the current directory when there's no home either.
XDG_CONFIG_HOME
Where the pkg build key lives. Falls back to ~/.config.
https_proxy, http_proxy, all_proxy, no_proxy
The proxy to reach repositories through, and the hosts that skip it, read the way curl(1) reads them; the uppercase spellings work too. Each one overrides its counterpart in the [network] table of pkg.toml(5).

SEE ALSO

Commands

pkg-install(1), pkg-remove(1), pkg-autoremove(1), pkg-upgrade(1), pkg-check-update(1), pkg-stage-update(1), pkg-search(1), pkg-list(1), pkg-list-sets(1), pkg-query(1), pkg-verify(1), pkg-repair(1), pkg-info(1), pkg-license(1), pkg-sbom(1), pkg-set(1), pkg-repo(1), pkg-compose(1), pkg-bundle(1), pkg-offline(1), pkg-build(1), pkg-keygen(1), pkg-sign(1), pkg-verify-sig(1), pkg-provenance(1), pkg-triggers(1), pkg-history(1), pkg-rollback(1), pkg-pin(1), pkg-completion(1)

File formats

pkg.toml(5), pkg-repos.d(5), pkg-pins.d(5), pkg-state(5)

Concepts

pkg-chain(7), pkg-sets(7), pkg-subpkgs(7), pkg-repository-layout(7)

Project documentation

docs/ARCHITECTURE.md, docs/design/repo.md, docs/design/trust.md, docs/design/package-sets.md, docs/design/naming.md, docs/design/install.md, docs/design/history.md, docs/design/triggers.md, docs/design/composition.md, docs/design/buildroot-attestation.md, docs/components/pkg-cli.md, pkg-cli/JSON.md, pkg-cli/VERBOSITY.md

AUTHOR

Auke Kok <sofar@foo-projects.org>