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

3.5 KiB

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 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:

    {
        '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. 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 — group-membership patterns, how metadata merges along the chain.
  • docs/agents/conventions.md — demagify magic-strings, naming, eval-loader constraints.
  • Fork's AGENTS.md — node attribute reference (hostname, username, dummy, etc.).