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