Add backups, token expiry warnings and name server checks

- Daily database backups: jarl-backup.timer runs 'jarld backup', which
  writes jarl-<UTC time>.db into backup.dir, checks it before naming it,
  and prunes by the time in the name while always keeping the newest
  keep_at_least. New backup: section in jarl.yaml; the dashboard and
  'jarl status' show the newest backup and warn when it is stale or
  failed. The Debian package enables the timer.
- 'jarld restore [FILE | latest]': checks the backup, shows it next to
  the current database, keeps the current one aside and records the
  restore. 'jarld serve' holds a database lock, so a restore can't run
  under a live jarld and two jarlds can't share a database.
- Token expiry warnings from 14 days ahead: dashboard, token lists, a
  daily log line, and a CLI warning via the Jarl-Token-Expires header;
  'jarl token list --expiring'.
- 'jarl zone serials' and the zone page's Name servers tab: compare the
  serial on a primary zone's secondaries (NS records, also-notify) or a
  secondary zone's primaries with this server's.
- 'jarl zone delegation': for zones on the internet, check the parent's
  delegation and glue against the zone; an undelegated zone gets a note.
- Server: Jarl/<version> header; CLI exit code 2 for failed checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-08 19:02:34 +00:00
1 parent 1afed741bf
commit 293e6ddd33
47 files changed
+3717 -95

No files matched your search

+45
View File
@@ -104,6 +104,8 @@ bind:
named_checkzone: $CHECKZONE
rndc: $RNDC
rndc_args: ["-k", "$E/legacy/rndc.key", "-s", "127.0.0.1", "-p", "$CTL"]
backup:
dir: $E/backups
EOF
cd $E
echo "secret-pass-123" | $JARLD -c jarl.yaml init --password-stdin --token --nameservers ns1.adbraun.test. --contact admin@adbraun.test > init.out 2>&1 || { cat init.out; exit 1; }
@@ -286,6 +288,49 @@ run "acme set --view external only" 0 $J acme unset adbraun.test --view internal
expect_dig "acme TXT removed from internal" "" _acme-challenge.adbraun.test TXT
run "zone file" 0 $J zone file adbraun.test --view external
contains "_acme-challenge"
# Name server checks: the external zone's NS (203.0.113.1) doesn't answer;
# the internal one lists only this server.
run "zone serials: internal, only this server" 0 $J zone serials adbraun.test --view internal
contains "this server"
run "zone serials: unreachable secondary (exit 2)" 2 $J zone serials adbraun.test --view external
contains "NO ANSWER"
# Server header and token expiry warnings
if curl -sI http://127.0.0.1:$HTTP/healthz | grep -qi "^Server: Jarl/"; then ok "Server header"; else bad "no Server: Jarl header"; fi
SOON=$($J token create soon --expires 3d -q)
run "expiring token works" 0 $J --token "$SOON" whoami
contains "warning: the API token expires"
run "token list --expiring" 0 $J token list --expiring
contains "soon"
run "status lists the expiring token" 0 $J status
contains "Token:.*soon"
# Backups and restore
run "jarld backup" 0 $JARLD -c jarl.yaml backup
contains "Database backed up to $E/backups/jarl-"
run "status shows the backup" 0 $J status
contains "Backup:.*$E/backups/jarl-"
run "restore refused while jarld runs" 1 $JARLD -c jarl.yaml restore latest --yes
contains "jarld is running"
run "a second jarld is refused" 1 $JARLD -c jarl.yaml serve
contains "in use by a running jarld"
run "record after the backup" 0 $J record add adbraun.test afterbackup A 192.168.1.99 --view internal
expect_dig "afterbackup A" "192.168.1.99" afterbackup.adbraun.test A
kill $JARLD_PID; wait $JARLD_PID 2>/dev/null
run "jarld restore lists backups" 0 $JARLD -c jarl.yaml restore
contains "jarl-.*\.db"
run "jarld restore latest" 0 $JARLD -c jarl.yaml restore latest --yes
contains "previous database is in"
$JARLD -c jarl.yaml serve >> jarld.log 2>&1 &
JARLD_PID=$!
for i in $(seq 50); do curl -s -o /dev/null http://127.0.0.1:$HTTP/healthz && break; sleep 0.2; done
run "restored database lacks the later record" 0 $J record list adbraun.test --view internal
if echo "$OUT" | grep -q afterbackup; then bad "afterbackup still there after restore"; else ok "afterbackup gone after restore"; fi
run "resync after restore" 0 $J resync --force
expect_dig "afterbackup gone from named" "" afterbackup.adbraun.test A
run "audit shows the restore" 0 $J audit --action system.restore
contains "system.restore"
run "status at the end" 0 $J status
contains "Drift:.*none"
+3 -3
View File
@@ -6,12 +6,12 @@
# The package installs, with Debian's paths:
# /usr/bin/jarld, /usr/bin/jarl
# /usr/share/bash-completion/completions/{jarl,jarld}
# /usr/lib/systemd/system/jarl.service
# /usr/lib/systemd/system/{jarl.service,jarl-backup.service,jarl-backup.timer}
# /usr/lib/sysusers.d/jarl.conf, /usr/lib/tmpfiles.d/jarl.conf
# /usr/share/doc/jarl/ (README, CHANGELOG, docs, contrib)
# and its scripts (deploy/debian) create the jarl user and directories and
# /etc/jarl/jarl.yaml on the first install, restart a running jarld on
# upgrades, and stop it on removal. No file in /etc belongs to the package,
# /etc/jarl/jarl.yaml on the first install, turn on the daily backup timer,
# restart a running jarld on upgrades, and stop both on removal. No file in /etc belongs to the package,
# so an upgrade never asks about configuration files.
#
# Variables: DEB_ARCH (Debian architecture, e.g. amd64, arm64, armhf),