jarld can now answer lego's httpreq DNS provider (POST /api/v1/acme/httpreq/present and /cleanup), so lego and Traefik publish DNS-01 challenges with only environment variables, without the jarl CLI. lego's username and API token arrive as HTTP Basic auth, accepted on these routes only. The endpoint is off by default (acme.httpreq.enabled) and HTTPS-only by default: jarld's own TLS, or a trusted proxy that says so with X-Forwarded-Proto or Forwarded (the last value counts). Plain HTTP can be allowed from loopback only or everywhere. GET /api/v1/acme/httpreq shows how jarld sees a request, to check a proxy setup. jarl acme and the endpoint share their code in internal/acme. Documented in docs/acme.md, configuration.md, api.md and permissions.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
5.6 KiB
Markdown
113 lines
5.6 KiB
Markdown
# Developing Jarl
|
|
|
|
## Layout
|
|
|
|
```
|
|
cmd/jarld/ daemon: serve, init, import, adopt, check, render, resync, backup, user
|
|
cmd/jarl/ CLI
|
|
internal/
|
|
model/ configuration objects (State: views, ACLs, keys, zones, records, options) and changesets
|
|
dnsname/ DNS name handling: FQDNs, relative names, check-names rules, reverse zones
|
|
rr/ record data parsing/canonicalization (miekg/dns), TXT quoting, the record type catalog
|
|
namedconf/ named.conf lexer, parser and printer; address match lists
|
|
catalog/ every BIND 9.20 statement: grammar (from named.conf(5)) + ARM descriptions, embedded
|
|
validate/ whole-configuration validation and security lints
|
|
render/ State → named.conf, keys.conf and zone files, with a source map for error messages
|
|
bind/ named-checkconf, named-checkzone and rndc wrappers
|
|
engine/ changesets: expand requests into ops, replay, validate, render, check, deploy, reload, verify, revert;
|
|
import and adopt
|
|
importer/ reads an existing named.conf tree and its zone files
|
|
store/ SQLite (modernc.org/sqlite, no cgo): state, changesets, users, tokens, sessions, audit
|
|
auth/ argon2id passwords, tokens, sessions, grants and roles
|
|
server/ HTTP API, authentication, the web UI
|
|
stats/ query statistics: statistics-channel poller, response-log tailer, collector, separate SQLite store
|
|
web/static/ the web UI: plain ES modules, no build step, embedded with go:embed
|
|
api/, client/ JSON types and the Go client shared by server and CLI
|
|
acme/ ACME DNS-01 challenge records, shared by jarl acme and the httpreq endpoint
|
|
config/ jarld's YAML configuration
|
|
deploy/ systemd unit, sysusers.d and tmpfiles.d files
|
|
contrib/acme.sh/ acme.sh DNS API hook
|
|
contrib/lego/ program for lego's exec DNS provider
|
|
scripts/e2e.sh end-to-end test against a real named
|
|
```
|
|
|
|
The main flow is in `internal/engine/engine.go` (`applyOps`):
|
|
expand → replay → validate → render → check → install → reload → verify →
|
|
record. [concepts.md](concepts.md) describes it from the outside.
|
|
|
|
## Building and testing
|
|
|
|
```sh
|
|
make # bin/jarld, bin/jarl (CGO_ENABLED=0, version from ./VERSION)
|
|
make test # go vet, gofmt check, go test ./...
|
|
make e2e # scripts/e2e.sh
|
|
```
|
|
|
|
### Tests against real BIND
|
|
|
|
The render, engine and adopt tests run the real `named-checkconf`,
|
|
`named-checkzone`, `named`, `rndc` and `dig` when they can find them, and
|
|
skip otherwise. They are found through `JARL_BIND_ROOT` (an extracted
|
|
package tree) or on the system. To get them without root or AppArmor in the
|
|
way, extract the packages:
|
|
|
|
```sh
|
|
mkdir -p ~/bind-root/debs && cd ~/bind-root/debs
|
|
apt-get download bind9 bind9-utils bind9-libs bind9-dnsutils \
|
|
liblmdb0 libmaxminddb0 liburcu8t64 libuv1t64 libjemalloc2 libjson-c5 libnghttp2-14
|
|
for f in *.deb; do dpkg -x "$f" ~/bind-root; done
|
|
cd -
|
|
JARL_BIND_ROOT=~/bind-root make test
|
|
JARL_BIND_ROOT=~/bind-root make e2e
|
|
```
|
|
|
|
The tests start named unprivileged on random high ports in temporary
|
|
directories. On Debian/Ubuntu, AppArmor confines the installed
|
|
`/usr/sbin/named` to BIND's own directories, so the tests that *start*
|
|
named (engine integration tests, `make e2e`) need the extracted copy. The
|
|
checker tests work with the installed `bind9-utils` too.
|
|
|
|
`scripts/e2e.sh` builds a legacy BIND setup, starts named, runs
|
|
`jarld init`, `import`, `adopt` and `serve`, then drives every CLI command
|
|
(records, batches, changesets, views, ACLs, TSIG, options, users, tokens,
|
|
ACME hooks, audit) and checks the answers with `dig`. `KEEP=1` leaves named
|
|
and jarld running afterwards. The output says where; set `JARL_CLI_CONFIG`
|
|
to that directory's `cli.yaml` to keep using the CLI against it.
|
|
|
|
### The web UI
|
|
|
|
The UI is plain JavaScript modules under `internal/web/static/`, with no
|
|
framework and no build step. The logo is `assets/logo.svg` (a white figure on
|
|
a Nordic-blue `#2f5fb3` disc) with an all-white variant
|
|
`assets/logo-white.svg`. Both are generated by `scripts/logo.py` (shapes are
|
|
given for the left half and mirrored); edit the script, not the SVGs. The
|
|
login page's "JARL" wordmark is drawn as SVG paths in `assets/brand.js`, so
|
|
it needs no font and takes the theme's text and accent colors. The header's
|
|
"Jarl" uses a Latin subset of Cinzel Bold in `assets/fonts/` (SIL OFL; keep
|
|
`Cinzel-OFL.txt` next to it). All DOM is built with the `h()` helper in
|
|
`ui.js`, which never uses `innerHTML`, so record data and comments can't
|
|
inject markup. The Content-Security-Policy only allows the app's own
|
|
scripts and styles. To work on it, run jarld and reload the browser: the
|
|
files are embedded at build time, so rebuild jarld after changes.
|
|
|
|
## Conventions
|
|
|
|
* **Every user-visible change goes into `CHANGELOG.md`** (under
|
|
"Unreleased"), and the docs in `docs/` are updated in the same change.
|
|
* Errors that reach users say what is wrong and how to fix it, naming the
|
|
object.
|
|
* New validation rules get a stable `code`, a test in
|
|
`internal/validate/validate_test.go`, and a row in
|
|
[validation.md](validation.md).
|
|
* New API operations go through the engine (`internal/engine/expand.go`,
|
|
`ops.go`) so they get permissions, validation, history and revert for
|
|
free, and are documented in [api.md](api.md).
|
|
* `gofmt` and `go vet` clean (`make vet`).
|
|
|
|
## Releasing
|
|
|
|
1. Move the "Unreleased" section of `CHANGELOG.md` under the new version
|
|
and date.
|
|
2. Put the version in `VERSION`.
|
|
3. `make test && JARL_BIND_ROOT=... make e2e && make dist`.
|