# Working on Housecarl Notes for coding agents (and people) changing this repository. Read this first, then [docs/design.md](docs/design.md) (how it fits together and what 0.1 leaves out) and [docs/development.md](docs/development.md) (layout, tests, releasing). Housecarl is a firewall manager for Linux: `housecarld` keeps the machine's nftables ruleset and serves a REST API and a web UI; `hc` is the CLI; SQLite holds everything. It is the third of a set with Jarl (the BIND manager, `../jarl`) and Steward (the DHCP server, `../steward`) and copies their ways: the same auth, socket and web UI helpers, the same release process, the same look. When in doubt, do it the way Jarl and Steward do. Go module `housecarl`, `CGO_ENABLED=0`, Linux only for housecarld. ## Ground rules * **The database is the source of truth.** The ruleset is rendered from it; never read nftables' state back as configuration (an importer would be the only exception). * **Every change goes through one engine**: edit a copy of the configuration, validate the whole result, render, check with `nft -c -f`, apply with `nft -f`, and save the objects, the history entry and the audit record together. Never write to the store from a handler. * **Housecarl's own table only.** Write `inet housecarl` and nothing else; never flush the whole ruleset (Docker, libvirt and others keep theirs). * **Don't lock the user out.** Changes made over the network are applied on probation and rolled back unless confirmed (docs/design.md). An empty configuration applies nothing. But never force a rule that keeps SSH or the web UI open: the maintainer doesn't want one. * **Shadow mode changes no packet** (docs/shadow.md): anything new the renderer writes that drops, rejects, translates or alters packets needs its shadow form (counted, marked `[would …]`) or must be left out there. `TestShadow` fails on what slips through. * **Stay within nftables 1.1.3** (Debian 13). The dev machine's newer nft accepts things 1.1.3 refuses; the tests with `HOUSECARL_NFT` and the e2e test use 1.1.3. * **Every user-visible change updates `CHANGELOG.md`** under `## [Unreleased]`, and the matching `docs/` page in the same change. New validation codes get a case in `internal/validate/validate_test.go` and a row in `docs/validation.md`. * **Errors** say what is wrong and how to fix it, naming the object. * **Schema changes** append a migration; never edit a shipped one. * `make test` runs `go vet` and a `gofmt -l` check. `goimports` isn't installed, so fix imports by hand. ## The logo and wordmark * The masters are `artwork/logo.svg` (the carved doorway) and `artwork/logo-small.svg` (without its carvings, for under about 64 px). Each is one evenodd path in a shared viewBox; their color comes from CSS (`--logo`, lighter in the dark theme). * `make logo` (`scripts/logo.py`) copies them into the web UI (`assets/logo.js`, and `assets/logo.svg`, the favicon, from the small one) and runs `scripts/banner.py`, which rebuilds `artwork/banner.svg`. Never edit those outputs by hand. * The wordmark's letters are paths in `assets/brand.js`, in the angular style of Jarl's and Steward's (A, R and L are Jarl's, S and E Steward's). `banner.py` reads them from there. ## Testing ```sh make test # vet + gofmt check + unit tests make e2e # real traffic through a router container (needs Docker) ``` * **Docker** on this machine needs `DOCKER_HOST=unix:///var/run/docker.sock`. `docker build` can't reach the network here: images are made with `docker run` + `docker commit` (scripts/e2e.sh makes `housecarl-nft`). The e2e test prints `RESULT: N passed, 0 failed`; run it after any change to `render`, `nft`, `engine`, `server`, `importer` or the CLI. * **Real nft in unit tests:** `HOUSECARL_NFT="docker run --rm -i --cap-add NET_ADMIN housecarl-nft nft"` and `HOUSECARL_NFT_IMAGE=housecarl-nft`; without them those tests skip. Unprivileged namespaces (`unshare -rn`) are blocked here by AppArmor. * **The systemd sandbox is only tested by a package test**: `make deb`, installed in a `debian:trixie` container running systemd (`--privileged --cgroupns=host /sbin/init`). * **The web UI** has no JS tests. Bundle `assets/app.js` with esbuild to catch syntax and import errors, and look at it in headless Chrome against the e2e lab (`KEEP=1`, web UI on `10.88.10.1:8070`). ## Web UI rules As in Jarl and Steward: * Build DOM only with `h()` from `ui.js`, never `innerHTML`. * The CSP is `self` only: no inline styles or scripts. Use a class, or set `el.style.prop`. * Colors are the CSS custom properties in `style.css`, with light and dark values; reuse them. * Rebuild housecarld after editing, since the files are embedded. * Function over polish. Leave pick-list checkboxes unticked by default. ## Git and releases The maintainer's rules: * **Commit only when asked. Never push.** The maintainer pushes, to Gitea at gitea.adbraun.lol. * **Branches** are `dev-X.Y.Z`, never named like a tag. * **A release** is a commit "Release X": it sets `VERSION`, updates the README status line, and (for a final release) moves `[Unreleased]` to `## [X] - YYYY-MM-DD`. The annotated tag is `vX` with message "Housecarl X". ## Shell hygiene * **Never kill processes by pattern** (`pkill -f`). The pattern matches the shell running it. Find the PID with `ps` and kill that. * **Run long-lived test servers in the background mode of your tool**, not with `&` in a shell that exits. * **Never apply a test ruleset to this machine's own nftables.** Test in a network namespace or a container.