Replaces requirements.txt + `python -m venv` + periodic `pip install`
with a single pyproject.toml driven by `uv sync`. .envrc shrinks to
`use uv` (plus the BW_*_WORKERS helper). The CroneKorkN/bundlewrap
fork is now pinned via [tool.uv.sources] (git, non-editable) instead
of `pip install -e git+...`; pinned rev lives in uv.lock and bumps
via `uv sync --upgrade-package bundlewrap`. Mirrors left4me 77b5e01.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
6.4 KiB
Markdown
150 lines
6.4 KiB
Markdown
# Conventions
|
|
|
|
Repo-specific idioms an agent has to know before editing this BundleWrap
|
|
config. For bundlewrap-the-language reference (item types, dep keywords,
|
|
metadata-reactor semantics, after-change runbook), see the fork's
|
|
[`AGENTS.md`](https://github.com/CroneKorkN/bundlewrap/blob/main/AGENTS.md).
|
|
|
|
## Secrets
|
|
|
|
Secrets live in `.secrets.cfg` and are referenced from node files by
|
|
"demagify" magic strings. The loader (`nodes.py`) walks node dicts and
|
|
resolves any leaf string of the form `!<verb>:<arg>`.
|
|
|
|
| Magic string | Resolves to |
|
|
|---|---|
|
|
| `!password_for:<id>` | `repo.vault.password_for(id)` |
|
|
| `!decrypt:<ciphertext>` | `repo.vault.decrypt(ciphertext)` |
|
|
| `!decrypt_file:<path>` | `repo.vault.decrypt_file(path)` |
|
|
| `!32_random_bytes_as_base64_for:<id>` | `repo.vault.random_bytes_as_base64_for(id, length=32)` |
|
|
|
|
Magic strings are only resolved inside **node files** (everything under
|
|
`nodes/`). They are *not* resolved in group files, bundle metadata
|
|
defaults, or item attributes — call `repo.vault.<verb>(...)` directly
|
|
there.
|
|
|
|
**Never echo decrypted values.** Don't print, log, or paste them, even
|
|
in `bw debug` exploration. If you need a sanity check, hash or
|
|
fingerprint instead. This applies to AI agents and humans equally.
|
|
|
|
## Bundlewrap version
|
|
|
|
Bundlewrap is pinned to the `main` branch of the maintainer's fork
|
|
[`github.com/CroneKorkN/bundlewrap`](https://github.com/CroneKorkN/bundlewrap)
|
|
via `[tool.uv.sources]` in `pyproject.toml`:
|
|
|
|
```toml
|
|
[tool.uv.sources]
|
|
bundlewrap = { git = "https://github.com/CroneKorkN/bundlewrap.git", branch = "main" }
|
|
```
|
|
|
|
`uv sync` (run automatically by direnv on entry) builds `.venv/` from
|
|
`pyproject.toml` + `uv.lock`. Non-editable — to pick up new fork commits,
|
|
re-run `uv sync --upgrade-package bundlewrap` and commit the updated
|
|
`uv.lock`. The fork's `main` tracks upstream `bundlewrap/bundlewrap` master;
|
|
the fork's [`AGENTS.md`](https://github.com/CroneKorkN/bundlewrap/blob/main/AGENTS.md)
|
|
is the canonical agent-oriented bundlewrap-language reference.
|
|
|
|
To hack on bundlewrap itself from inside this checkout, clone the fork
|
|
to a sibling path and override the source with an editable path dep —
|
|
not part of the default workflow.
|
|
|
|
## Eval-loaded node and group files
|
|
|
|
`nodes/*.py` and `groups/*.py` are loaded by `eval()` in `nodes.py` /
|
|
`groups.py` (top of the repo). Each file must be a single Python
|
|
expression — typically a dict literal.
|
|
|
|
Consequences:
|
|
|
|
- **No top-level imports.** No `from foo import bar`, no `import os`.
|
|
Helpers go in `libs/` and are reached via `repo.libs.<x>` from
|
|
bundle code, not from node files.
|
|
- **No statements.** No `if`/`for`/`def` at the top level. Use
|
|
expressions (ternaries, comprehensions) instead.
|
|
- **Silent drop on parse error.** Vanilla bundlewrap silently omits a
|
|
node/group whose file fails to eval. The maintainer patched
|
|
`groups.py` (commit `dc40295`) to print the error instead — but it
|
|
still skips the file. Symptom: `bw nodes` lists fewer nodes than you
|
|
expect. Cure: re-read the file and check for accidental imports or
|
|
syntax errors.
|
|
|
|
## Group inheritance order
|
|
|
|
Metadata merges along this chain (from the fork's docs):
|
|
|
|
```
|
|
all → location → os → machine → applications → node
|
|
```
|
|
|
|
`groups/all.py` is the universal base; `groups/{locations,os,machine,applications}/`
|
|
hold the per-axis groups; the node's own metadata wins last.
|
|
|
|
## Naming conventions
|
|
|
|
| Where | Convention | Example |
|
|
|---|---|---|
|
|
| `nodes/*.py` | `<location>.<role>.py` | `home.server.py`, `htz.mails.py` |
|
|
| `groups/*.py` | one file per group; subdir = purpose | `groups/applications/nextcloud.py`, `groups/os/debian-13.py` |
|
|
| `bundles/<name>` | lowercase, hyphen-separated | `backup-server`, `bind-acme`, `routeros-monitoring` |
|
|
| Custom items | `items/<type>.py` | `items/download.py` |
|
|
|
|
Underscores in bundle names appear only in `_old`-suffixed leftovers
|
|
(see below); don't introduce new ones.
|
|
|
|
## Suspension and soft-delete idioms
|
|
|
|
These conventions look like dead code; they aren't. Don't clean them up.
|
|
|
|
- **`*.py_` parked node.** A node file whose name ends in `.py_` is
|
|
silently excluded from `bw nodes` (the loader only matches `*.py`).
|
|
Used to keep decommissioned-but-not-deleted node configs in tree.
|
|
Example: `nodes/htz.l4d2.py_`.
|
|
- **"for now / disable / dummy / offline" markers.** Commits or comments
|
|
containing these phrases mark deliberate suspensions, not bugs. Check
|
|
`git log -- <file>` before re-enabling — the maintainer reverses
|
|
these manually when the underlying condition resolves.
|
|
- **`_old` / `_old2` directories.** Recovery buffers during big
|
|
refactors. Don't delete them without asking, even if they look
|
|
orphaned.
|
|
|
|
## Files agents must not modify
|
|
|
|
| Path | Why |
|
|
|---|---|
|
|
| `.secrets.cfg*` | vault key material |
|
|
| `.venv/` | uv-managed Python environment — uv owns rebuilds, don't hand-edit |
|
|
| `.cache/` | bw runtime cache |
|
|
| `.bw_debug_history` | shell history for `bw debug` |
|
|
| `/.cocoindex_code/` | local code index, not in git |
|
|
| `README.md` (root) | maintainer's personal TODO scratchpad — not project docs |
|
|
|
|
Treat `hooks/` and `items/` (custom item types) with extra care: they
|
|
affect `bw`'s behavior for the whole repo, not a single bundle.
|
|
|
|
## Working style
|
|
|
|
- **Iterative commits are normal.** The maintainer commits in small
|
|
checkpoints (`+`, `fix`, `whitespace`, terse one-liners). Don't
|
|
rebase WIP branches without asking. As an agent, prefer
|
|
complete-feeling commits over mimicking the iterative style.
|
|
- **Burst-state awareness.** Before writing into a subsystem, run
|
|
`git log --since='1 month ago' bundles/<x>` (or `nodes/<x>.py`).
|
|
≥10 recent commits means the subsystem is in flux; read the most
|
|
recent diffs first — your assumptions about its metadata shape may
|
|
already be stale.
|
|
- **Branch names.** PRs go through self-hosted Gitea/Forgejo. Branch
|
|
names are lowercase snake_case, descriptive
|
|
(`debian-13`, `htz.mails_debian_13_squash`, `l4d2_the_next`).
|
|
|
|
## Bundlewrap-version migration recipe
|
|
|
|
When the next bw major lands:
|
|
|
|
1. Read the upstream migration guide.
|
|
2. `git grep` for affected reactor / item patterns.
|
|
3. Rewrite each (one commit per pattern is fine — see Working style).
|
|
4. Bump the bundlewrap source in `pyproject.toml` (or `uv.lock` via
|
|
`uv sync --upgrade-package bundlewrap`) last.
|
|
|
|
Pattern from commit `186d503` (bw 4 → 5).
|