The Ur Project

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

pkg-sets(7)

NAME

pkg-sets - package sets and selection roots

DESCRIPTION

Look at the installed package list of any machine that's been running for a year. It's 500 names, maybe more, and you can't tell which ones you actually asked for. You installed a desktop and a handful of tools, and everything else came along for the ride. Once that list is flat, the context is gone, and no tool can tell you what's safe to remove.

pkg keeps that context, with two separate ideas that meet in pkg-autoremove(1).

A set is a named, unversioned list of members, published by a repository and resolved against the chain. You refer to one with a leading plus: +console, +xfce. Members can be packages, subpackages, or other sets. Sets live remotely, in each repository's metadata/sets.json.

A selection root is something you explicitly asked for: a package name, or a set name. Everything installed that no root reaches is a dependency, and it's fair game for the orphan sweep. Roots live locally, in /var/lib/pkg/roots.toml.

So a set is what the distribution offers, and a root is what you chose.

SETS

Unioned across the chain, not shadowed

Packages shadow: the first repository that pins a name wins it (pkg-chain(7)). Sets don't. Every repository whose sets.json defines +name contributes members, and pkg takes the union. Each member then resolves through the ordinary position-decides pin chain.

Those are two separate things. Two repositories that both define +dev-tools are contributors to it, not alternatives. The description, and any other single-value metadata, comes from the highest-precedence repository that defines the set.

Version-blind until the last step

Reading sets.json, taking the union, expanding nested +set references (with a cycle check) and flattening it all into a member list involves no version knowledge at all. Only then does each member go through the pin chain.

A set that no repository defines is a hard error: unknown set. A member that nothing pins is held and reported, the same as any other unpinned name. Held is fatal only where the result has to be deterministic, and that means pkg-compose(1) and pkg-bundle(1).

A member can narrow to one subpackage

openssh as a member means the package's whole runtime content. openssh/server means that subpackage and nothing else. That's the same name/subpkg grammar pkg install takes, and the two are different selections that never deduplicate each other. The package name still resolves through the chain; the subpackage part only picks one out of the winner. If the winning repository ships no such subpackage, the member is held, not a hard error.

No repository qualifier

A set is a union over the whole chain by definition, so +dev-tools@Common is a grammar error. It doesn't mean "Common's slice of it", because there's no such thing.

SELECTION ROOTS

Intent, never a frozen expansion

Installing +console records the intent console, not the eleven package names it happened to expand to today. Every converge works out the membership again from the current sets.json. So when the people publishing the set change it, that change reaches your machine without you doing anything.

A package root is the same: a bare name, no version. Converge follows the pin, up or down.

Promoting a dependency

Running pkg install name on a package that's already installed as a dependency isn't a no-op. It records the root. That's the escape valve for keeping something after it leaves a set.

Never act on missing information

Both of these safeguards decline rather than delete:

  • A machine with no recorded roots doesn't get swept. An empty roots file means "we don't know what was wanted", not "nothing was wanted". pkg-autoremove(1) removes nothing, and says so.
  • A set root the chain can't resolve -- its sets.json is unreachable, or the set was removed -- holds the whole converge. Letting that set contribute nothing would orphan everything it wanted, and that's wrong.

Where roots live

In /var/lib/pkg/roots.toml. It's written by the same transaction that installs or removes what the roots name, so a crash leaves both or neither, and pkg-rollback(1) restores the roots together with the packages. See pkg-state(5).

EXAMPLES

Discovering what sets exist

pkg set list pkg list-sets --all

Inspecting one before installing it

pkg set show +console pkg set show console

The plus is optional here. The output names each member, the repository that contributed it, and the version-release it resolves to, or "held (no pin in chain)".

Installing a set

pkg install +console

This records console as a set root. Members that no other root reaches become removable when the set root goes away.

Which sets is this machine rooted on?

pkg list-sets

What the roots don't explain

pkg list

Every package an installed root reaches folds into that root's row, whether by declared membership or through its dependency closure. What's left is what no root asked for: installed by hand, or left behind. To credit a set only with what it literally names, and leave the libraries it pulled in on their own rows:

pkg list --collapse=sets

See pkg-list(1).

Keeping one member after leaving the set

pkg install htop # promote htop to an explicit package root pkg remove +monitoring # drop the set root; htop stays

Removing a set

pkg remove +xfce --dry-run pkg remove +xfce

Dropping a set root removes what the set pulled in, in the same command, except what another root or an installed package outside the set still needs. Packages the set never reached are left alone. Removing a plain package deliberately doesn't prune anything.

Composing from a set

pkg compose root --output /srv/roots/console \ --closure +console --trust-key "$PUBKEY"

Compose refuses held members. An image built from a set that couldn't be fully resolved wouldn't be reproducible.

Looking at the roots file directly

cat /var/lib/pkg/roots.toml

packages = ["vim", "htop"] sets = ["console"]

  • Two lists, and that's the whole file

SEE ALSO

pkg(1), pkg-install(1), pkg-remove(1), pkg-autoremove(1), pkg-set(1), pkg-list(1), pkg-list-sets(1), pkg-state(5), pkg-chain(7), pkg-subpkgs(7)

docs/design/package-sets.md, docs/design/naming.md