|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +`fastops` is an nbdev-based Python library providing fluent builders for Docker, Compose, Caddy, Cloudflare DNS/tunnels, and Hetzner VPS provisioning — all composable from Python with no YAML/config files by hand. |
| 6 | + |
| 7 | +## Critical Rule: nbdev Workflow |
| 8 | + |
| 9 | +**Never edit `.py` files in `fastops/` directly.** They are autogenerated. Only edit notebooks in `nbs/` and run `uv run nbdev-prepare` to regenerate `fastops/*.py`, `_modidx.py`, and docs. |
| 10 | + |
| 11 | +Use hyphenated nbdev CLI commands (`nbdev-prepare`, `nbdev-export`, etc.), not underscore variants. |
| 12 | + |
| 13 | +Mapping: |
| 14 | + |
| 15 | +| Notebook | Module | |
| 16 | +|---|---| |
| 17 | +| `nbs/00_core.ipynb` | `fastops/core.py` — Dockerfile, Cli, Docker, Compose, app presets, env/secret helpers | |
| 18 | +| `nbs/01_cloudflare.ipynb` | `fastops/cloudflare.py` — CF wrapper, DNS records, tunnels | |
| 19 | +| `nbs/02_proxy.ipynb` | `fastops/proxy.py` — Caddyfile builder, caddy_svc, cloudflared_svc, crowdsec | |
| 20 | +| `nbs/05_vps.ipynb` | `fastops/vps.py` — cloud-init, Hetzner hcloud, SSH/rsync deploy | |
| 21 | +| `nbs/06_ship.ipynb` | `fastops/ship.py` — end-to-end: provision → build_stack → ship | |
| 22 | + |
| 23 | +## Build System |
| 24 | + |
| 25 | +- **Hatchling** (not setuptools). Build config lives in `pyproject.toml` under `[tool.hatch.build]`. Do NOT edit `MANIFEST.in` for build includes. |
| 26 | +- Package management: `uv add <pkg>` (never `pip install`). Run tools with `uv run`. |
| 27 | + |
| 28 | +## Architecture & Patterns |
| 29 | + |
| 30 | +### Immutable fluent builders (core pattern) |
| 31 | +`Dockerfile`, `Compose`, and `Caddyfile` all extend `fastcore.L` (an immutable list). Every method returns a **new instance** via `_add()` / `_new()` — never mutates. Example: |
| 32 | +```python |
| 33 | +df = Dockerfile().from_('python','3.12-slim').workdir('/app').cmd(['python','app.py']) |
| 34 | +``` |
| 35 | + |
| 36 | +### `Cli` base + `__getattr__` dispatch |
| 37 | +`Cli` turns unknown attribute access into CLI subcommands: `Docker().image('prune', f=True)` → `docker image prune -f`. The `_mk_flags()` helper converts kwargs to CLI flags (single-char → `-k v`, multi-char → `--key=v`). |
| 38 | + |
| 39 | +### `@patch` from fastcore |
| 40 | +Methods added after class definition use `@patch` (e.g., `Dockerfile.build`, `Dockerfile.inst_uv`). Check for patched methods in the same notebook — they won't be in the class body. |
| 41 | + |
| 42 | +### Service composition |
| 43 | +`caddy_svc()`, `cloudflared_svc()`, `crowdsec()` return `dict` kwargs meant to be splatted into `Compose().svc('name', **result)`. The `ship._build_stack()` function orchestrates the full topology. |
| 44 | + |
| 45 | +## Code Style |
| 46 | + |
| 47 | +- **fastcore idioms**: `store_attr`, `patch`, `L`, `listify`, `joins`, `bind`, `filter_values`, `Path` (fastcore's, not pathlib's). |
| 48 | +- **Succinct variable names**, dense functions, fast.ai style. No verbose docstrings — one-line descriptions only. |
| 49 | +- Trailing-underscore convention for Python-keyword collisions: `from_()`, `as_=`, `exec_()`. |
| 50 | + |
| 51 | +## Key External Dependencies |
| 52 | + |
| 53 | +- `fastcore` — foundational utilities (`L`, `patch`, `bind`, `run`, `Path`) |
| 54 | +- `cloudflare` — official Cloudflare Python SDK (not requests-based) |
| 55 | +- `hcloud` — Hetzner Cloud Python client |
| 56 | +- `fastcloudinit` — cloud-init YAML generation |
| 57 | +- `pyyaml`, `python-dotenv`, `keyring` — config/secrets |
| 58 | + |
| 59 | +## Environment Variables |
| 60 | + |
| 61 | +| Variable | Used by | |
| 62 | +|---|---| |
| 63 | +| `CLOUDFLARE_API_TOKEN` | `cloudflare.py` — CF class | |
| 64 | +| `HCLOUD_TOKEN` | `vps.py` — Hetzner client | |
| 65 | +| `DOCKR_RUNTIME` | `core.py` — set to `podman` for podman support | |
| 66 | +| `FASTOPS_VPS_HOST` / `FASTOPS_VPS_NAME` | `ship.py` — stored by `provision()` | |
| 67 | + |
| 68 | +Config persists to `~/.config/fastops/.env` via `env_set()`/`env_get()`. Secrets use OS keychain via `keyring` with `secret_set()`/`secret_get()`. |
| 69 | + |
| 70 | +## Testing |
| 71 | + |
| 72 | +Tests live inline in the notebooks as cells. Run: `uv run nbdev-prepare` (exports + tests + docs). For pytest: `uv run pytest`. Include test cells when adding features to notebooks. |
0 commit comments