The Ur Project

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

pkg.toml(5)

NAME

pkg.toml - system-wide client configuration for pkg

SYNOPSIS

/etc/pkg.toml

DESCRIPTION

Most machines never need this file. With no /etc/pkg.toml at all, pkg(1) runs on its defaults, and that's a perfectly normal, supported state. You write one when you want this machine to behave differently: always install manual pages, never ask before removing things, refuse a license, keep less history.

It's TOML. pkg --config points one invocation at a different file.

This file is only about how the client behaves. Repositories are configured in pkg-repos.d(5), not here.

What about typos?

Unknown keys are an error, not a warning. A typo in this file makes every pkg command fail with the parse error. That's annoying for about ten seconds, and much better than pkg silently doing something other than what you meant.

TOP-LEVEL KEYS

confirm = "LEVEL"

How much confirmation commands that change the system need. The levels form a dial: each notch confirms everything the looser one does, and more. Default destructive.

  • none: Never prompt.
  • dangerous: Prompt only when --force is in play.
  • destructive: Prompt for --force, and for commands whose job is to delete or revert: pkg remove, pkg autoremove and pkg rollback. Removals that happen along the way during an install or an upgrade -- a shed subpackage, a replaced provider -- don't prompt.
  • all: Prompt for every command that changes the system.

The prompt comes after the plan is printed, and after --dry-run has already returned, so you've seen exactly what would happen before you're asked. A plan that changes nothing never prompts, whatever the level.

--yes skips the prompt for one invocation.

THE [install] TABLE

include_subpkgs = [...]

Subpackage selectors added to every bare-name expansion, on top of what a bare name already gets you. Default: empty.

Each entry is either a bare class (man, which matches that class and every one of its class/variant forms) or a class/variant pair (lang/zh_CN, which matches only that variant).

It applies at every expansion, not only at install time. A bare pkg upgrade re-expands every selection root, so a selector you add here reaches packages that are already installed on the next upgrade. Removing a selector uninstalls nothing, and pkg-autoremove(1) doesn't prune subpackages of a rooted name either.

Think of it as the machine-wide version of typing name/subpkg by hand. pkg-subpkgs(7) explains what a bare name leaves out, and why.

THE [license] TABLE

Which licenses this machine accepts. pkg-license(1) --check measures the installed set against it.

No table permits everything, which is exactly what this machine did before the table existed.

deny = [...]
License terms this machine refuses. Default: empty.
allow = [...]
License terms this machine accepts. Default: empty, which permits anything not denied. A non-empty allow list turns this into an allowlist: a term must be named to be accepted. deny always wins over allow.
unknown = "allow" | "deny"

What to do with a package whose license can't be identified: an expression that uses the SPDX LicenseRef- escape hatch, or one that doesn't parse. Default: allow.

Shipping no license text is deliberately not something you can refuse here. That's a packaging gap on the producer's side, not a statement about what you're willing to accept, and a policy nobody could ever switch on isn't a policy. pkg-license(1) --unknown reports it.

What a term is

Both lists hold SPDX license ids and WITH exceptions, matched without regard to case, with * and ? globbing: LicenseRef-* covers every use of the escape hatch. Case doesn't matter because it doesn't in SPDX either, and getting the canonical spelling exactly right isn't something a config file should demand of you.

How a decision is made

Over the parsed expression, never over a flat list of the ids in it. Apache-2.0 OR MIT with Apache-2.0 denied is still available under MIT. GPL-2.0-only AND MIT with MIT denied isn't available at all. A package is accepted when some path through its expression avoids everything the policy refuses.

What is not here yet

Prompting before accepting a license, a record of what's already been accepted, and applying the policy at install time instead of after the fact. Today, pkg license --check is the only reader of this table, and it reports and changes nothing.

THE [history] TABLE

How far back pkg-rollback(1) can reach on this machine. History is pruned after each transaction. The newest record and its snapshot are always kept.

keep_records = N
Transaction records to keep. Default 1000, at least 1.
keep_snapshots = N
Snapshots of the installed set to keep. Default 50, at least 1, and no more than keep_records, since a snapshot without its record can't be reached. A rollback to a transaction whose snapshot was pruned is worked out from a later one, as long as every record in between is still there. A snapshot costs about 100 KB per 700 installed subpackages.

THE [network] TABLE

How pkg reaches repositories. With no proxy set here or in the environment, pkg connects to every repository directly, and on most networks that's all you need.

proxy = "URL"

The HTTP proxy every repository request goes through, as http://host:port. Default: none.

Credentials go in the URL, http://user:password@host:port, and are sent as Basic proxy authentication. An https:// repository is tunnelled through the proxy with CONNECT, so the proxy can't read or change what's inside.

Credentials only work for https:// repositories, because they're sent with the CONNECT and nowhere else. An http:// repository that would go through a proxy with credentials is an error up front, not a failed login halfway through. Reach it directly with no_proxy, or use its https:// URL. Nothing it could do would get past verification anyway: every signature and every hash is still checked against what this machine trusts.

Only http:// proxies are supported. A socks5:// or https:// proxy is an error, never something pkg quietly ignores before connecting directly. Leave out the port and it's 80, which is almost never where a proxy listens, so spell it out.

no_proxy = [...]

Hosts pkg always reaches directly, never through the proxy. Default: empty. Each entry is one of:

  • *: every host, which turns the proxy off.
  • A domain name: that host and every host under it. example.com and .example.com both cover mirror.example.com.
  • An IP address, or a CIDR range like 10.0.0.0/8. These match only a repository URL that names an address. A host name in a URL is never looked up to check it against a range.

The environment wins

The usual proxy variables override this table, read the way curl(1) reads them: https_proxy or HTTPS_PROXY for an https:// repository, http_proxy or HTTP_PROXY for an http:// one, and all_proxy or ALL_PROXY for either when the specific one isn't set. no_proxy or NO_PROXY replaces the list here, it doesn't add to it. Each setting is decided on its own, so a NO_PROXY in the environment still leaves the proxy here in use. A variable set to the empty string counts as not set.

So why have this table at all? Because pkg often runs without your environment. run0(1) starts it with a clean one, and sudo(8) normally does too, and a systemd timer never had one. A proxy only in your shell then never reaches the command that needed it.

To skip a configured proxy for one command:

NO_PROXY='*' pkg check-update

EXAMPLES

A workstation that wants manual pages and headers

confirm = "destructive"

[install] include_subpkgs = ["man", "doc", "dev"]

A build machine that wants development content but no documentation

[install] include_subpkgs = ["dev", "staticdev"]

A machine with one locale

[install] include_subpkgs = ["man", "lang/en_US"]

A server that deliberately opts into daemons

[install] include_subpkgs = ["server"]

Now pkg install openssh lands sshd as well. Think about that one before you do it. server is left out by default because a listening daemon changes what the machine is.

An unattended machine

confirm = "none"

The same as passing --yes to everything. You're usually better off putting --yes in the automation itself, so a person at the same machine still gets asked.

A cautious machine

confirm = "all"

Every install, upgrade, remove, autoremove, repair and rollback that would change anything asks first, and so does a compose onto a disk.

A license policy

[license] deny = ["AGPL-*"] unknown = "deny"

Then see what the installed set has to say about it:

pkg license --check

Keeping less history

[history] keep_records = 200 keep_snapshots = 20

Behind a corporate proxy

[network] proxy = "http://proxy.corp.example:3128" no_proxy = ["corp.example", "10.0.0.0/8"]

Repositories on the internet go through the proxy, and an internal mirror under corp.example or on the 10/8 network is reached directly.

The default, spelled out

This file is exactly the same as no file at all:

confirm = "destructive"

[install] include_subpkgs = []

Testing a configuration without installing it

pkg --config /tmp/candidate-pkg.toml install vim --dry-run

Confirming a typo is caught

printf 'confrim = "none"\n' > /tmp/bad.toml pkg --config /tmp/bad.toml list

Fails with a parse error that names the file and the unknown key.

SEE ALSO

pkg(1), pkg-install(1), pkg-license(1), pkg-remove(1), pkg-rollback(1), pkg-repos.d(5), pkg-state(5), pkg-subpkgs(7)

docs/design/install.md, docs/design/naming.md