Files
command-center/omp-docs/tools/packages.md
T
Frank 6df45d709a Add pkg-inventory.sh: report what a machine has outgrown its manifests
install.sh installs desired − installed from packages/*.pkgs, which guarantees a fresh box reaches
the declared state — and nothing anywhere checked that the declared state still describes the
machines. Measured against the Calamares install day, blankpad carries 41 hand-installed packages
and packages/blankpad.pkgs declared one line.

pkg-inventory.sh walks the other direction: installed − declared, with the pacman log as the
baseline so installer furniture is never reported as a hand install. It splits additions by
whether a sync db can serve them (Chaotic-AUR counts, so only true AUR routes to aur.pkgs),
excludes orphans from --emit, reports flatpak apps without pretending install.sh installs them,
and exits 3 rather than guessing when the sync databases are unreadable.

16 black-box tests over fixtures and a stubbed pacman. Two of them exist because the first
implementation was wrong in ways a small fixture cannot show: sort|head under pipefail dies of
SIGPIPE on a real 1000-line install-day log (awk-exit closes the pipe exactly as head does), and
tr without a trailing echo glues the next report label onto the last folded name.

Docs: omp-docs/tools/packages.md now owns the package tier — install.sh was never documented.

Refs #51
2026-10-20 18:07:42 -07:00

107 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: reference
title: package desired state
description: packages/*.pkgs plus the two tools that keep a machine and its manifest agreeing — install.sh installs desired-minus-installed, pkg-inventory.sh reports installed-minus-declared.
tags:
- packages
- pacman
- provisioning
- fleet
- blankpad
verified: 2026-10-20
timestamp: 2026-10-20
---
# Package desired state
Two tools, one invariant: **the manifests must describe the machine.** A rebuild is only faithful
if both directions are checked.
|tool|direction|writes|
|---|---|---|
|`scripts/install.sh`|desired → installed|installs `desired − installed` with `--apply`; never removes|
|`scripts/pkg-inventory.sh`|installed → declared|nothing — read-only report|
## Manifests
`packages/common.pkgs` (every host) + `packages/<host>.pkgs` (this host) + `packages/aur.pkgs`
(AUR-only, installed through `yay`). The parser is `sed 's/#.*//'` then the first field, so `#`
comments trail freely and one token per line is the whole grammar. An unknown `--host` gets the
common manifest only: a wrong-host run is a no-op, not a hazard.
## Rebuild a workstation
1. Install the OS (CachyOS, niri/noctalia profile). The installer's own package set is *not* in the
manifests and must not be — see the baseline law below.
2. Clone this repo to `~/.omp`, then `scripts/install.sh --apply` — packages first, because the
enablement step needs `sshd` and `firewalld` present ([dotfiles-deploy](dotfiles-deploy.md)).
3. `scripts/deploy.sh sync` for the config tier ([dotfiles-deploy](dotfiles-deploy.md)).
4. `scripts/pkg-inventory.sh --emit` on a *known-good* machine and fold the output into
`packages/<host>.pkgs` / `packages/aur.pkgs` before step 2 — that is how a hand install stops
being invisible to the next rebuild.
## pkg-inventory report
```
undeclared additions (declare these): N ← exit 1 when N > 0
repo (sync dbs): <names> → belongs in packages/<host>.pkgs
aur (needs a helper): <names> → belongs in packages/aur.pkgs
installer-provided, undeclared: N + names → a reinstall supplies these; do not declare
declared, not installed: <names> → install.sh installs these
orphans ignored: <names> → pacman -Qtdq
flatpak: <app ids> → reported only, install.sh installs no flatpaks
```
Exit codes: `0` clean · `1` drift · `2` usage · `3` classification impossible. Overrides
`PKGINV_REPO` / `PKGINV_HOST` / `PKGINV_LOG` / `PKGINV_BASELINE` exist for tests.
## Law: the installer's own day is the baseline
Whether a package is *yours* is decided by the pacman log, not by opinion: the baseline date is the
oldest `[ALPM] installed` entry — Calamares' pacstrap — and everything installed **on or before** it
is installer furniture. Without that cut, a base install reports several hundred "additions" and the
list nobody can act on is worse than no list.
- **Read the rotated logs too** (`pacman.log.N`, `.N.gz`). On a machine older than the rotation
window the install day exists only there; skip them and the whole base system is reported as
hand-installed.
- Calamares keeps installing the desktop for minutes after the base transaction, which is why the
date cut is inclusive.
## Law: refuse rather than guess the AUR split
`aur.pkgs` vs `<host>.pkgs` is decided by whether any sync database can serve the name — Chaotic-AUR
counts as a sync database, so only true AUR belongs in `aur.pkgs`. If the sync databases are
unavailable the tool exits `3` instead of assuming: guessing sends every package to `yay`, which is
how a manifest becomes a pile of source builds.
Classify with `pacman -Sl` (world-readable). **`pacman -Sqq` requires root** — a tool that only
reads the machine must not need sudo to answer a question about it.
**Known gap:** `aur.pkgs` is read for *every* host, so a desktop-only AUR package (a niri module, a
GUI client) cannot be declared there without asking the headless servers to build it. Such a name is
still reported as an AUR addition; leave it undeclared until `install.sh` grows a per-host AUR
manifest.
## Law: orphans never reach a manifest
`pacman -Qtdq` names are packages nothing depends on; emitting them would teach a fresh machine to
install the same dead weight. They are printed as `orphans ignored` and excluded from `--emit`.
## Two traps that produced this tool
- **`set -o pipefail` plus `head -1`**: `head` closes the pipe, `sort` dies of `SIGPIPE`, the
substitution returns `141`, and `set -e` aborts the script. It survives a small test fixture and
fails on a real 1000-line install-day log — take a minimum in one pass (`awk 'END'`-style), never
sort-then-first. The same applies to `awk 'NR==1{exit}'`, which closes the pipe exactly as `head`
does.
- **`tr '\n' ' '` swallows the final newline** and `fold` faithfully preserves that absence, so the
next report label lands on the same line as the last name. `tr '\n' ' '; echo` instead.
## Related
- [tools/index](index.md) · [dotfiles-deploy](dotfiles-deploy.md) (the config tier that follows) ·
[live-sync](live-sync.md) (the heartbeat that commits machine state)
- Tests: `tests/pkg-inventory.bats` (fixtures + stubbed `pacman`, no real package database touched).
- Refs #51.