pkg-repo(1)
NAME
pkg-repo - add, remove, enable, disable and list configured repositories
SYNOPSIS
pkg repo list [--all] [--trust]
pkg repo add base-url [--name n] [--fingerprint sha256] [--priority n] [--consumes repo]... [--disabled] [--allow-mixed-stream] [--dry-run]
pkg repo remove name [--keep-anchor] [--force]
pkg repo enable name [--force]
pkg repo disable name [--force]
DESCRIPTION
A repository on this machine is two files: a drop-in under /etc/pkg/repos.d that says where it lives and where it sits in the chain, and a trust anchor beside it that says who is allowed to sign for it. You can write both by hand. pkg-repos.d(5) describes the format, and pkg-chain(7) explains how the order is worked out.
But writing them by hand means knowing the repository's real name, making up a priority, picking a path for the anchor, and fetching that anchor yourself. pkg repo does all four from nothing more than the base URL. The one thing it refuses to do for you is decide that the anchor it fetched is the right one. That part can't be automated, and TRUST below explains why.
rm and delete are aliases for remove.
COMMANDS
list
Prints the configured chain in precedence order, highest first: name, priority, whether it's enabled, the URL or path it points at, and any consume edges. A repository that attested a product line when it was added gets a stream line too.
NAME PRI ENABLED SOURCE Common 20 yes http://repo.example:8082/Common consumes Base Base 10 yes http://repo.example:8082/Base
Config order. Runtime order can differ: a repo's own attested consumes edge outranks this.
This view is deliberately offline, and it says so. It shows what repos.d describes. At runtime, the edges a repository attests in its own signed metadata outrank the configuration, so the real order can differ.
It's also the cheapest way to check the whole directory. Every pkg command loads and validates repos.d before it does anything else, so a dangling edge, a cycle, a duplicate name, a missing tuf_root or an unknown key fails right here, naming the file.
add
Derives everything it can from the base URL, and checks all of it before writing anything:
- The URL. It has to start with
http://orhttps://. Trailing slashes are dropped. - The anchor. 1.root.json is fetched and the chain walked forward to the newest root, each hop exactly one version newer and signed by the root before it. The newest root is the one that gets pinned. Pinning 1.root.json instead would mean trusting whatever a long-retired root key signs as its successor. These checks prove the chain is consistent. They prove nothing about whose chain it is.
- The live check. A TUF client is built on that anchor and runs a full refresh against the repository: timestamp, snapshot, targets, and the current package set. An anchor that doesn't verify the repository's own metadata is refused before you're asked anything. There's no point spending your confirmation on a repository that could never have worked.
- The name. It comes from the repository itself: the name it states in its signed pin file. Only a repository that states no name falls back to the last path segment of the URL. The URL is a poor source of identity: /Sandcastle/Base and a flat /Base both end in
Base, and the wrong name picks the wrong anchor file and drops the attested consume edge.--nameoverrides both. A name that isn't a safe filename component, or one that's already configured, is refused. - The stream. If the repository attests a product line and an enabled repository here is in a different one, the add is refused. Mixing two product lines resolves packages across builds nobody ever tested together. This is checked now, while you're standing in front of the command and nothing is broken yet, not later when it would break
pkgin the middle of a migration. A repository that attests no stream never conflicts. - The consume edge. Taken from the edge the repository attests, filtered down to repositories that are configured and enabled here. An edge to a repository this machine doesn't have would make every later
pkgcommand fail.--consumesoverrides it, and is checked the same way. - The priority. Defaults to the highest configured priority plus 10, and never below 10, so a repository you add later sits above the ones already there.
- Confirmation, then write. You're asked only when there's no fingerprint; see TRUST. The drop-in (<priority>-<lowercase-name>.toml, priority zero-padded to two digits) and the anchor (<lowercase-name>.root.json, one directory above repos.d) are both written atomically. If the drop-in path already exists, pick another slot with
--priority. If the anchor path already holds a different root, the add is refused: replacing a root another repository is verifying against is a re-pin, not an add, and a routine command shouldn't do that silently. Byte-identical is fine, and so is an older root that walks forward to the one being pinned: that's the same repository, rotated sinceremove --keep-anchorleft its anchor behind, and the file is kept as it is. When no configured repository uses the file, the refusal names it so you can delete it.
Before anything is written, add prints what it found (unless you asked for --json): the name and where it came from, what the repository attests about itself, the anchor's sha256, version, expiry, keys and role thresholds, and the two paths it will write.
remove
Removes the drop-in, and the anchor it names, unless you pass --keep-anchor or another configured repository points at that same anchor. It also forgets the root and metadata versions this machine had recorded for the repository, so a later re-add of a new root starts fresh. Those versions are kept with --keep-anchor, since re-adding the kept anchor is the same trust relationship, and kept while another configured repository has the same URL and an anchor with the same content, as after you add a URL under a new name and remove the old one. That record is what stops a mirror replaying older signed metadata, so it isn't dropped while anything still uses it.
A repository that another enabled repository consumes is refused, and the error names the dependents. Removing it would leave dangling edges, and those break every later command, not just this one.
When several drop-ins carry the name you gave, remove won't guess which one you meant either; it names the files, and you delete the one you mean.
remove asks before it deletes anything. --yes answers for you.
enable and disable
Flips one drop-in's enabled and nothing else. URL, anchor, priority and consumes stay exactly as they were. Parking a repository costs nothing to undo, and never re-fetches or re-pins an anchor.
Flipping a repository to the state it's already in is a no-op. It's reported, and it exits 0. Neither command prompts, because the change is trivially reversible and being cheap is the whole point.
Both refuse a flip that leaves a chain no later command would accept. The damage wouldn't show up here: a dangling edge refuses the next pkg command, far away from the change that caused it. So disable refuses while another enabled repository consumes the target, and names the dependents. enable refuses when the repository's own edge points at one that's still parked, or when it would leave two enabled repositories with the same name. When several drop-ins carry the name you gave, neither will guess which one you meant; edit that file by hand.
TRUST
Why can't it just download the anchor?
Because the anchor can't be authenticated by fetching it. The bytes come from the very server they're supposed to authenticate. These base URLs are routinely plain HTTP, so anyone in the middle can hand back their own root, and every check after that verifies happily against it.
So there are two ways to add a repository, and add is honest about which one you're using.
With a fingerprint
You get a root's sha256 from somewhere the network fetch can't touch: the release notes, the installer media, a signed announcement. Pass it with --fingerprint. It can match any root in the walked chain. Matching version k authenticates k, and every later root is signed by the one before it, so a fingerprint published for one root keeps working after the next rotation.
A mismatch is a hard refusal, and nothing is written. A match needs no prompt, because the comparison already happened. This is also how a script, a --json run or CI adds a repository without a human.
Without one
That's trust on first use, and it says so. The anchor's sha256, key ids, threshold, roles and expiry are printed under an explicit warning, and you have to answer y yourself. --yes doesn't answer this one. A script that pins whatever the network returned is exactly the failure this is here to stop.
Understand what you're accepting: everything from that repository, from now on, verifies back to bytes that server handed you.
OPTIONS
list
--all- Include repositories whose drop-in sets
enabled = false. Without it, you get a count of the disabled ones that were left out. --trust- Also show what this machine trusts for each repository: the anchor file's root version and sha256, the newest root version this machine has recorded, and the repository's current root, walked from the anchor over the network, with its version and sha256. Compare that sha256 with the fingerprint the project publishes. This is the only form of
listthat touches the network.
add
--fingerprintSHA256- The sha256 of one of the repository's roots, from a channel independent of this fetch. The only way to actually authenticate the anchor. See TRUST.
--dry-run- Run every check and print what would be written, including the anchor's sha256, without writing anything. This is how you get the fingerprint to compare out-of-band.
--nameNAME- Use NAME instead of the name the repository states, or the last path segment of the URL. Must be a safe filename component: ASCII letters, digits, dash, underscore and dot, not starting with a dot. It names both files.
--priorityN- Chain priority. Higher sorts earlier among repositories that no consume edge relates. Must not be negative.
--consumesREPO- A repository this one sits on top of, instead of the edge the repository attests. Can be repeated. Each must already be configured and enabled.
--disabled- Write the drop-in with
enabled = false, so it's parked, not active. --allow-mixed-stream- Add it even though it belongs to a different product line than the repositories already configured. The one time that's legitimate is the window while you migrate from one line to the other.
remove
--keep-anchor- Leave the trust anchor file on disk, and the versions recorded against it.
--force- Remove it even though other repositories consume it. Their edges become dangling, and every later
pkgcommand refuses until you fix them.
enable, disable
--force- Flip it anyway, and really break the chain until it's fixed.
EXAMPLES
Seeing what is configured
pkg repo list pkg repo list --all pkg --json repo list --all | jq -r '.repos[] | "(.name) (.priority)"'
Adding a repository, the right way
Get the fingerprint out-of-band -- from the release notes, the installer media, a signed announcement -- then:
pkg repo add http://repo.example:8082/Base \ --fingerprint 4f3c1a...9a1b
Getting the fingerprint to compare
pkg repo add http://repo.example:8082/Base --dry-run
Compare the printed sha256 against your independent source, then run it again with --fingerprint.
Adding interactively, accepting trust on first use
pkg repo add http://repo.example:8082/Base
It prints the warning and the anchor details, and asks.
Adding a stack in order
pkg repo add http://repo.example:8082/Base --fingerprint 4f3c... pkg repo add http://repo.example:8082/Common --fingerprint 71de... pkg repo list
Common's attested edge puts it above Base by itself. No --consumes needed.
Adding a repository under a different name
pkg repo add http://mirror.internal/pub/common \ --name Common --fingerprint 71de...
Adding a repository parked
pkg repo add http://staging:8082/Common \ --name Staging --disabled --fingerprint 9c2f... pkg repo list --all
Temporarily taking a repository out of the chain
pkg repo disable Staging pkg upgrade --dry-run pkg repo enable Staging
Trying to disable something that is consumed
pkg repo disable Base
Refused, naming Common as a dependent. That refusal is the point: parking Base would leave Common's edge dangling, and the next command would refuse.
Removing a repository
pkg repo remove Staging pkg repo remove Staging --keep-anchor
Non-interactive add in CI
pkg --json repo add http://repo.example:8082/Common \ --fingerprint "$COMMON_ROOT_SHA256"
--fingerprint is required here. --yes won't substitute for it.
Auditing the anchors on a machine
pkg --json repo list --all \ | jq -r '.repos[] | "(.name)\t(.tuf_root)"' sha256sum /etc/pkg/*.root.json pkg repo list --trust
Recovering from a dangling edge
Every command refuses with a dangling-consume error. Find out which drop-in is at fault:
pkg repo list --all
Then re-add the missing repository, re-enable it, or edit the offending drop-in's consumes line by hand.
JSON
repo list emits repos_dir and repos, each with name, priority, enabled, url, path, tuf_root, consumes, stream, config_path, and trust (null unless --trust). The other subcommands emit an ok envelope describing what was, or with --dry-run would be, written or removed.
EXIT STATUS
0 on success, including a no-op enable or disable. 1 on any refusal: an unreachable repository, an anchor that doesn't verify the chain, a fingerprint mismatch, a name or anchor collision, a stream conflict, a chain the change would break, or a declined confirmation.
SEE ALSO
pkg(1), pkg-upgrade(1), pkg-list(1), pkg-set(1), pkg-repos.d(5), pkg-chain(7), pkg-repository-layout(7)
docs/design/repo.md, docs/design/trust.md, docs/design/federation.md