Files
jarl/docs/install.md
T
abrown38andClaude Opus 5.5 3b7bee67f8 Add optional query statistics
Off by default (stats in jarl.yaml). When enabled, Jarl adds a loopback
statistics channel and a response log to named's configuration (merged
into existing logging/statistics-channels statements) and switches
response logging with rndc responselog, because the responselog option
only counts when named starts. jarld polls named's counters every minute
and tails the response log (resuming after restarts and across
rotation). It stores aggregates, never individual queries, in a separate
SQLite database: per-minute, hourly and daily counters (queries, result
codes, record types, views, zones, cache hits, upstream latency, DNSSEC)
and hourly/daily top lists (names, clients, NXDOMAIN and SERVFAIL names,
refused clients), each with its own retention. Clients can be anonymized
or excluded.

- Web UI: Statistics page with KPI tiles, queries and failed answers over
  time, a weekday x hour heatmap, top lists and breakdowns; SVG charts
  with hover/keyboard readouts, a table view and light/dark palettes
  validated for colour-vision deficiency.
- CLI: jarl stats status|summary|top|timeline|heatmap|breakdown.
- API: /api/v1/stats/*, behind a new "stats" permission resource.
- jarld brings named's configuration up to date with jarl.yaml at
  startup (engine.SyncConfig), never overwriting hand-edited files.
- resync now refuses to overwrite drifted files without force, as
  documented.
- tmpfiles.d creates /var/log/named (allowed by named's AppArmor profile
  on Debian 13 and Ubuntu).
- The e2e harness runs named with -f like the Debian unit.
- docs/stats.md, plus API, CLI, configuration, permissions and README
  updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:21:34 +00:00

269 lines
9.1 KiB
Markdown

# Installing Jarl and adopting an existing BIND server
This guide targets Debian/Ubuntu with BIND 9.20 (`bind9` package,
`/etc/bind`, user and group `bind`, AppArmor). Other layouts work too: every
path is configurable in `/etc/jarl/jarl.yaml` (see
[configuration.md](configuration.md)).
The migration is designed to be safe. Your current configuration is
imported into Jarl and checked with `named-checkconf` before named is
touched. Adopting keeps a copy of the old `named.conf`, and if named fails
to load Jarl's configuration the old one is put back automatically.
## 1. Build
Jarl is two static binaries with no runtime dependencies (SQLite is
compiled in). Building needs Go 1.27 or newer, on any machine:
```sh
make # bin/jarld and bin/jarl
make test # optional: unit and integration tests
make dist # optional: dist/jarl-<version>-linux-{amd64,arm64}.tar.gz
```
If you build on another machine, copy the binaries plus the `deploy/`,
`docs/` and `contrib/` directories to the DNS server.
## 2. Install the files
On the DNS server, as root:
```sh
make install # or: make install PREFIX=/usr
```
This installs:
| File | Purpose |
|------|---------|
| `/usr/local/bin/jarld`, `/usr/local/bin/jarl` | daemon and CLI |
| `/etc/systemd/system/jarl.service` | systemd unit (`Type=notify`, sandboxed) |
| `/usr/lib/sysusers.d/jarl.conf` | creates the `jarl` system user and adds it to group `bind` |
| `/usr/lib/tmpfiles.d/jarl.conf` | creates `/etc/jarl`, `/var/lib/jarl`, `/etc/bind/jarl`, `/var/lib/bind/jarl`, `/var/cache/bind/jarl` |
| `/etc/jarl/jarl.yaml` | example configuration (only if none exists) |
| `/usr/local/share/doc/jarl/` | these docs and the acme.sh hook |
Then create the user and directories:
```sh
systemd-sysusers jarl.conf
systemd-tmpfiles --create jarl.conf
chgrp jarl /etc/jarl/jarl.yaml
systemctl daemon-reload
```
Without `make` (from a release tarball), the same steps are:
```sh
install -m 0755 jarld jarl /usr/local/bin/
install -m 0644 deploy/systemd/jarl.service /etc/systemd/system/
install -m 0644 deploy/sysusers.d/jarl.conf /usr/lib/sysusers.d/
install -m 0644 deploy/tmpfiles.d/jarl.conf /usr/lib/tmpfiles.d/
systemd-sysusers jarl.conf && systemd-tmpfiles --create jarl.conf
jarld config --example > /etc/jarl/jarl.yaml
chgrp jarl /etc/jarl/jarl.yaml && chmod 0640 /etc/jarl/jarl.yaml
systemctl daemon-reload
```
### Why the jarl user is in group bind
jarld runs as `jarl`, not root. Membership of `bind` lets it read named's
configuration and `/etc/bind/rndc.key` (for `named-checkconf` and `rndc`),
and give the files it generates to group `bind` so named can read them.
named in turn can't write Jarl's configuration (`/etc/bind/jarl` is 2750),
but it can write journals in `/var/lib/bind/jarl` (2770).
## 3. Configure
Edit `/etc/jarl/jarl.yaml`. The defaults match Debian's BIND 9.20 layout,
so usually only `listen` and `tls` need changes:
```yaml
listen:
- "127.0.0.1:8053" # always keep localhost for the local CLI and hooks
- "192.168.1.2:8053" # a management address for the web UI
tls:
cert_file: /etc/jarl/tls/fullchain.pem
key_file: /etc/jarl/tls/privkey.pem
```
Keep the web UI and API **off the internet**: listen on localhost and a
management network only, and restrict the port in your firewall. When the
server becomes internet-facing for DNS, that changes nothing for Jarl.
A plain-HTTP listener is fine on localhost. Anything reachable over the
network should use TLS, either with `tls:` above or behind a reverse proxy
that terminates TLS (set `trusted_proxies` so client IPs in the audit log
are right). See "TLS certificate from Let's Encrypt" below.
Check the result with `jarld config` (prints the effective configuration).
## 4. Back up BIND
Jarl keeps a copy of `named.conf`, but take your own backup anyway:
```sh
tar czf /root/bind-before-jarl-$(date +%F).tgz /etc/bind /var/lib/bind /var/cache/bind
```
## 5. Create the database and the first administrator
```sh
sudo -u jarl jarld init --token \
--nameservers ns1.adbraun.lol. --contact hostmaster@adbraun.lol
```
It asks for the administrator's password (user `admin`, change it with
`-u NAME`). `--token` also prints an API token for the CLI, shown only
once. `--nameservers` and `--contact` are the defaults for zones you create
later, and you can change them in the UI or with `jarl settings set`.
## 6. Import the current configuration
```sh
sudo -u jarl jarld import --dry-run # see what would be imported
sudo -u jarl jarld import
```
The importer reads `/etc/bind/named.conf` and everything it includes: ACLs,
keys, options, logging, views, zones, and every primary zone file (with
comments, `$TTL`, `$ORIGIN` and `$INCLUDE`; `$GENERATE` ranges are expanded
into individual records). It converts legacy
spellings (`master`/`slave`/`masters`) and keeps `rndc.key` as an include.
If everything validates, the import is applied as changeset #2. Jarl writes
its files into `/etc/bind/jarl` and `/var/lib/bind/jarl`, but **named keeps
running on the old configuration**. If there are problems, the import is
left as an open changeset to fix in the web UI or CLI first, and the
command tells you what is wrong.
Look at what Jarl generated before going further:
```sh
less /etc/bind/jarl/named.conf
ls -R /var/lib/bind/jarl
sudo -u jarl jarld check # validation + named-checkconf -z
```
## 7. Adopt: switch named over to Jarl
```sh
sudo jarld adopt
```
This needs root because it rewrites `/etc/bind/named.conf`:
1. bumps the serial of every primary zone, so secondaries and caches notice
the change,
2. saves `named.conf` as `named.conf.pre-jarl-<date>` and replaces it with a
single `include "/etc/bind/jarl/named.conf";`,
3. runs `named-checkconf`, `rndc reconfig` and reloads every zone,
4. checks that named serves every zone with the new serial.
If any step fails, the previous `named.conf` is restored and named is
reloaded. Afterwards, check that the answers match:
```sh
dig @127.0.0.1 adbraun.lol SOA +short
dig @127.0.0.1 -x 192.168.1.1 +short
```
From now on, change DNS only through Jarl. Hand edits to the generated
files are detected as drift and are never silently overwritten (see
[operations.md](operations.md)).
**Going back.** `cp /etc/bind/named.conf.pre-jarl-<date> /etc/bind/named.conf && rndc reconfig`
restores the old setup. Your old zone files were never modified.
## 8. Start the daemon
```sh
systemctl enable --now jarl
systemctl status jarl
journalctl -u jarl -f
```
Open `https://<management-address>:8053/` and log in as `admin`.
## 9. Set up the CLI
The CLI can run on the server or on any machine that can reach jarld.
```sh
jarl config set server https://ns1.adbraun.lol:8053 # or http://127.0.0.1:8053 on the server
jarl config set token # paste the token from 'jarld init' at the prompt
jarl status
jarl zone list
```
The CLI's settings are stored in `~/.config/jarl/config.yaml` (mode 0600).
`JARL_SERVER` and `JARL_TOKEN` override them, which is handy in scripts.
Create more tokens in the web UI (Account → API tokens) or with
`jarl token create NAME`. See [cli.md](cli.md).
## Optional: query statistics
To see query volume, top names and clients in the UI and CLI, set
`stats: {enabled: true}` in `/etc/jarl/jarl.yaml` and restart jarl. See
[stats.md](stats.md).
## TLS certificate from Let's Encrypt
Jarl can publish the DNS-01 challenge for its own certificate. On the DNS
server, run certbot with the hooks against the local listener:
```sh
# As yourself (with an admin token configured): a service account and a
# token that can only touch _acme-challenge TXT records.
jarl user create certbot --no-password --role acme
jarl token create certbot --user certbot --role acme -q > certbot.token
sudo install -m 0600 certbot.token /etc/jarl/certbot.token && rm certbot.token
# certbot runs as root and reads the token from the file.
sudo certbot certonly --manual --preferred-challenges dns \
--manual-auth-hook 'jarl --server http://127.0.0.1:8053 --token @/etc/jarl/certbot.token acme certbot-auth' \
--manual-cleanup-hook 'jarl --server http://127.0.0.1:8053 --token @/etc/jarl/certbot.token acme certbot-cleanup' \
-d ns1.adbraun.lol
```
certbot keeps its keys readable only by root, so give jarld a copy after
each renewal with a deploy hook, `/etc/letsencrypt/renewal-hooks/deploy/jarl`:
```sh
#!/bin/sh
set -e
install -d -m 0750 -g jarl /etc/jarl/tls
install -m 0640 -g jarl "$RENEWED_LINEAGE/fullchain.pem" "$RENEWED_LINEAGE/privkey.pem" /etc/jarl/tls/
systemctl reload jarl
```
`systemctl reload jarl` sends SIGHUP, and jarld rereads the certificate
without dropping connections. More in [acme.md](acme.md).
## Upgrading
```sh
make && sudo make install
sudo systemctl restart jarl
```
Database migrations run automatically at startup. Before an upgrade, back
up the database (it is safe while jarld runs):
```sh
sudo -u jarl jarld backup /var/lib/jarl/jarl-$(date +%F).db
```
## Uninstalling
```sh
cp /etc/bind/named.conf.pre-jarl-<date> /etc/bind/named.conf # or write a named.conf that includes Jarl's files
rndc reconfig
systemctl disable --now jarl
make uninstall
```
`/etc/bind/jarl` and `/var/lib/bind/jarl` contain plain named.conf and zone
files that keep working without Jarl. `make uninstall` leaves them and
Jarl's database in place.