The Ur Project

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

pkg-install(1)

NAME

pkg-install - install packages and sets

SYNOPSIS

pkg install [--dry-run] [--force] spec [spec...]

pkg add [--dry-run] [--force] spec [spec...]

DESCRIPTION

You want something on this system that isn't there yet. You name it, pkg works out everything it drags along, shows you the plan, and then applies it in one transaction. Either all of it lands, or none of it does.

The plan always comes first. You see exactly what will be installed, upgraded and removed, and how much will be downloaded, before anything changes.

What can I name?

Each spec is one of these:

name
The package's runtime content. That's every subpackage the winning repository carries, minus build-derived output, development content and server daemons, plus whatever [install] include_subpkgs in pkg.toml(5) selects. See pkg-subpkgs(7).
name/subpkg
One subpackage, checked against the repository. If the winning repository doesn't carry it, you get 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. Without it, resolution stays on the bounded current set, which is the fast path.
+set
A set, expanded across the whole chain to its members before anything else happens. See pkg-sets(7). A member that no repository pins is held and reported, and the install goes ahead with the rest.

Why install something that's already installed?

Because pkg remembers what you asked for. Everything you name on the command line is recorded as a selection root: set references as set roots, and package names (with any =version stripped off) as package roots. Roots are your intent. The rest is what they pulled in.

That makes pkg install the "I want this" switch, too. Running it on a package that's only installed as a dependency isn't a no-op. It promotes that package to a root, so a later pkg-autoremove(1) won't sweep it away when whatever pulled it in goes.

What --force does, and doesn't

--force is "I know, do it anyway". It gets past two kinds of refusal: a dependency the repository can't satisfy (that edge is dropped, and the rest of the closure installs), and a pre-flight GC blocker on a removal. A GC blocker is a running process that maps a file on its way out, an installed binary that still needs a soname, or a kernel module that's loaded.

It won't install a package that doesn't exist, and it won't break a hold. A name a local pin holds (pkg-pins.d(5)) stays where it is; release it with pkg unpin first. Everything --force overrides is reported, never silenced, and passing it makes the command ask for confirmation.

OPTIONS

--dry-run
Compute and print the plan, then stop. No confirmation is asked, since nothing changes.
--force
I know, do it anyway. Proceeds past an unsatisfiable dependency and past a GC blocker on a removal; see above. Not past a pin, and not to a package that doesn't exist.

All global options apply; see pkg(1).

OUTPUT

The plan

The plan is printed first, collapsed to one row per package the way pkg-list(1) collapses a listing. The runtime subpackages go in brackets, and everything else gets a row of its own:

Plan: install:

  • vim 9.1.1-2 [/bin, /data]
  • vim/man 9.1.1-2

remove:

  • oldthing 1.0-1 [/bin]

upgrade: ~ libc 2.41-3 -> 2.41-4 [/lib] fetch: 4 package(s), 7.7 MiB to download

--collapse=none gives you one row per subpackage instead (pkg(1)). The fetch count is in subpackages either way. It's what will be downloaded, not what's being shown.

Sizes are rounded to a magnitude. Pass -vv and you get the exact byte count next to each one (7.7 MiB (8123456 bytes)).

When --force is dropping a dependency edge, the plan says so before you're asked anything, so you can still back out:

--force is overriding 1 resolution refusal(s); the closure will be incomplete:

And when a GC check would refuse the apply, the plan lists it under blocked:, with --force overrides in the line.

Same version, different bytes?

An upgrade line between two identical version-releases looks like a no-op. It isn't. It's a move between repositories, or a rebuild, and the line shows the payload digests that actually differ:

~ expect 5.45.4-6 -> 5.45.4-6 [/bin] (rebuild a1b2c3d4e5f6 -> 0f9e8d7c6b5a)

Every subpackage has its own payload, so the digests only show up when a row stands for exactly one of them. A row that covers several just says (rebuild). Use --collapse=none to see every pair.

While it fetches

A progress line on stderr names the package in flight, where it is in the plan, and the transfer rate:

fetch [ 2/9] gcc/lib 86.4 MiB / 92.8 MiB 93% 13.4 MiB/s

It's only drawn when stderr is a terminal, never under --json, and --no-progress turns it off entirely. See pkg(1).

After it's applied

Transaction complete: 4 installed, 1 removed (#212, see pkg history info 212)

The number is the transaction's sequence number in pkg-history(1). A few more things can follow that line, and you should read them.

Processes still running the replaced files. A process on the old inode keeps running the old code, and this is the only notice you get:

needs restart (still running the replaced files): pid 1421 /usr/sbin/sshd

If the running processes couldn't be scanned (not run as root, or a process whose memory map can't be read), a warning says so instead. Which processes still run the old files is then unknown, not "none".

Loaded kernel modules whose files were replaced, under needs reboot. And config files under /etc you've edited, which were kept, with the new default parked next to them under config kept. Nothing merges those two for you.

Under --force, whatever got bypassed:

--force bypassed 1 blocker(s): libc/lib: pid 1421 maps /usr/lib/libc.so.6

Any triggers the transaction fired are reported after that; see pkg-triggers(1).

CONFIRMATION

The prompt is controlled by the confirm key in pkg.toml(5). At the default, destructive, a plain install doesn't prompt, but --force does. The prompt comes after the plan is printed and after the --dry-run exit, so you've already seen exactly what would happen. A plan that changes nothing never prompts.

JSON

--json replaces the human plan with:

{ "ok": true, "kind": "plan", "install": [ { "name":..., "subpkg":..., "version":..., "release":..., "digest":... } ], "remove": [ ... ], "upgrade": [ { "from": {...}, "to": {...} } ], "fetches": [ { "sha256":..., "size":... } ], "estimated_disk_bytes": 8123456, "relaxed": [ ... ], "blocked_by": [ ... ] }

and the completion line with:

{ "ok": true, "kind": "tx-result", "seq": 212, "installed_count": 4, "removed_count": 1, "needs_restart": [ { "pid":..., "command":..., "stale":... } ], "restart_scan_unavailable": null, "needs_reboot": [ { "module":..., "path":... } ], "overridden_blockers": 0, "pkg_new": [ { "path":..., "pkgnew":... } ] }

relaxed lists the dependency edges --force dropped, and blocked_by the GC checks that would refuse the apply. A --json run can't answer a prompt, so add --yes whenever the confirm level would ask. See pkg-cli/JSON.md.

EXAMPLES

The common case

pkg install vim pkg install vim git htop

Look before you leap

pkg install vim --dry-run

Development content

A bare name never brings in headers or static archives. Ask for them:

pkg install openssl/dev zlib/dev

If you want that on every install on this machine, use [install] include_subpkgs in pkg.toml(5).

Manual pages

pkg install vim/man coreutils/man

A daemon

Server daemons are left out of a bare name on purpose. Wanting the client tools isn't the same as wanting to run a server, so you ask for that one:

pkg install openssh # client tools only pkg install openssh/server # sshd as well

A specific version

pkg install openssl=3.5.4

This is the only spec form that searches superseded builds. Note that a later pkg upgrade converges it right back onto the pin. An explicit version is a point-in-time choice, not a hold. If you want it to stay there, pin it with pkg-pin(1) once it's installed.

A set

pkg set show +console pkg install +console

Marking an existing dependency as wanted

pkg install libxml2

If it's already installed as a dependency, nothing gets installed, but libxml2 becomes a selection root and survives pkg-autoremove(1).

Installing over a blocker

pkg install libc --dry-run pkg install libc --force

Read the reported blockers afterwards. At the default confirm level, --force prompts.

Non-interactive

pkg --yes install vim pkg --json --yes install vim | jq .installed_count

Which subpackages did a bare name turn into?

pkg -vvv install vim --dry-run 2>&1 | grep 'goal:'

Where did the time go?

pkg -v install vim

Installing from a repository that isn't configured

pkg --repo-url http://staging:8082/Common \ --tuf-root /etc/pkg/common.root.json \ install vim --dry-run

Installing into a fixture state root

pkg --state-root /tmp/fixture --repo /srv/local install vim

EXIT STATUS

0 on success. 1 on any failure, including a declined confirmation.

SEE ALSO

pkg(1), pkg-remove(1), pkg-upgrade(1), pkg-autoremove(1), pkg-search(1), pkg-info(1), pkg-pin(1), pkg-history(1), pkg-triggers(1), pkg.toml(5), pkg-pins.d(5), pkg-state(5), pkg-chain(7), pkg-sets(7), pkg-subpkgs(7)