• v0.2.0 c9590cc3d4

    Jarl v0.2.0
    release / release (push) Successful in 1m43s
    Stable

    adam released this 2026-10-09 21:15:13 +00:00 | 99 commits to main since this release

    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 .debs 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 .debs, 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.
    Downloads
  • Jarl v0.2.0-rc.2
    release / release (push) Successful in 1m42s
    Pre-Release

    adam released this 2026-10-09 16:00:39 +00:00 | 100 commits to main since this release

    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 .debs 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 .debs, 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.
    Downloads
  • Jarl v0.2.0-rc.1
    release / release (push) Successful in 7m19s
    Pre-Release

    adam released this 2026-10-09 15:29:01 +00:00 | 103 commits to main since this release

    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.
    • 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 .debs 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 .debs, 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.
    Downloads
  • v0.1.0 4d00be53c3

    Jarl v0.1.0
    release / release (push) Successful in 7m7s
    Stable

    adam released this 2026-10-09 08:40:56 +00:00 | 118 commits to main since this release

    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 .debs and SHA256SUMS), and uploads the .debs
      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.

    Downloads