Zones of the same name in different views can be linked. Shared records
are kept in step: a change in one view reaches the others in the same
changeset, as explicit operations, so the diff, permission checks,
history and undo cover every view. Records local to a view override the
shared records of that name and type there; removing the override brings
the shared ones back, and sharing a view's version makes it every view's.
Each zone keeps its own options, serial and file.
Linking existing zones changes no answer: what is the same everywhere
becomes shared, the rest stays local. Unlinking keeps every record, all
local. ACME challenges are written once per link, and --view all changes
a link once. jarl zone link/unlink, zone create --link-with, record
--local/--shared, LINK column, web UI controls, POST /zones/{zone}/link
and /unlink, validation checks linked_*, store migration 11, docs and
e2e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jarl is now an ACME client (lego's core, no DNS providers). It answers
DNS-01 challenges in a zone it serves, or through Porkbun's DNS API while
Porkbun serves the domain; "auto" decides from the domain's public name
servers at every renewal, and a CNAME on _acme-challenge is followed.
Certificates are used for jarld's HTTPS (served without a restart; each
port speaks HTTPS and plain HTTP, plain HTTP from loopback and trusted
proxies still served) and/or written for named's DNS over TLS/HTTPS, with
rndc reconfig on renewal. Renewal follows the CA's ARI window or two
thirds of the lifetime, with back-off. Certificates you renew yourself
can be used too. Certificates page and dashboard card, jarl cert,
/api/v1/certificates, Prometheus gauges, docs/certificates.md, and an
e2e section against Pebble.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Settings section in docs/exposure.md: the counting model, a table of all
15 rate-limit settings from the BIND 9.20 ARM, and what was checked
against real named (TCP never limited, NXDOMAIN falls back to
responses-per-second and is counted per zone across names, all-per-second
only drops, slipped answers are TC or BADCOOKIE depending on DNS cookies).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jarl works out which views are reachable from the internet (match-clients
ending in a catch-all, not private-looking addresses: an internal view of a
global IPv6 prefix is still yours; a server without views: open allow-query
and a non-private listen address), with a per-view override (schema 9), and
checks public ones: rate limiting, open resolver, minimal-any, cache
snooping, open NOTIFY and transfers, version and host name, management
interfaces. Fixes are staged like any change, including rate limiting from
ISC's example in log-only mode with the secondaries exempt, then enforcing.
named logs what rate limiting limits (or would, in log-only mode, where its
counters don't move) as query-errors; Jarl routes those lines to its log
and counts them (rrl breakdown, ratelimited top lists, Prometheus
jarl_ratelimit_logged_total), shown on the new Exposure page.
A change that newly opens a public view up must be confirmed (accept_risk,
--accept-risk, a checkbox on the Changes page; 409 confirm_risk
otherwise). Findings can be acknowledged with a reason and stay visible as
info. GET /exposure, POST /exposure/fix|ack|unack, jarl exposure,
docs/exposure.md.
Also fixes query statistics skipping TSIG-signed queries ("IP#port/key
NAME" in named's log).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jarl changeset show/diff/apply/discard take the changeset from -C or
JARL_CHANGESET when no ID is given, and remove takes [ID] SEQ. A changeset
from JARL_CHANGESET is checked before anything uses it (staging included):
it must exist, be open and be the caller's own, so a stale export can't
stage into an applied changeset or into another user's draft that later
got its ID. Commands say on stderr which changeset they use, and apply and
discard remind to unset JARL_CHANGESET. -C and ID arguments are used as
given.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ACLs, views' match lists, options with address match lists and blocking
client groups take ranges such as 10.0.10.101-10.0.10.199 or
2001:db8::100-2001:db8::1ff (negated, or with spaces around the dash, too).
Jarl stores the range as written and writes the fewest prefixes that cover
exactly that range to named.conf, with the range as a comment. New package
internal/iprange; namedconf gets an AMRange element (a range used to parse
as an ACL name), ExpandRanges and statement comments. Mistakes are refused
when entered, validation sees what ranges cover, and IPv6 ranges in the
IPv4-mapped space get a warning. The web UI shows what a range becomes
while typing (GET /catalog/ranges).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A web changeset with a staged record delete could not be applied after
the record was deleted elsewhere (e.g. with the CLI), and the error only
said "record N does not exist", so it was hard to find the operation to
remove. A delete of a record that is already gone is now a no-op with a
warning. Other operations that no longer apply fail with their number and
summary (op_summary in the API), and the Changes page marks that row and
offers to remove it from the error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New metrics: section (enabled, listen, allow, token). /metrics is served on
the main listeners, the local socket (root and jarld's user) and optional
metrics-only addresses; a scrape needs an allowed address or the bearer
token. Exports health (named up, problems, drift, backups, blocklists,
DNSSEC phases, token expiry) and, with stats on, named's and Jarl's query
counters since jarld started plus named's process and cache gauges.
docs/metrics.md has example queries and alerts checked with promtool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client groups put devices (addresses, networks, or ACLs of them) at a
blocking step: off (only your rules), normal (the default), or steps
added above it, each including the lists of the steps below. Lists
start at a step. In each view, a gate policy zone in front of a step's
lists lets the devices below that step through (rpz-client-ip triggers
with rpz-passthru), so every list is still loaded once. The IPv6 part
of a gate leaves the IPv4-mapped range out: named matches IPv4 clients
as ::ffff:a.b.c.d.
Web UI: a Devices panel on the Blocking page and a step on each list.
CLI: jarl block step, jarl block group, jarl blocklist --step. API:
/blocking/steps, /blocking/groups. Database schema 8.
Also: copying the state kept empty lists as empty (not nil), so
reverting a changeset no longer adds restore operations for unchanged
blocklists, rules and views.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Blocking page shows how many queries each blocklist, and the rules
together, blocked in the last 24 hours (with statistics on). The + CNAME
button on A/AAAA rows shows when the row is hovered or focused, and
always on touch screens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each view's match-clients starts with "key jarl-view-NAME" (after
"!key" for the views listed after it), so a query signed with a view's
key lands in that view from any address. The secrets are derived from a
random seed in Jarl's database. The delegation check signs its queries
to this server's own addresses with the key of the zone's view: before,
they got whichever view matched the server's address, usually internal.
jarl view key / GET /views/{id}/key show a key for dig -k (tsig:write,
audited). jarl-view- key names are reserved; the importer drops view
keys from a configuration Jarl wrote.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Added:
- Server statistics: named's memory and CPU (from /proc), cache size,
sockets and client counts against their limits (statistics channel,
rndc status), host memory and load, and free disk space. Memory and
CPU are kept over time as gauges (sum and sample count, averaged in
timelines). Statistics page Server section, jarl stats server,
GET /stats/server.
- jarl record add|set|delete|disable|enable --view a,b | all: the same
change in each view, applied as one changeset.
- Blocking can be paused until resumed (jarl block pause indefinitely,
"Until I resume", {"indefinitely": true}).
Changed:
- List options (allow-transfer, forwarders, ...) get their braces added
when given without them.
- The dashboard lists named-checkconf's warnings about the running
configuration; the CLI prints them after an apply.
- Stats page: tile and card rows fill their width, and the queries and
failed answers charts share margins so their time axes line up.
- Dashboard: options have their own tile.
- 24-hour times in the web UI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Options of a block (server options, a view, a zone) are listed and
written grouped by topic, alphabetical within a group, so the same
option is in the same place everywhere. Top-level statements keep the
order they were added in.
named.conf diffs add the enclosing blocks to each hunk header, like
git's function context: @@ -50,6 +49,8 @@ view "internal" {.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A disabled zone stays in Jarl with its records, options and history,
still editable, but named doesn't load it; enabling it serves it as it is
then. Signed zones, and zones another view shares, can't be disabled.
jarl zone disable|enable, the zone page's Disable/Enable button.
Database schema 7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jarl block pause|resume (and the Blocking page) lets every name through
for a while; jarld resumes on time. The lists stay loaded: the rules zone
just passes everything. Statistics gain the clients with the most blocked
queries and blocked queries on the queries chart. The view choice in the
blocking forms is a dropdown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jarld downloads blocklists (Adblock, hosts or plain domains), checks them
daily and reloads named only when a list changed; your own block and
allow rules take precedence, and the most specific rule wins. Lists and
rules apply per view (default: every view that offers recursion). Web UI
Blocking page with name checks, jarl blocklist and jarl block, the
/blocking API, blocked counts in the statistics. Database schema 6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zones list shows a padlock after signed zones, linking to their
DNSSEC tab. Client lists in the statistics show the host name Jarl's
records have for each address (PTR, else A/AAAA); no DNS queries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-o id and -o field=NAME,... print just the IDs or chosen fields; -q on
show and list prints the IDs. jarl record list shortens data too long
for the terminal (--wide shows it in full).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DNSSEC tab and 'jarl zone dnssec status' give the jarl record add
command (and an Add to parent button) when the parent zone is in Jarl.
jarl.service grants CAP_NET_BIND_SERVICE so the web UI can listen on 443.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A disabled record stays in Jarl with its ID, TTL and comment but is
left out of the zone; the zone file lists it as a comment. Web UI
toggle, jarl record disable|enable, record.disable|enable operations
and API endpoints. Database schema 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every DNS query the name server checks send is now reported from
exchangeHostPort as a dig command line with a short answer (addresses,
referral, DS, NXDOMAIN) and its round-trip time; the serials check's SOA
queries keep their own events with the server's result. The delegation
check streams with ?stream=1 like /serials, and the Name servers tab
shows its queries as they happen, keeps its result and follows a running
check when you come back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zone's Name servers tab no longer waits for the other servers before
showing: it lists each query as a dig command line with a waiting
counter, fills in the answers as they arrive, then shows the result with
the queries under "Queries sent". Results (serials and delegation) are
kept for 10 minutes so revisiting the tab is instant; "Check again" asks
anew, and a check keeps running when the tab is left.
GET /zones/{zone}/serials?stream=1 streams the queries as
newline-delimited JSON (nscheck.Checker.Watch); without it the endpoint
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make deb versions a build that isn't a release after the last tag:
0.2.0~rc.1+git3.gca0f148, plus the build time (.m<UTC time>) with
uncommitted changes. apt sorts it after that RC and before the next, so
installing it over the RC or an older build is an upgrade and needs no
--reinstall. mkdeb.sh removes older packages of the same architecture
from dist/ so the install globs match one package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a release build (the commit tagged v<VERSION>, unchanged) gives a
version number. Others print 'dev <commit> (3 commits after v0.2.0-rc.1)',
from git describe (the Makefile's DESCRIBE), in jarld version,
jarl --version, jarl status, jarl whoami and the startup log; a build
without git says '(source build)'. The CLI's User-Agent and the footer's
tooltip follow suit, and /status's build gains since and ahead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first release candidate of 0.2.0: DNSSEC signing and Porkbun
support, two-factor login, view colours and navigation, and the build
shown in a footer. Its notes are the CHANGELOG's Unreleased section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DNSSEC notes in CHANGELOG.md belong under Unreleased; git had put
them into the 0.1.0 section, which didn't exist when the branch was made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pre-releases (versions with a hyphen) now upload their .debs to the
"testing" distribution instead of "stable", so servers following stable
never get an RC from apt upgrade. An RC can take its release notes from
its final version's section or Unreleased; the workflow checks the notes
with 'gitea-release.sh notes'. Also fix publishing RCs, whose .deb names
carry the Debian version (0.2.0~rc.1) and were rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The version moves from the header to a "Powered by Jarl" footer. A build
of a release tag (HEAD tagged v<VERSION>, no tracked changes; the release
workflow sets RELEASE_TAG) shows and links the release; any other build
shows and links its commit, marked -modified with uncommitted changes.
jarld version, jarl --version, /status and /auth/whoami report the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The view shown in the zone list and on the zone page now links to that
view. The zone page and changeset page get breadcrumbs, and the zone list
keeps its filters in the address so going back restores them. Views can
be given a colour (Views > Edit, `jarl view color`, PUT /views/{id}/color)
so zones of the same view are easy to spot; the colour is UI-only, kept
outside changesets, and needs no deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users can set up an authenticator app from their account page; login
then asks for a code after the password, with one-time recovery codes
as a fallback. Sensitive actions (tokens, user changes, 2FA resets)
need a code from the last 5 minutes. totp.require (none/admins/all)
forces setup, and admins can reset a user's 2FA from the UI, the jarl
CLI or jarld on the server. The dashboard and jarl status flag users
missing 2FA and a shared "admin" account still in use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jarld import warned that zones with allow-update { none; } use dynamic
updates, though that turns them off (validation already allowed it).
docs/cli.md now gives the CLI configuration file's location on macOS and
Windows, not only ~/.config/jarl/config.yaml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a fresh Debian/Ubuntu machine it installs BIND and the build tools
(Go from go.dev), writes a hand-made style configuration with internal
and external views, a split-horizon zone (example.com or
JARL_TEST_ZONE) and a reverse zone, starts named and prints the steps
to install Jarl and adopt the configuration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make deb now builds two packages: jarl-cli (the CLI, its completion and
the ACME hooks, no scripts) and jarl (the server), which depends on
jarl-cli of the same version.
Pushing a v* tag runs .gitea/workflows/release.yml: it tests, builds the
tarballs, the CLI for Linux, macOS and Windows (make cli-dist) and the
.debs for amd64 and arm64, then scripts/gitea-release.sh creates the
Gitea release and uploads the .debs to the Debian package registry, so
servers install and upgrade with apt. The stats package's inode lookup
is split by OS so the CLI builds for Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- 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>
The header now shows the login page's wordmark letters (without the rule)
instead of "Jarl" set in Cinzel, so the bundled Cinzel font, its preload
and its licence stanza in the .deb's copyright file are removed: the web
UI needs no web font.
The README opens with the login page's banner, the logo above the
wordmark (artwork/banner.svg). scripts/banner.py builds it from the web
UI's logo.js and brand.js, with light and dark colors, and logo.py runs
it after tracing the logo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The logo was an 880 KB SVG wrapping a 1254px PNG, downloaded again on
every page load. Keep that PNG as the master (artwork/logo.png) and have
scripts/logo.py trace it with potrace into a 6 KB logo.svg (favicon,
README) and logo.js, which brand.js draws inline in the header and on the
login page so the logo arrives with the page. Remove the unused
logo-white.svg.
Static files now carry content ETags, so reloads get 304s instead of
downloading the UI again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jarld also listens on /run/jarl/jarl.sock (socket in jarl.yaml). Requests
on it from root or jarld's own user need no token and act as an
administrator; the kernel's SO_PEERCRED says who is connecting, and
anyone else must send a token. The jarl CLI uses the socket when no server
is configured, or with -s local, so 'sudo jarl status' works on the server
with nothing set up and a lost token or password never locks root out.
Changes are audited under the system user's name via "socket".
The systemd unit creates /run/jarl (0750). Socket paths over 107 bytes
are refused with a clear message. The docs explain the socket and what
happens when a token is revoked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make deb builds dist/jarl_<version>_<arch>.deb with dpkg-deb alone (no
root), for the host or another architecture (DEB_ARCH=arm64). The package
creates the jarl user and directories and an example jarl.yaml on the
first install, restarts a running jarld on upgrades, never touches
/etc/jarl/jarl.yaml, stops jarld on removal, and keeps Jarl's data even
on purge.
make install and the package install bash completion for jarl and jarld.
Besides commands and flags it completes server and group names from the
CLI configuration, zone, view, ACL, TSIG key and user names from the
server, and record types. make dist includes the completion scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jarl token rotate, a Rotate button in the web UI and
POST /api/v1/tokens/{id}/rotate give a token a new secret while it keeps
its ID, name, user and grants. The old secret stops at once or after a
grace period of up to 7 days, during which jarld logs who still uses it.
A token that expires gets its lifetime again. --save writes the new
secret where the CLI got the old one (its configuration or --token @FILE),
--write FILE to any file, and --current picks the token the CLI runs
with, so a restricted token can renew itself. A restricted token can't
rotate a wider token of its user.
Also fixes token create --save for named servers, and future times
(token expiry) shown as "just now" in the web UI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
contrib/lego/jarl-lego.sh turns lego's "present|cleanup FQDN VALUE" calls
into jarl acme set|unset --exact, so lego (and CNAME delegation to a
dedicated ACME zone) works with Jarl. docs/acme.md explains the setup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard counted every open changeset, including empty ones and
other users', so "Pending changes (1 open)" led to an empty Changes page.
It now counts the user's own changesets that contain changes, the status
API's open_changesets skips empty ones, and in review mode a draft created
for an edit the server rejects is discarded instead of left open.
docs/concepts.md: zone types, a new zone's name servers and glue, reverse
zones for any network, and dynamic updates versus API tokens and TSIG keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CLI: name several servers and groups in the config file; select them
with -s/--server NAME|GROUP|all|URL or JARL_SERVER. A command aimed at
several servers is dry-run on all of them first and only runs if every
one accepts it. Named servers always use their own token.
- Reverse zones: /32 and /128 get single-address zones; networks between
octet or nibble boundaries (e.g. a /22) are covered by several zones
created together, from the CLI and the web UI.
- Fix: under jarl.service's RestrictSUIDSGID, creating a view directory
failed on chmod with the setgid bit; fall back to the permission bits.
- Web UI: the review/live switch is a small header button, and the
pending-changes bar only shows when there are pending changes.
- TSIG key wording; the keys page points to restricted API tokens.
- Docs: several servers (cli.md), a secondary server (operations.md),
tokens vs TSIG keys (permissions.md); CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the logo, a Viking jarl drawn like a woodcut whose spear-pointed crown
holds a DNS tree. It is generated by scripts/logo.py, which mirrors the
left half so the figure is exactly symmetrical, as logo.svg (white on a
Nordic-blue disc) and logo-white.svg. It links home in the header, sits on
the login page and is the favicon.
The login page shows an angular "JARL" wordmark drawn as SVG paths over a
rule with a "DNS" node, coloured from the theme. The header's "Jarl" uses
a self-hosted Cinzel Bold subset (SIL OFL); jarld serves .woff2 as
font/woff2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New zone dialog: choose Domain or Reverse zone. Reverse zones are
created from a network and their name is generated; both kinds show the
exact zone name and flag likely mistakes (addresses or URLs as names,
host-like names, duplicates in the view, parent/child zones, networks
off the octet/nibble boundaries) before anything is submitted.
- Record form: clears every field but the type after an add; shows the
record exactly as it will be stored and warns about unqualified names.
In reverse zones it takes an address, defaults to PTR, suggests host
names of A/AAAA records with that address and warns about a second PTR;
the record list shows addresses.
- Address match lists (options, view match lists, ACL elements) offer the
existing ACLs, TSIG keys and built-ins as one-click suggestions and
flag unknown names.
- Server: in reverse zones a record can be named by its address, and a
PTR target like nas.example.com without the final dot is read as the
full host name (a bare label is refused). Warnings noticed while parsing
record data reach the result again.
- Fixes: brand link hover underline, the dashboard's "New zone" link
opening the dialog twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>