Multi-server CLI, more reverse zones, and a fix for new view directories

- 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>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-08 08:12:03 +00:00
1 parent a7d389be27
commit 785b0006f9
22 files changed
+1375 -185

No files matched your search

+60 -1
View File
@@ -14,7 +14,7 @@ jarl whoami
| Setting | Flag | Environment | Config file key |
|---------|------|-------------|-----------------|
| Server URL | `--server` | `JARL_SERVER` | `server` |
| Server URL, or a server or group name (below) | `-s/--server` | `JARL_SERVER` | `server` (`default` for a name) |
| API token (`@FILE` reads it from a file) | `--token` | `JARL_TOKEN` | `token` |
| Skip TLS verification | `--insecure` | | `insecure` |
| Extra trusted CA (PEM) | `--ca-cert` | | `ca_cert` |
@@ -27,6 +27,55 @@ file is `~/.config/jarl/config.yaml` (mode 0600), or `--config` /
Get tokens from the web UI (Account → API tokens), from
`jarl token create`, or from `jarld init --token` on the server.
### Several servers and groups
With more than one Jarl (say a primary and a secondary, each with its own
jarld), give each a name; group them to run a command on several at once:
```sh
jarl config server add ns1 https://ns1.adbraun.lol:8053 # asks for ns1's token
jarl config server add ns2 https://ns2.adbraun.lol:8053
jarl config group set dns ns1 ns2
jarl config use ns1 # the default when --server is left out
jarl -s ns2 zone list # or JARL_SERVER=ns2
jarl -s dns acl update homelab --add 10.0.20.0/24
jarl -s all status # every named server
jarl -s ns2 config set token # change ns2's token
```
The file then looks like this:
```yaml
servers:
ns1: {url: "https://ns1.adbraun.lol:8053", token: jarl_...}
ns2: {url: "https://ns2.adbraun.lol:8053", token: "@/home/adam/.config/jarl/ns2.token"}
groups:
dns: [ns1, ns2]
default: ns1
```
`--server` takes a server name, a group (groups may contain groups),
`all`, a comma separated list of those, or a URL. A named server always uses
its own token; `--token` and `JARL_TOKEN` only fill in for servers without
one, so a token is never sent to the wrong server. The plain `server:` and
`token:` keys still work and are used when nothing is selected.
**How a command runs on a group.** It is first run on every server as a dry
run (in parallel). If any server rejects it (for example a record in a zone
that is a secondary there, an ACL that already exists, or a token without the
permission), the errors are shown and *nothing is changed anywhere*. Only
when it would succeed everywhere does it run for real, one server after
another, with a `== ns1 (URL) ==` header before each server's output. A
read-only command simply shows each server's answer. If a server still fails
in the real run (it went down in between, say), jarl stops and says which
servers already applied the change.
Commands that can't be dry-run (`token create`, `user ...`, `changeset
create/discard/revert`) refuse to run on a group, and `--changeset` can't be
combined with one, since changeset IDs belong to a single server. Headers go
to stderr with `-o json` or `-o yaml`, so stdout holds just the documents.
## Global flags
| Flag | Meaning |
@@ -65,6 +114,8 @@ jarl zone list [--view V] [--type primary]
jarl zone show adbraun.lol
jarl zone create lab.adbraun.lol --ns ns1.adbraun.lol.
jarl zone create 192.168.20.0/24 # reverse zone from a network
jarl zone create 10.0.8.0/22 # four zones, 8-11.0.10.in-addr.arpa
jarl zone create 10.0.10.100/32 # a zone for just that address
jarl zone create adbraun.lol --view external --clone-from adbraun.lol --clone-view internal
jarl zone create partner.example --type secondary --primary "192.0.2.53 key xfer"
jarl zone create consul --type forward --forwarder "127.0.0.1 port 8600"
@@ -75,6 +126,14 @@ jarl zone file adbraun.lol # the generated zone file
jarl zone import adbraun.lol db.adbraun.lol [--replace] [--use-soa]
```
Reverse names follow the dots (the nibbles in IPv6), so a reverse zone
covers a /8, /16 or /24 (a multiple of /4 for IPv6). A network in between is
covered by several zones, created together in one changeset: a /22 by four
/24 zones (up to 16 zones). A /32 (or /128) gets a zone of its own that
answers for that one address: useful when another server, such as your
router, answers for the rest of the /24. IPv4 networks smaller than a /24
(/25 to /31) live in the enclosing /24's zone.
`zone import` adds the records of a zone file (`-` reads stdin). With
`--replace` the zone is made equal to the file, so records that aren't in it
are deleted. Try it with `--dry-run` first. `--option "NAME VALUE"` on