introduces a balanced set of agent + human docs: - root AGENTS.md (with CLAUDE.md symlink) — 5-rule quickstart, layout map, mental model, use-case keyed example pointers. - docs/agents/conventions.md — vault/demagify, eval-loader constraints, group inheritance, naming, do-not-touch list, suspension idioms, working-style notes. - docs/agents/commands.md — repo-specific deltas to the fork's bw runbook (apt-key offline-verify, *.py_ suspended-node visibility, vault-echo rule). - per-area AGENTS.md for bundles/, nodes/, groups/, libs/, hooks/, data/, items/, bin/ — mechanism-focused, no enumeration. - bundles/AGENTS.template.md — per-bundle doc template with optional `## Writes into` section for cross-namespace reactors. bundlewrap-language reference (item types, dep keywords, reactors, runbook, three-tier safety envelope) is not duplicated here; we link out to the fork's AGENTS.md instead. bw test still green. all internal links resolve. Phase 0 invariants preserved (libs/hooks docstrings, bin/* # purpose: headers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 KiB
groups/
What's here
Groups attach bundles and shared metadata to nodes. One file per group, organized by axis:
groups/
├── all.py # universal base (every node belongs)
├── applications/<x>.py # role-shaped groups (mailserver, monitored, …)
├── locations/<x>.py # physical/network location (home, htz, …)
├── machine/<x>.py # hardware kind (hardware, hetzner-cloud, raspberry-pi)
└── os/<x>.py # OS major/variant (debian-13, debian-13-pve, routeros, …)
Loader mechanism
groups.py (top of the repo) walks groups/ and runs eval() on each
*.py. Same eval-as-expression rule as
nodes/: one dict literal, no
top-level imports, no statements. Errors print and the group is skipped
— a real foot-gun, since a missing group silently changes node
membership.
Group files are not demagified. Magic strings like
!password_for:<x> only resolve in nodes/*.py. Inside a group file,
call repo.vault.<verb>(...) directly.
Inheritance and merge order
Metadata merges along this chain:
all → location → os → machine → applications → node
Per-axis subdirs are conventional, not enforced — bw doesn't read the
subdir. Each group lists its supergroups, and bw resolves the DAG.
Membership is set-union; metadata merge follows the order above, with
the node's own metadata block winning last.
Conventions
- One group per file. Filename without
.py= group name. Subdir groups them by axis for humans, not for bw. - Family files for OS variants. Common parent + per-variant child.
Example:
debian-13-common.pyis shared bydebian-13.pyanddebian-13-pve.py. Use this pattern when introducing related-but-distinct OS group families. all.pyis the universal default. Currently empty ({}); kept for the rare repo-wide opt-in.
How to add a group
-
Pick the right axis subdir (or root
all.pyfor universal default). -
Create
groups/<axis>/<name>.pyas a single dict expression:{ 'supergroups': [ # parent groups whose bundles/metadata this one extends ], 'bundles': [ # bundles every member of this group should have ], 'metadata': { # shared metadata for members }, } -
Wire the group into the relevant
nodes/*.py('groups': {...}) orgroups/*.pysupergroupslist. -
Verify with
bw nodes <node> -a groupsandbw metadata <node>.
How to add a new OS major (recipe)
Pattern from prior debian-12 → debian-13 work:
- Add
groups/os/debian-N.pyandgroups/os/debian-N-common.pyparallel to the existing files. Don't edit in place. - Add
data/apt/keys/debian-N-*.{asc,gpg}for the new release's signing keys. Seecommands.md#apt-key-changes-need-offline-verificationbefore pushing keys live. - Bump dependent bundles that branch on
os_version/os_codename(bundles/bind/items.py, etc.). - Bump affected nodes'
groupslists one at a time. Apply, watch. - Delete the old OS group file once no node references it.
Pitfalls
bw groups -n <node>doesn't exist. Usebw nodes <node> -a groups.- Cycles. A group can't be its own supergroup transitively;
bw testcatches this but the error message is terse. - Silent eval failure. A group file with a syntax error is skipped
and prints a one-line error. If a node loses bundles unexpectedly,
scan
groups.pyoutput for the error.
See also
nodes/AGENTS.md— node files; howgroups: {...}attaches groups.docs/agents/conventions.md— inheritance order, naming conventions, eval-loader constraints.- Fork's
AGENTS.md— group attribute reference, metadata-merge semantics.