Zones get a DNSSEC setting: named signs them with BIND's default (or a custom) dnssec-policy and inline signing, with keys in a per-view key directory. Jarl shows the keys, the DS record and when it is safe to publish it, checks the parent's DS records (check-ds, which also tells named), and refuses steps that would break a signed domain: switching straight to unsigned, finishing a turn-off too early, deleting or moving a signed zone, TTLs above max-zone-ttl. Turning off goes through the insecure policy; wipe starts over once the parent has no DS. Backups store the DNSSEC keys and restore puts them back for named; the backup unit gets CAP_DAC_READ_SEARCH to read them. Jarl raises a zone's serial above named's whenever it writes an older one (after a restore). The serials check compares the signed serial. With a Porkbun API key in jarl.yaml, the registrar's name servers, glue records and DS records can be shown and changed, each change checked first (dry run, force to override). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 KiB
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).
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:
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, or build a Debian
package (below) and copy that.
2. Install the files
There are two ways: a Debian package, which is easiest to upgrade and
remove, or make install.
With the Debian package
make deb # dist/jarl_0.1.0_amd64.deb
make deb DEB_ARCH=arm64 # for another architecture (also armhf, i386, ...)
sudo apt install ./dist/jarl_0.1.0_amd64.deb
make deb needs only dpkg-deb (no root, no debhelper) and cross-builds
for any architecture Go supports. apt also installs bind9 (recommended)
if it's missing. Installing the package:
- puts the binaries in
/usr/bin, bash completion in/usr/share/bash-completion/completions, the systemd units in/usr/lib/systemd/system(jarl.service, andjarl-backup.servicewith its dailyjarl-backup.timer), the sysusers and tmpfiles files in/usr/lib, and the docs and contrib files in/usr/share/doc/jarl; - creates the
jarluser (in groupbind) and Jarl's directories, and writes/etc/jarl/jarl.yamlfrom the example on the first install. No file in/etcbelongs to the package, so your configuration is never replaced or asked about on upgrades; - turns on the daily database backup,
jarl-backup.timer(operations.md). It does nothing until the database exists. If you turn it off (systemctl disable jarl-backup.timer), upgrades leave it off; - doesn't start jarld: continue with 3. Configure, then
systemctl enable --now jarlin step 8.
Upgrading: build and install the newer package the same way. A running jarld is restarted, and upgrades its database as it starts.
Removing (sudo apt remove jarl) stops jarld and the backup timer and
removes the program, but named keeps running with the configuration Jarl
last wrote. Even apt purge jarl keeps /etc/jarl, /var/lib/jarl (the
database), /var/backups/jarl and the
files in /etc/bind/jarl, /var/lib/bind/jarl and /var/cache/bind/jarl,
since named.conf may still include them. Remove them by hand once named no
longer uses them.
With make install
On the DNS server, as root:
make install # or: make install PREFIX=/usr
This installs:
| File | Purpose |
|---|---|
/usr/local/bin/jarld, /usr/local/bin/jarl |
daemon and CLI |
/usr/local/share/bash-completion/completions/jarl, .../jarld |
bash completion (loaded automatically by the bash-completion package) |
/etc/systemd/system/jarl.service |
systemd unit (Type=notify, sandboxed) |
/etc/systemd/system/jarl-backup.service, jarl-backup.timer |
the daily database backup, DNSSEC keys included (operations.md) |
/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, /var/backups/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:
systemd-sysusers jarl.conf
systemd-tmpfiles --create jarl.conf
chgrp jarl /etc/jarl/jarl.yaml
systemctl daemon-reload
systemctl enable --now jarl-backup.timer # daily backups; idle until the database exists
Without make (from a release tarball), the same steps are:
install -m 0755 jarld jarl /usr/local/bin/
install -m 0644 deploy/systemd/jarl.service deploy/systemd/jarl-backup.service deploy/systemd/jarl-backup.timer /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
systemctl enable --now jarl-backup.timer
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:
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:
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
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
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:
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
sudo jarld adopt
This needs root because it rewrites /etc/bind/named.conf:
- bumps the serial of every primary zone, so secondaries and caches notice the change,
- saves
named.confasnamed.conf.pre-jarl-<date>and replaces it with a singleinclude "/etc/bind/jarl/named.conf";, - runs
named-checkconf,rndc reconfigand reloads every zone, - 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:
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).
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
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
On the server itself, root needs nothing: sudo jarl status and every
other command use jarld's local socket without a token
(cli.md).
On other machines, or as an ordinary user, give the CLI a server and a token:
jarl config set server https://ns1.adbraun.lol:8053
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.
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.
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:
# 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:
#!/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.
Upgrading
make && sudo make install
sudo systemctl restart jarl
Database migrations run automatically at startup. Before an upgrade, take
a backup (it is safe while jarld runs); jarld restore puts it back if
needed:
sudo jarld backup
Uninstalling
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 jarl-backup.timer
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,
Jarl's database and its backups in place.