bundlewrap/nodes/AGENTS.md
CroneKorkN 04558a9189
docs: scaffold agent-friendly entry points (Phase 1)
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>
2026-05-10 15:44:45 +02:00

92 lines
3.5 KiB
Markdown

# nodes/
## What's here
One file per node, ~22 in total. Each file is a single Python
expression (a dict literal) describing one machine: hostname, groups,
bundles, and metadata.
## Loader mechanism
`nodes.py` (top of the repo) walks `nodes/`, reads each `*.py` file,
runs `eval()` on its content, and then *demagifies* the result —
resolving any `!password_for:`, `!decrypt:`, `!decrypt_file:`, and
`!32_random_bytes_as_base64_for:` magic strings via `repo.vault`. See
[`docs/agents/conventions.md#secrets`](../docs/agents/conventions.md#secrets)
for the magic-string list.
This loader shape has consequences:
- **No top-level imports.** The file must be a single expression. No
`import os`, no `def`, no `if`. Use `repo.libs.<x>` from bundle code
if you need a helper.
- **Silent drop on parse failure.** Vanilla bundlewrap omits a node
whose file fails to eval. The maintainer's `groups.py` was patched
in commit `dc40295` to print the error; the node-loader prints
on `nodes.py` errors via the same shape. Symptom either way:
`bw nodes` lists fewer nodes than expected.
## Conventions
- **Filename = node name.** `home.server.py` defines the node `home.server`.
- **Naming pattern: `<location>.<role>.py`.** Examples:
`home.server.py`, `htz.mails.py`, `ovh.left4me.py`,
`mseibert.freescout.py`.
- **`*.py_` parks a node** without loading it. Used to keep
decommissioned-but-not-deleted configs in tree (e.g.
`htz.l4d2.py_`). The loader only matches `*.py`.
- **Magic strings only resolve here.** `!password_for:` etc. work in
node files; in groups, bundles, or items they don't — call
`repo.vault.<verb>(...)` directly there.
## How to add a new node
1. Create `nodes/<location>.<role>.py` with a single dict expression:
```python
{
'id': 'a-uuid-or-stable-name',
'hostname': '<dns-name-or-ip>',
'groups': {
'debian-13',
'monitored',
# …
},
'bundles': {
# only bundles not provided by the groups above
},
'metadata': {
# node-local overrides and required keys
},
}
```
2. Add to relevant `groups/<axis>/<x>.py` if group membership is the
attachment point (preferred over per-node `bundles` lists).
3. Verify:
- `bw nodes` — your node should appear.
- `bw nodes <node> -a groups` — confirm group membership resolved
as expected (`bw groups -n <node>` does **not** exist).
- `bw metadata <node>` — confirm merged metadata.
## Pitfalls
- **Renaming a node renames the node.** Vault entries (anything keyed
on `!password_for:<node>`), `bw hash` records, and ssh known_hosts
associations all key on node name. Search-and-replace before
renaming, or vault lookups silently return new (wrong) values.
- **Don't restore `_old` or `*.py_` files** without checking
[`conventions.md#suspension-and-soft-delete-idioms`](../docs/agents/conventions.md#suspension-and-soft-delete-idioms).
These are intentional parks/buffers, not bugs.
- **`id` must be unique.** A pre-apply hook (`hooks/unique_node_ids.py`)
enforces this; duplicate IDs fail `bw test` and `bw apply`.
## See also
- [`groups/AGENTS.md`](../groups/AGENTS.md) — group-membership patterns,
how metadata merges along the chain.
- [`docs/agents/conventions.md`](../docs/agents/conventions.md) —
demagify magic-strings, naming, eval-loader constraints.
- Fork's [`AGENTS.md`](https://github.com/CroneKorkN/bundlewrap/blob/main/AGENTS.md)
— node attribute reference (`hostname`, `username`, `dummy`, etc.).