Jarl keeps a BIND 9.20 server's whole configuration (views, ACLs, TSIG keys, zones of every type, records, named.conf options) in SQLite and renders named.conf, keys.conf and zone files from it. Every change goes through one changeset pipeline: whole-configuration validation, named-checkconf -z and named-checkzone in a staging directory, atomic install, rndc reconfig/reload, serial verification with rndc zonestatus, and rollback if named does not load the change. Changes apply one at a time by default or are bundled and applied together. Applied changesets can be reverted. - jarld: the daemon with the REST API and an embedded web UI, plus import, adopt, check, resync, backup and local user recovery commands - jarl: the CLI, including ACME DNS-01 hooks for certbot and acme.sh - users with argon2id passwords and CSRF-protected sessions; hashed API tokens with granular grants and roles; an audit log - systemd unit, sysusers.d/tmpfiles.d files, Makefile - unit tests, integration tests against a real named, scripts/e2e.sh - docs/ guides, README and CHANGELOG Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
5.8 KiB
Markdown
127 lines
5.8 KiB
Markdown
# Jarl
|
|
|
|
*A Jarl rules a Hold; this one rules your zones.*
|
|
|
|
Jarl manages a BIND 9 (named) server: zones, records of every type, views,
|
|
ACLs, TSIG keys and any named.conf option. It has a web UI, a REST API and
|
|
a command line client. All three go through the same validated, audited,
|
|
all-or-nothing change pipeline, and nothing reaches named before BIND's own
|
|
checkers have accepted it.
|
|
|
|
```
|
|
$ jarl record add adbraun.lol nas A 192.168.1.20 --ptr
|
|
add nas.adbraun.lol. IN A 192.168.1.20
|
|
add 20.1.168.192.in-addr.arpa. IN PTR nas.adbraun.lol.
|
|
zone 1.168.192.in-addr.arpa serial 2026100700 -> 2026100701
|
|
zone adbraun.lol serial 2026100701 -> 2026100702
|
|
applied (changeset 12)
|
|
|
|
$ jarl record add adbraun.lol www A 192.168.1.99
|
|
error: www.adbraun.lol has a CNAME, so it can't have any other records (found A); a CNAME must be the only record at its name (RFC 1034) [zone adbraun.lol]
|
|
```
|
|
|
|
## Features
|
|
|
|
* **Everything in one place**: primary, secondary, forward, stub, mirror,
|
|
hint, redirect and in-view zones, reverse zones from a CIDR, split DNS with
|
|
views, ACLs, TSIG keys, and every BIND 9.20 option at server, view, zone
|
|
and top level, with the ARM's description and grammar built in.
|
|
* **Safe changes.** Every change is validated as a whole (CNAME rules,
|
|
glue, delegations, RRset TTLs, references, option syntax, open-resolver
|
|
and open-transfer lints), checked with `named-checkconf -z`, installed
|
|
atomically, reloaded, and verified with `rndc zonestatus`. If named
|
|
doesn't load it, the previous files are put back.
|
|
* **Changesets**: apply edits one at a time, or bundle them, review the
|
|
diff, and apply them together. Every applied changeset can be reverted.
|
|
Serials are bumped automatically.
|
|
* **Users and API tokens with granular permissions**, e.g. a token that may
|
|
only write `_acme-challenge` TXT records in one zone. Passwords use
|
|
argon2id, sessions have CSRF protection, logins are rate limited.
|
|
* **Audit log** of every login, change and token, with user, token, IP and
|
|
outcome.
|
|
* **Let's Encrypt DNS-01** built in: `jarl acme` plus certbot and acme.sh
|
|
hooks, including CNAME delegation to a dedicated ACME zone.
|
|
* **Adopts an existing server**: imports named.conf and zone files
|
|
(comments included), then switches named over with an automatic
|
|
fallback.
|
|
* One static Go binary for the daemon (web UI embedded, SQLite built in) and
|
|
one for the CLI, run by a sandboxed systemd service as an unprivileged
|
|
user.
|
|
|
|
## Quick start
|
|
|
|
On a Debian/Ubuntu BIND 9.20 server ([install.md](docs/install.md) has the
|
|
details):
|
|
|
|
```sh
|
|
make && sudo make install
|
|
sudo systemd-sysusers jarl.conf && sudo systemd-tmpfiles --create jarl.conf
|
|
sudo chgrp jarl /etc/jarl/jarl.yaml
|
|
sudo -u jarl jarld init --token # first admin + a CLI token
|
|
sudo -u jarl jarld import # read the current named.conf and zones
|
|
sudo jarld adopt # switch named over (keeps a backup, rolls back on failure)
|
|
sudo systemctl enable --now jarl
|
|
|
|
jarl config set server http://127.0.0.1:8053
|
|
jarl config set token # paste the token from 'jarld init'
|
|
jarl status
|
|
```
|
|
|
|
Then open the web UI at `http://127.0.0.1:8053/`, or set `listen` and `tls`
|
|
in `/etc/jarl/jarl.yaml` to reach it from your management network.
|
|
|
|
## Documentation
|
|
|
|
| | |
|
|
|-|-|
|
|
| [docs/concepts.md](docs/concepts.md) | How Jarl works: source of truth, changesets, the apply pipeline, authentication. Read this first |
|
|
| [docs/install.md](docs/install.md) | Installing, importing and adopting an existing server, TLS, upgrading |
|
|
| [docs/configuration.md](docs/configuration.md) | `/etc/jarl/jarl.yaml` reference |
|
|
| [docs/cli.md](docs/cli.md) | The `jarl` command line client |
|
|
| [docs/api.md](docs/api.md) | REST API reference |
|
|
| [docs/permissions.md](docs/permissions.md) | Users, tokens, grants and roles |
|
|
| [docs/acme.md](docs/acme.md) | Let's Encrypt DNS-01 with certbot and acme.sh |
|
|
| [docs/validation.md](docs/validation.md) | Every check Jarl makes |
|
|
| [docs/operations.md](docs/operations.md) | Backups, drift, failures, recovery, monitoring |
|
|
| [docs/development.md](docs/development.md) | Code layout, tests against real BIND, conventions |
|
|
| [CHANGELOG.md](CHANGELOG.md) | Changes per release |
|
|
|
|
## The web UI
|
|
|
|
The UI is built for getting things done rather than for looks. It has
|
|
these pages:
|
|
|
|
* **Dashboard**: named status, adoption, drift, recent changes, open
|
|
problems.
|
|
* **Zones**: list, create (fields adapt to the zone type), per-zone records
|
|
with inline editing and type-specific hints, settings (SOA, TTL, serial
|
|
policy, view), options, the generated zone file, and pasting a zone file
|
|
to merge or replace records.
|
|
* **Views, ACLs, TSIG keys, Server options**: the options editor knows every
|
|
BIND 9.20 statement for the scope and shows its description, grammar and
|
|
a value field that fits it.
|
|
* **Changes**: pending edits with the diff, named-checkconf output and
|
|
problems, applied as one changeset. History lists applied changesets with
|
|
their diffs and a revert button.
|
|
* **Audit, Users, Account** (password, API tokens) and **Search**.
|
|
|
|
By default, edits are collected as pending changes. Pending rows are shown
|
|
inline (green = new, yellow = changed, struck out = deleted) until you
|
|
review and apply them. Switch on "Apply edits immediately" to skip the
|
|
review step.
|
|
|
|
## Requirements
|
|
|
|
* BIND 9.20, with `named-checkconf`, `named-checkzone` and `rndc`. The
|
|
option catalog follows 9.20. 9.18 will probably work, because
|
|
named-checkconf still checks every change, but it is untested.
|
|
* Linux with systemd for the packaged service. Defaults match
|
|
Debian/Ubuntu, and other layouts are configurable.
|
|
* Go 1.27+ to build.
|
|
|
|
## Status
|
|
|
|
Version 0.1.0, the first release. It is feature-complete for one server's
|
|
configuration. DNSSEC signing and managing several servers are not handled
|
|
yet ([concepts.md](docs/concepts.md#what-jarl-deliberately-doesnt-do-yet)).
|