Files
command-center/omp-docs/tools/packages.md
T
Frank 06f45e83c1 refactor(packages): one manifest parser, so a name means one thing to both tools
install.sh and pkg-inventory.sh carried byte-identical copies of read_manifest.
That is the duplicated-code half of the problem; the worse half is untested —
neither script had any test for the grammar that decides what gets installed.

- scripts/lib/manifest.sh is the parser; both scripts source it
- tests/manifest-parser.bats writes the contract first (comments, CRLF, first
  field only, unreadable file fails loudly instead of yielding "nothing desired")
- an unreadable manifest now fails inside the helper rather than depending on
  whichever `set` options the caller happens to run under
- install.sh check mode verified end to end against a stubbed pacman: a CRLF
  manifest with trailing comments resolves to bare names, installed names drop

Refs #60
2026-10-21 00:54:31 -07:00

120 lines
6.4 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-21
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`). **One parser reads them for both tools**:
`scripts/lib/manifest.sh` — `sed 's/#.*//'` then the first field, so `#` comments trail freely and
one token per line is the whole grammar; an unreadable manifest is a loud failure rather than an
empty desired list. `tests/manifest-parser.bats` owns that contract. The two scripts carried
verbatim copies of the body until 2026-10-21, which is exactly how a manifest line can come to mean
two things to the two halves of the same check. 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, never drift (see the law below)
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 — the niri
module `nirimod-git`, say, or 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 and stays
undeclared (see the note in `packages/blankpad.pkgs`) until `install.sh` grows a per-host AUR
manifest.
## Law: orphans never reach a manifest
Drift is `pacman -Qqe` — *explicitly* installed, therefore yours. `pacman -Qtdq` names packages
installed **as a dependency** that nothing requires any more, and a package carries exactly one
install reason, so the two sets cannot overlap (blankpad 2026-10-21: 206 explicit, 3 orphans, none
in common). That is why nothing in `pkg-inventory.sh` subtracts the orphan list from the additions:
the query is already the guard. Do not add such a subtraction — it cannot fire, and it would bury
the invariant that matters, *an implicit install is never drift no matter when it arrived*, which
`tests/pkg-inventory.bats` pins with a dependency installed after the baseline. What the
`orphans ignored` line is for is the other half: dead weight on the machine that a manifest must
never teach a fresh box to install.
## 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)
and `tests/manifest-parser.bats` (the grammar above, observed as names in / names out).
- Refs #51.