-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>
512 lines
31 KiB
Markdown
512 lines
31 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to Jarl are documented here. The format follows
|
||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
||
[Semantic Versioning](https://semver.org/).
|
||
|
||
## [Unreleased]
|
||
|
||
### Added
|
||
|
||
- Disabled records: a record can be disabled instead of deleted. It stays
|
||
in Jarl with its ID, TTL and comment but is left out of the zone (the
|
||
zone file lists it as a comment), and enabling it puts it back exactly
|
||
as it was. Web UI: a Disable/Enable button on each record, disabled
|
||
records greyed out with a badge, a filter for them, and a count in the
|
||
zone list. CLI: `jarl record disable|enable` (by ID or by name, type and
|
||
data, `--ptr` for the PTR record, `--missing-ok`), `record list
|
||
--disabled`, and `disable:`/`enable:` entries for `jarl apply -f`. API:
|
||
`POST /records/{id}/disable|enable`, `POST
|
||
/zones/{zone}/records/disable|enable`, a `disabled` field on records,
|
||
`?disabled=` on record lists and `record.disable|enable` operations.
|
||
Validation checks the zone as it is served: the last NS record can't be
|
||
disabled, enabling a record is checked like adding it, and a disabled
|
||
record that a CNAME now blocks gets a warning. Database schema 5.
|
||
- DNSSEC for a zone whose parent zone is in Jarl: the DNSSEC tab gives the
|
||
`jarl record add PARENT NAME DS ...` command to copy and an "Add to
|
||
PARENT" button (staged like any change), links to the parent zone, and
|
||
says when the parent has the DS record. `jarl zone dnssec status` prints
|
||
the command too, and turning signing off gives the matching `jarl record
|
||
delete`.
|
||
|
||
- CLI output for scripts: `-o id` prints just the IDs of what a command
|
||
shows or lists (`jarl zone show adbraun.lol -o id`), `-o
|
||
field=view,soa.serial` just those fields, tab separated. `-q` on `show`
|
||
and `list` prints the IDs too. With a change (`jarl record add ... -o
|
||
id`) they apply to the changed object.
|
||
|
||
### Changed
|
||
|
||
- The web UI and API can listen on ports below 1024, such as 443:
|
||
`jarl.service` now gives jarld the `CAP_NET_BIND_SERVICE` capability
|
||
(and only that). With a unit that doesn't, jarld's error says what's
|
||
missing. See docs/configuration.md, "HTTPS on port 443".
|
||
- Records table: names are no longer greyed out when they repeat the row
|
||
above (it made those records look inactive). Records of the same name
|
||
are separated by a faint line instead, and a full line marks where the
|
||
next name starts.
|
||
|
||
### Fixed
|
||
|
||
- `jarl record list`: one long value (a DKIM key) padded every row to its
|
||
length, so on a terminal each row wrapped onto a second, blank-looking
|
||
line. Long data is now shortened to fit the terminal; `--wide` shows it
|
||
in full, as does output to a pipe or file.
|
||
|
||
## [0.2.0] - 2026-10-09
|
||
|
||
### Added
|
||
|
||
- DNSSEC signing per zone (docs/dnssec.md): `jarl zone dnssec
|
||
enable|status|check-ds|disable|wipe` and the zone page's DNSSEC tab, or
|
||
the zone's new `dnssec_policy` field in the API. named signs the zone
|
||
with BIND's `default` policy (one ECDSA P-256 key that is never replaced)
|
||
or a custom one, using inline signing, with keys in a per-view directory
|
||
under `/var/lib/bind/jarl/dnssec-keys`. Jarl shows the keys and the DS
|
||
record to give the registrar, says when it's safe to publish it, and
|
||
checks the parent's DS records (`check-ds`, which also tells named with
|
||
`rndc dnssec -checkds`). It refuses steps that would break a signed
|
||
domain: switching straight to unsigned (it goes through BIND's
|
||
`insecure` policy instead, and finishes only once named is done),
|
||
deleting a signed zone or moving it to another view, and TTLs above the
|
||
policy's `max-zone-ttl`. `wipe` starts over with a clean slate once the
|
||
parent has no DS. Each step in the web UI opens a dialog that says what it
|
||
needs first. `jarl zone delegation` now also compares the parent's DS
|
||
records with the zone's keys.
|
||
- Porkbun support (optional, docs/dnssec.md#porkbun): with an API key in
|
||
the new `porkbun:` section of jarl.yaml (and nowhere else), `jarl zone
|
||
registrar status|sync-glue|set-ns|publish-ds|remove-ds` and the zone
|
||
page's Registrar tab show and change the domain's name servers, glue
|
||
records (one per name server inside the domain, with all its IPv4 and
|
||
IPv6 addresses) and DS records at Porkbun. Every change first checks that
|
||
it won't take the domain offline (the name servers answer for the zone,
|
||
glue matches, a DS goes out only once the domain is delegated to servers
|
||
that serve the signed zone, DS records match the zone's keys before the
|
||
name servers change); `--dry-run` shows the checks and `--force` skips
|
||
them. Administrators only.
|
||
- Backups include the DNSSEC keys (private keys too), stored inside the
|
||
backup file, and `jarld restore` puts them back for named
|
||
(`bind.named_user`, default `bind`). `jarl-backup.service` has
|
||
`CAP_DAC_READ_SEARCH` to read them; `--without-keys` skips them.
|
||
- Jarl raises a zone's serial above the one named has loaded whenever it
|
||
writes a zone file whose serial isn't higher (after restoring an older
|
||
database, say), so named loads it (it refuses otherwise for a signed
|
||
zone) and secondaries pick it up.
|
||
- Two-factor login to the web UI with an authenticator app (TOTP: SHA-1,
|
||
6 digits, 30 s, as every app supports). Users turn it on under Account
|
||
(password, QR code, a code to confirm) and get 10 one-time recovery
|
||
codes. Logging in then asks for a code (or a recovery code) after the
|
||
password; each code works once. In a session with two-factor login,
|
||
creating or rotating API tokens, changing users and resetting someone's
|
||
two-factor login need a code from the last 5 minutes: the web UI asks
|
||
for one, API clients send `X-Jarl-TOTP`. API tokens and the local socket
|
||
are not affected.
|
||
- `totp.require` in jarl.yaml (`none`, `admins`, `all`) makes two-factor
|
||
login compulsory; such a user who hasn't set it up can only set it up
|
||
after logging in. `totp.issuer` names the server in authenticator apps
|
||
(default `Jarl <hostname>`).
|
||
- Resetting a user's two-factor login (lost phone): Users → Reset 2FA,
|
||
`jarl user totp reset NAME`, or on the server
|
||
`jarld user totp reset NAME` (also with jarld stopped).
|
||
- The dashboard and `jarl status` list users who must use two-factor login
|
||
but haven't set it up, and suggest disabling the first `admin` account
|
||
when it is still active next to personal administrator accounts.
|
||
`jarl user list`/`show` and the Users page show who uses it.
|
||
- API: `POST /auth/login/totp`, `/me/totp` (`setup`, `enable`, `disable`,
|
||
`recovery-codes`), `DELETE /users/{id}/totp`; database migration 4.
|
||
- `scripts/testenv.sh`: turns a fresh Debian/Ubuntu machine into a test bed.
|
||
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
|
||
(docs/development.md).
|
||
- View colours. Give a view a colour (Views → Edit, `jarl view color`, or
|
||
`PUT /views/{id}/color`) and the zone list, the zone page and the views
|
||
list show its name in that colour. The colours are muted, and pastel in
|
||
dark mode. The default is the link colour. A colour isn't configuration,
|
||
so setting one needs no changeset or deploy.
|
||
- A "← Zones" link above a zone, back to the zone list, and "← History"
|
||
above a changeset. The zone list keeps its filter, view and type in the
|
||
address, so going back to it (with that link or the browser's back
|
||
button) restores them. A view opened from a zone or the zone list has a
|
||
"← example.com (external)" or "← Zones" link back.
|
||
|
||
### Changed
|
||
|
||
- The version moved from the header to a footer: "Powered by Jarl v0.1.0",
|
||
linking to the release on Gitea. A build that isn't of a release tag
|
||
shows its commit instead, linked to the commit and marked `-modified`
|
||
with uncommitted changes. Before, every build of a commit after a
|
||
release still showed the release's version. `GET /status` and
|
||
`/auth/whoami` report the build as `build` (docs/development.md).
|
||
- `jarld version`, `jarl --version`, `jarl status` and `jarl whoami` give
|
||
a version number only for release builds. Other builds say they are
|
||
development builds and how far past the last tag they are: `dev
|
||
ca0f148b78b6 (3 commits after v0.2.0-rc.1)`. Before, they printed the
|
||
version and the commit (`0.2.0-rc.1 (ca0f148b78b6)`), which read like a
|
||
release.
|
||
- `make deb` gives development builds their own package version after
|
||
the last tag (`0.2.0~rc.1+git3.gca0f148`, plus the build time with
|
||
uncommitted changes). Installing one over the RC or an older build is
|
||
an upgrade: no more `apt install --reinstall`. It also removes older
|
||
packages from `dist/` (docs/development.md).
|
||
- The view in the zone list, and on the zone page, links to that view,
|
||
and the views page highlights it. It used to be plain text, and the zone
|
||
page linked to the views page as a whole.
|
||
- The zone's Name servers tab shows at once and follows the check as it
|
||
happens. Each query is listed as a dig command line (`dig @192.0.2.1
|
||
example.org. SOA +norec`) with a "waiting 1.8 s" counter until it is
|
||
answered. The finished result then replaces the progress, and the queries
|
||
stay under "Queries sent". Results, the delegation check's included, are
|
||
kept for 10 minutes, so returning to the tab shows them at once; "Check
|
||
again" asks anew. A check keeps running if you leave the tab. Behind it,
|
||
`GET /zones/{zone}/serials?stream=1` streams the queries as
|
||
newline-delimited JSON (docs/api.md).
|
||
- The delegation check streams its queries the same way, every one of
|
||
them: finding the parent zone, its servers' addresses, the NS and DS
|
||
queries to each parent server and the SOA from each delegated server.
|
||
Each shows a short answer ("referral to katelyn.ns.cloudflare.com., …",
|
||
"DS 2371", "NXDOMAIN"). The serials check now lists its address lookups
|
||
as separate A and AAAA queries. `GET /zones/{zone}/delegation?stream=1`
|
||
streams them.
|
||
- Switching a zone's tabs (Records, Settings, …) no longer blanks the page
|
||
while the tab loads: the page changes once the tab is ready, and
|
||
"Loading…" appears only if a page takes a while.
|
||
- `jarl zone serials` compares the serial named serves, which for a signed
|
||
zone is the signed zone's (ahead of Jarl's).
|
||
- `jarld import` turns `dnssec-policy` in an existing configuration into
|
||
the zones' DNSSEC setting, and warns that their keys must be moved into
|
||
Jarl's key directory before adopting.
|
||
- `dnssec-policy` is no longer accepted as a zone, view or server option
|
||
(zones have a DNSSEC setting instead), nor `key-directory` as a zone
|
||
option.
|
||
- Deleting a zone also removes the journal and signed files named kept
|
||
next to it.
|
||
- Release candidates (tags like `v0.2.0-rc.1`) publish their `.deb`s to a
|
||
separate `testing` distribution of the package registry instead of
|
||
`stable`. Servers that follow `stable` can't pick up an RC with
|
||
`apt upgrade`. An RC may also take its release notes from the
|
||
`## [0.2.0]` or `## [Unreleased]` section of the CHANGELOG instead of
|
||
needing its own (docs/development.md, "Release candidates").
|
||
|
||
### Fixed
|
||
|
||
- Publishing a release candidate failed: `scripts/gitea-release.sh`
|
||
rejected its `.deb`s, whose names carry the Debian version
|
||
(`0.2.0~rc.1`) instead of `0.2.0-rc.1`.
|
||
- `jarld init -u` and the install guide recommend naming the first
|
||
administrator after yourself (one account per person).
|
||
- `jarld import` no longer warns that a zone with `allow-update { none; }`
|
||
uses dynamic updates (validation already allowed it).
|
||
- docs/cli.md gives the CLI configuration file's location on macOS
|
||
(`~/Library/Application Support/jarl/config.yaml`) and Windows
|
||
(`%AppData%\jarl\config.yaml`). It was documented only as
|
||
`~/.config/jarl/config.yaml`.
|
||
|
||
## [0.1.0] - 2026-10-09
|
||
|
||
The first version.
|
||
|
||
### Added
|
||
|
||
- Daily database backups: the `jarl-backup.timer` systemd timer (turned on
|
||
by the Debian package) runs `jarld backup`, which writes
|
||
`jarl-<UTC time>.db` into `backup.dir` (`/var/backups/jarl`), checks it
|
||
with SQLite's integrity check before giving it that name, and deletes
|
||
backups older than `backup.retention` (30d) while always keeping the
|
||
newest `backup.keep_at_least` (7). Ages come from the names, and only
|
||
files named like backups are ever deleted. Settings are in the new
|
||
`backup:` section of jarl.yaml; the schedule is the timer's. The dashboard
|
||
and `jarl status` show the newest backup, and warn when it is older than
|
||
`backup.warn_after` (2d) or the last attempt failed. `backup.stats` also
|
||
backs up the statistics database.
|
||
- `jarld restore [FILE | latest]`: lists backups, or checks one, shows what
|
||
it holds next to the current database, and after confirmation replaces
|
||
the database with it, keeping the current one as
|
||
`jarl.db.before-restore-<time>`. It refuses while jarld runs: `jarld
|
||
serve` now holds a lock (`jarl.db.lock`), which also stops two jarlds from
|
||
using the same database. docs/operations.md explains restoring.
|
||
- Warnings before API tokens expire, from 14 days ahead: on the dashboard,
|
||
in token lists ("soon", "expired"), once a day in jarld's log, and from
|
||
the `jarl` CLI on stderr whenever it uses such a token (responses carry a
|
||
`Jarl-Token-Expires` header). `jarl token list --expiring [WITHIN]` lists
|
||
them.
|
||
- `jarl zone serials ZONE` and the zone page's Name servers tab: ask the
|
||
zone's other name servers which serial they serve (a primary zone's NS
|
||
records and also-notify targets, a secondary zone's primaries, with their
|
||
ports and TSIG keys) and compare it with this server's. Exits with 2 when
|
||
one is behind or doesn't answer.
|
||
- `jarl zone delegation ZONE` and a button on the Name servers tab: for
|
||
zones on the internet, checks that the parent zone delegates the zone to
|
||
the name servers in its NS records, with matching glue, and that each of
|
||
them serves it. An undelegated (internal-only) zone gets one calm note.
|
||
Neither check runs unless asked.
|
||
- jarld sends `Server: Jarl/<version>`.
|
||
- CLI exit code 2: a check ran and found problems.
|
||
- CLI: several servers in one configuration file. Name them
|
||
(`jarl config server add ns1 URL`), pick one with `-s/--server NAME` or
|
||
`JARL_SERVER`, set a default with `jarl config use`, and group them
|
||
(`jarl config group set dns ns1 ns2`). A command aimed at a group (or
|
||
`all`) is first checked on every server as a dry run and only runs if it
|
||
succeeds everywhere, so a change one server rejects changes nothing
|
||
anywhere. Named servers always use their own token.
|
||
- Reverse zones for any network: a /32 (or /128) gets a zone that answers
|
||
for just that address, and a network between octet (or nibble)
|
||
boundaries, such as a /22, is covered by several zones (four /24s) that
|
||
are created together, from the CLI (`jarl zone create 10.0.8.0/22`) and
|
||
the web UI. Networks smaller than a /24 explain that they belong in the
|
||
enclosing /24's zone.
|
||
- docs/operations.md: setting up a secondary server with its own jarld.
|
||
- A local unix socket, `/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);
|
||
anyone else must send a token. The `jarl` CLI uses it when no server is
|
||
configured, or with `-s local`, so `sudo jarl status` works on the
|
||
server with nothing set up, and losing every 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).
|
||
- Jarl is released under the MIT License (`LICENSE`), which is installed
|
||
with the docs and is the Debian package's `copyright` file.
|
||
- Debian packages: `make deb` builds, with only `dpkg-deb` (no root) and
|
||
for the host or any other architecture (`DEB_ARCH=arm64`), `jarl` (the
|
||
server: jarld, its units and docs) and `jarl-cli` (the `jarl` CLI, its
|
||
completion and the ACME hooks), which `jarl` depends on. `jarl-cli` has no
|
||
scripts, so it can go on any machine you manage Jarl from. Installing
|
||
`jarl` creates the `jarl` user and directories and an example
|
||
`/etc/jarl/jarl.yaml`; upgrades restart a running jarld and never touch
|
||
`/etc/jarl/jarl.yaml`; removing it stops jarld, and even a purge keeps
|
||
Jarl's database and the files named uses.
|
||
- Releases from Gitea Actions: pushing a `v<version>` tag runs
|
||
`.gitea/workflows/release.yml`, which tests, builds and publishes a Gitea
|
||
release (the CHANGELOG section as notes; the tarballs, the CLI for Linux,
|
||
macOS and Windows, the `.deb`s and `SHA256SUMS`), and uploads the `.deb`s
|
||
for amd64 and arm64 to the Gitea Debian registry, so servers install and
|
||
upgrade Jarl with apt. `make cli-dist` builds the standalone CLI files;
|
||
`scripts/gitea-release.sh` publishes. docs/development.md has the setup
|
||
(a runner and a `RELEASE_TOKEN` secret), docs/install.md the apt source.
|
||
- Bash completion for `jarl` and `jarld`, installed by `make install` and
|
||
the Debian package. 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` tarballs include the completion scripts (`completion/`).
|
||
- Token rotation: `jarl token rotate`, the 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 (`--grace`), 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.
|
||
`jarl token list` shows when a token was last rotated.
|
||
- An endpoint for lego's `httpreq` DNS provider
|
||
(`/api/v1/acme/httpreq/present` and `/cleanup`), so lego and Traefik can
|
||
publish DNS-01 challenges with only environment variables, without the
|
||
jarl CLI. lego's username and API token arrive as HTTP Basic auth, which
|
||
is accepted on these routes only. 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`.
|
||
Plain HTTP can be allowed from loopback only (`allow_loopback_http`) or
|
||
everywhere (`require_https: false`). `GET /api/v1/acme/httpreq` shows how
|
||
jarld sees a request, to check a proxy setup. `jarl acme` and the endpoint
|
||
share their code (`internal/acme`).
|
||
- Trusted proxies may also send the standard `Forwarded: proto=https`
|
||
header. When a proxy header lists several values, the last one (added by
|
||
the nearest proxy) counts.
|
||
- lego support: `contrib/lego/jarl-lego.sh` is a program for lego's `exec`
|
||
DNS provider that publishes DNS-01 challenges through `jarl acme`,
|
||
including challenge names reached through a CNAME (docs/acme.md).
|
||
- docs/concepts.md: what each zone type does, what to enter as a new zone's
|
||
name servers (and when glue addresses are needed), how reverse zones map
|
||
to networks, and why dynamic updates give way to API tokens.
|
||
|
||
- A logo: a bearded Viking jarl in blue and white, whose crown holds a DNS
|
||
tree (a root node and its branches). The master is `artwork/logo.png`;
|
||
`scripts/logo.py` traces it into vector paths (`assets/logo.svg`, 6 KB,
|
||
and `assets/logo.js`). It is shown on the login page, links home in the
|
||
top left of the web UI, and is the favicon. The UI draws it inline, so it
|
||
appears together with the page.
|
||
- The login page shows an angular, Norse-style "JARL" wordmark drawn as
|
||
SVG paths (`assets/brand.js`) above a rule with a "DNS" node; it follows
|
||
the light and dark themes. The header beside the logo shows the same
|
||
letters, without the rule, so the UI needs no web font.
|
||
- The README opens with the login page's banner, the logo above the
|
||
wordmark (`artwork/banner.svg`, built by `scripts/banner.py` from the web
|
||
UI's own logo and wordmark, with light and dark colors).
|
||
|
||
- Web UI: wherever an address match list is entered (server, view and zone
|
||
options such as allow-query or allow-transfer, view match lists, ACL
|
||
elements) the existing ACLs, TSIG keys and built-in names are offered as
|
||
one-click suggestions, and names that don't exist are flagged as you type.
|
||
|
||
- **Query statistics** (off by default; `stats` in jarl.yaml, docs/stats.md).
|
||
When enabled, Jarl adds a loopback statistics channel and a response log to
|
||
named's configuration (merged into existing `logging` and
|
||
`statistics-channels` statements) and switches response logging with
|
||
`rndc responselog`. jarld aggregates named's counters and the log into a
|
||
separate SQLite database: per-minute, hourly and daily counters (queries,
|
||
result codes, record types, views, zones, cache hits, upstream latency,
|
||
DNSSEC) and hourly and daily top lists (names, clients, NXDOMAIN and
|
||
SERVFAIL names, refused clients), each with its own retention. Clients can
|
||
be anonymized or excluded. It is shown on a new Statistics page in the web
|
||
UI (KPI tiles, queries and failed answers over time, a weekday x hour
|
||
heatmap, top lists and breakdowns, each chart with a table view), in
|
||
`jarl stats status|summary|top|timeline|heatmap|breakdown`, and in the API
|
||
under `/api/v1/stats/`, behind the new `stats` permission. Works with the
|
||
bind9 packages of Debian 13 and Ubuntu 26.04.
|
||
- At startup jarld brings named's configuration up to date with jarl.yaml
|
||
settings that affect it (such as statistics), through the usual checked
|
||
pipeline. Files changed by hand are never overwritten.
|
||
- **jarld daemon**: REST API (`/api/v1`) and embedded web UI, with SQLite as
|
||
the source of truth for the whole BIND configuration. Runs under systemd
|
||
(`Type=notify`, sandboxed, unprivileged `jarl` user in group `bind`).
|
||
SIGHUP and certificate file changes reload TLS.
|
||
- **Configuration model**: views (ordered, match-clients/destinations,
|
||
recursive-only), ACLs (nested, negation, keys, reference tracking with
|
||
rename and delete protection), TSIG keys (kept in a separate `keys.conf`,
|
||
never in diffs), zones of every type (primary, secondary, stub,
|
||
static-stub, forward, mirror, hint, redirect, in-view), reverse zones
|
||
from a CIDR, records of every type, options at server, view, zone and
|
||
top-level scope, and defaults for new zones.
|
||
- **BIND 9.20 option catalog**: all 353 statements with grammar, allowed
|
||
scopes, value kinds, deprecation status and ARM descriptions, used for
|
||
validation and by the UI's option editor.
|
||
- **Changesets**: immediate one-operation changes by default; staging into
|
||
named changesets (`?changeset=`, `X-Jarl-Changeset`, `--changeset`),
|
||
batches (`POST /apply`, `jarl apply -f`), dry runs, previews with diffs,
|
||
and revert of applied changesets. Optimistic locking with object versions.
|
||
- **Apply pipeline**: whole-configuration validation, rendering, `named-checkconf -z`
|
||
and `named-checkzone` in a staging directory, atomic install, `rndc reconfig`
|
||
and per-zone reload, verification with `rndc zonestatus`, and automatic
|
||
rollback when named doesn't load the change. Automatic serials (date,
|
||
increment or unixtime policy, RFC 1982), bumped only when DNS data
|
||
changes. Drift detection for files changed outside Jarl.
|
||
- **Validation**: CNAME/DNAME rules, NS/MX/SRV targets, glue and
|
||
delegations, RRset TTL consistency, BIND 9.20 RRset/type limits, SPF/DMARC
|
||
sanity, ACL/key references and cycles, option syntax and scope, and
|
||
security lints (open resolver, open zone transfers, dynamic updates on
|
||
managed zones).
|
||
- **Records**: TXT entered as plain text (automatic quoting and 255-byte
|
||
splitting), relative and absolute names with detection of doubled
|
||
origins, PTR records kept in step with A/AAAA (`ptr`), RRset replace,
|
||
delete by match, zone file import (merge or replace).
|
||
- **Import and adoption**: `jarld import` reads an existing named.conf tree
|
||
and zone files (comments, `$INCLUDE`, `$GENERATE`, legacy
|
||
`master`/`slave` spellings); `jarld adopt` switches named to Jarl's
|
||
configuration with a backup and automatic fallback.
|
||
- **Authentication**: web logins with argon2id passwords, sessions
|
||
(HttpOnly, SameSite=Strict) and CSRF tokens, rate limiting per user and
|
||
IP; hashed API tokens (`jarl_...`) with optional expiry.
|
||
- **Permissions**: granular grants (resource × read/write, filtered by zone,
|
||
view, record name pattern and type), roles (`readonly`, `dns-editor`,
|
||
`operator`, `acme`), tokens restricted to a subset of their user's
|
||
rights, administrators.
|
||
- **Audit log** of logins, changes, users, tokens and secret reveals, with
|
||
user, token, IP and outcome, searchable and written to the journal.
|
||
- **Web UI**: dashboard, zones and records with inline editing and pending
|
||
markers, views, ACLs, TSIG keys, the option editor, review and apply,
|
||
history with revert, audit log, users, account and tokens, search.
|
||
Review mode by default, with an "apply immediately" switch.
|
||
- **jarl CLI**: zones, records, views, ACLs, TSIG keys, options, settings,
|
||
changesets, batches, history, users, tokens, audit, search, status, check
|
||
and resync. Table, JSON and YAML output, and documented exit codes.
|
||
- **ACME DNS-01**: `jarl acme set|unset`, certbot hooks
|
||
(`jarl acme certbot-auth|certbot-cleanup`), an acme.sh DNS API hook
|
||
(`contrib/acme.sh/dns_jarl.sh`), automatic zone and view discovery,
|
||
idempotent retries, and `--follow-cname` / `--resolver` for a dedicated
|
||
ACME zone.
|
||
- **Operations**: `jarld backup` (online), `jarld resync`, `jarld check`,
|
||
`jarld render`, local user recovery commands, audit retention.
|
||
- **Packaging**: Makefile (build, test, e2e, install, dist), systemd unit,
|
||
sysusers.d and tmpfiles.d files, example configuration
|
||
(`jarld config --example`).
|
||
- **Tests**: unit tests for every package, integration tests against the
|
||
real named 9.20 (checkers, reload and rollback, import and adopt), and an
|
||
end-to-end script (`scripts/e2e.sh`) that drives every CLI command
|
||
against a running named.
|
||
- **Documentation**: concepts, install and adoption, configuration, CLI,
|
||
API, permissions, ACME, validation, operations and development guides, and
|
||
a split-DNS guide for adding views to a server that has none.
|
||
|
||
### Changed
|
||
|
||
- Web UI: the review/live switch is a small "Review edits" / "Live edits"
|
||
button in the header, and the bar below it only appears while there are
|
||
pending changes.
|
||
- TSIG keys: the name help no longer calls `xfer-key` a domain name (any
|
||
letters, digits, hyphens and dots will do; both servers must use the same
|
||
name), and the keys page points to restricted API tokens for giving
|
||
scripts or people limited access to zones and records.
|
||
- Web UI, new zone dialog: choose **Domain** or **Reverse zone**. For a
|
||
reverse zone you give the network (10.10.0.0/24, 2001:db8:1::/48) and the
|
||
zone name is generated. Either way the dialog shows the exact zone name it
|
||
will create and explains problems before submitting: an address or URL
|
||
instead of a name, a host-like name such as www.example.com, a zone that
|
||
already exists in the view, a child or parent of an existing zone, and
|
||
networks that don't follow the octet or nibble boundaries.
|
||
- Web UI, record form: after adding a record every field is cleared except
|
||
the type, and the cursor returns to the name. A "Will add:" line shows the
|
||
record exactly as it will be stored, with warnings for unqualified names
|
||
(e.g. a CNAME to readthedocs.io without the final dot). In reverse zones
|
||
the form asks for the address, defaults to PTR, offers the host names of
|
||
A/AAAA records with that address, warns about a second PTR for an
|
||
address, and the record list shows each record's address.
|
||
- Reverse zones: a record name can be given as an address (10.10.0.20) in
|
||
the API and CLI too, and a PTR target without a final dot that contains a
|
||
dot (nas.adbraun.lol) is read as the full host name (with a warning); a
|
||
bare label is refused.
|
||
|
||
- `jarl resync` / `jarld resync` refuse to overwrite files that were changed
|
||
outside Jarl unless `--force` is given, as documented. Before, they always
|
||
overwrote them.
|
||
|
||
### Fixed
|
||
|
||
- The logo popped in after the page: it was an 880 KB SVG wrapping a
|
||
1254-pixel PNG, downloaded in full on every page load. It is now traced
|
||
into 6 KB of vector paths and drawn inline. Static files also get an ETag,
|
||
so reloads revalidate them with 304 answers instead of downloading them
|
||
again.
|
||
- `jarl token create --save` stored the token at the top level of the
|
||
CLI configuration even when a named server was selected; it now goes into
|
||
that server's entry.
|
||
- The web UI showed future times, such as token expiry dates, as "just
|
||
now"; they now read "in 29 days".
|
||
- The dashboard's "Pending changes (1 open)" counted empty changesets (and
|
||
other users'), so the Changes page then showed none. It now counts your
|
||
own changesets that contain changes, and the status API's
|
||
`open_changesets` only counts non-empty ones. In review mode, a draft
|
||
created for an edit that the server rejects is discarded instead of
|
||
being left open and empty.
|
||
- Under the shipped systemd unit (`RestrictSUIDSGID=yes`), the first zone
|
||
in a new view failed with "chmod /var/lib/bind/jarl/VIEW: operation not
|
||
permitted": setting the setgid bit on the new directory is refused there.
|
||
Jarl now falls back to the permission bits, so the directory is
|
||
group-writable for named, and only changes the mode when it needs to.
|
||
- Web UI: the "Jarl 0.1.0" brand link no longer underlines the gap between
|
||
the name and the version on hover.
|
||
- Web UI: creating a zone from the dashboard's "New zone" link no longer
|
||
opens the dialog a second time.
|
||
- Warnings noticed while parsing record data (a name read as absolute) were
|
||
lost before reaching the result; they are shown again.
|
||
|
||
- The negative-caching warning now uses the effective negative TTL, which
|
||
is the smaller of the SOA minimum and the zone's default TTL (RFC 2308).
|
||
It explains the real effect (new names can stay invisible to resolvers
|
||
that already asked) and gives the command that fixes it. Before, it
|
||
fired on the SOA minimum alone and wrongly blamed BIND's resolver cap.
|
||
- Problems printed by `jarld check` show zone names without the trailing dot.
|
||
- `jarl` now shows the warnings a change introduces when it is applied or
|
||
staged, not only on dry runs.
|
||
- `jarl check` and `jarl resync` no longer print named-checkconf's routine
|
||
"loaded serial" lines, only its errors and warnings.
|
||
- `jarl zone list` showed only primary zones: `zone create`'s default
|
||
`--type primary` leaked into the list filter through a shared variable.
|
||
Hint, secondary, forward and other zones are listed again.
|
||
- named-checkconf and named-checkzone output is classified by the tools'
|
||
exit code. Their warnings (e.g. `both "recursion no;" and "allow-recursion"
|
||
active`) were shown as errors. They are now shown as warnings in the
|
||
CLI, `jarld check` and the web UI.
|
||
- The `zone_outside_view` error says how to fix it (`--adopt-zones`).
|
||
- The end-to-end test starts named with `-f` like the Debian unit; with `-g`,
|
||
named sends all logging to stderr.
|