Packages¶
Installed software must never fail setup merely because DOTS did not install it.
Ownership model¶
| Term | Meaning |
|---|---|
| managed | Declared by the resolved DOTS package/component set and owned by Homebrew |
| missing | Declared by DOTS but absent |
| outdated | Declared, Homebrew-owned, update available |
| external | Declared by DOTS; app exists but Homebrew does not own it |
| inactive | Installed; known DOTS owner(s) exist but none are selected |
| undeclared | Top-level Homebrew install with no DOTS group/component owner |
known ≠ selected ≠ installed
installed ≠ managed
managed = declared by resolved DOTS configuration (active groups + selected components)
inactive = known in a Brewfile owner, but that owner is not active
undeclared = no owner in brew/groups/*.Brewfile or component brewfile=
Desired state is the union of active profile package-group Brewfiles plus
selected optional components — never every Brewfile on disk, and never the
aggregate brew/Brewfile as ownership authority.
Where ownership lives¶
brew/groups/*.Brewfile # profile package groups (canonical)
brew/Brewfile.<component> # optional --with components (canonical)
brew/Brewfile # convenience / backward-compatible aggregate only
configs/packages/groups.toml # required vs optional logical ids
configs/packages/{apt,pacman,dnf,xbps}.toml # Linux native names
There is no packages.txt. Do not treat top-level brew/Brewfile as the sole
authority.
Logical groups¶
From configs/packages/groups.toml (generated):
| Group | Role |
|---|---|
core |
Portable essentials |
modern |
Shared ergonomic CLI |
workstation |
Desktop-user tools (not headless) |
infra |
Containers / k8s / terraform |
media |
Image / video processing |
gui |
macOS GUI / fonts |
server |
Headless extras |
dev |
Local CI / lint / Rust ergonomics (install-only) |
security |
Secrets scanning / SBOM / signing CLIs (install-only; no auth) |
network |
Network / remote / k8s context CLIs (install-only; no Tailscale up) |
data |
Tabular / analytical CLIs |
geo |
Geospatial CLI tooling |
required → ERROR if missing after install for that profile contract.
optional → WARN if missing; may be empty/SKIP on some managers.
Unavailable on a distro: map value "" (skip) — do not invent fake names.
New workstation tool groups are mostly optional membership. They do not
change global Git (difftastic / mergiraf are install-only — opt in per-repo).
mitmproxy is intentionally not in network (defer as opt-in later).
DOTS never runs tailscale up / login.
Aggregate brew/Brewfile still covers only the legacy bare-setup.sh set
(core…infra). New groups are owned solely by brew/groups/<name>.Brewfile
and selected via profile packages =.
CLI¶
./dots packages groups # capability groups (+ active)
./dots packages group geo # one group in detail
./dots packages plan --profile home # resolved plan (read-only)
./dots packages status # full audit (advisories only)
./dots packages explain dust # owners + activate hint
./dots packages outdated # outdated managed packages
./dots packages upgrade # upgrade managed outdated only
./dots packages upgrade --all # opt-in: broader Homebrew upgrades
./dots packages adopt glow --group modern # suggest Brewfile lines (no auto-edit)
# If a package already has a component owner, adopt points to --with instead.
Human guide: Package groups.
See also Components and --with.
External casks¶
When a declared cask app exists but Homebrew does not own it:
- Warn
- Attempt
brew install --cask --adopt - Leave untouched on failure
Never --force. Never delete unmanaged app copies.
Hard invariant¶
DOTS never runs brew bundle cleanup or uninstalls undeclared software.
Implementation¶
Logic: helpers/package_state.sh, helpers/packages.sh, helpers/cask_apps.sh.
Tests: scripts/tests/package_state_test.sh, scripts/tests/cask_app_test.sh,
scripts/tests/component_discovery_test.sh.