- 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>
259 lines
11 KiB
Markdown
259 lines
11 KiB
Markdown
# The `jarl` command line client
|
|
|
|
`jarl` talks to jarld's REST API with an API token. It runs on the DNS server
|
|
or on any machine that can reach jarld. Every command has `--help` with
|
|
examples, and `jarl completion bash|zsh|fish` prints shell completion.
|
|
|
|
## Setup
|
|
|
|
```sh
|
|
jarl config set server https://ns1.adbraun.lol:8053
|
|
jarl config set token # paste the token at the prompt
|
|
jarl whoami
|
|
```
|
|
|
|
| Setting | Flag | Environment | Config file key |
|
|
|---------|------|-------------|-----------------|
|
|
| Server URL | `--server` | `JARL_SERVER` | `server` |
|
|
| 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` |
|
|
| Changeset to stage into | `-C/--changeset` | `JARL_CHANGESET` | |
|
|
|
|
Flags win over the environment, and the environment wins over the file. The
|
|
file is `~/.config/jarl/config.yaml` (mode 0600), or `--config` /
|
|
`JARL_CLI_CONFIG`. `jarl config show` prints it with the token masked.
|
|
|
|
Get tokens from the web UI (Account → API tokens), from
|
|
`jarl token create`, or from `jarld init --token` on the server.
|
|
|
|
## Global flags
|
|
|
|
| Flag | Meaning |
|
|
|------|---------|
|
|
| `-o table\|json\|yaml` | Output format. JSON and YAML have the API's full objects, for scripts. |
|
|
| `-n, --dry-run` | Validate and show the diff without changing anything. |
|
|
| `-C, --changeset ID` | Stage the change into an open changeset instead of applying it. |
|
|
| `--force` | Overwrite BIND files that were changed outside Jarl (drift). |
|
|
| `-q, --quiet` | Print only errors, or just the ID where a command creates something. |
|
|
|
|
## Exit codes
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| 0 | Success |
|
|
| 1 | Other error (bad arguments, connection failure, ...) |
|
|
| 3 | Not authenticated, or not allowed (token lacks the permission) |
|
|
| 4 | Not found |
|
|
| 5 | Conflict: concurrent change, object in use, drift |
|
|
| 6 | Invalid request, or rejected by validation (e.g. a CNAME conflict, or named-checkconf found an error) |
|
|
| 7 | Server error, or BIND failed: a tool couldn't run, or named didn't load the change (it was rolled back) |
|
|
|
|
Validation errors are printed with the offending zone and view:
|
|
|
|
```
|
|
$ jarl record add adbraun.lol www A 192.168.1.99
|
|
error: www.adbraun.lol has a CNAME, so it can't have any other records (found A); a CNAME must be the only record at its name (RFC 1034) [zone adbraun.lol]
|
|
$ echo $?
|
|
6
|
|
```
|
|
|
|
## Zones
|
|
|
|
```sh
|
|
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 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"
|
|
jarl zone update adbraun.lol --ttl 3600 --contact hostmaster@adbraun.lol --refresh 7200
|
|
jarl zone update adbraun.lol --move-to-view internal
|
|
jarl zone delete lab.adbraun.lol [-y]
|
|
jarl zone file adbraun.lol # the generated zone file
|
|
jarl zone import adbraun.lol db.adbraun.lol [--replace] [--use-soa]
|
|
```
|
|
|
|
`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
|
|
`zone create` sets any zone option in the same step.
|
|
|
|
When a zone exists in several views, add `--view NAME` to commands that
|
|
name it.
|
|
|
|
## Records
|
|
|
|
```sh
|
|
jarl record list adbraun.lol [--name www] [-t A] [--search nas]
|
|
jarl record add adbraun.lol nas A 192.168.1.20 --ptr --comment "synology"
|
|
jarl record add adbraun.lol @ MX 10 mail
|
|
jarl record add adbraun.lol @ TXT "v=spf1 mx -all" # plain text, quoting is automatic
|
|
jarl record add adbraun.lol _sip._tcp SRV 0 5 5060 sip
|
|
jarl record add adbraun.lol @ CAA 0 issue "letsencrypt.org"
|
|
jarl record set adbraun.lol api A 192.168.1.30 192.168.1.31 --ttl 60 # replace the RRset
|
|
jarl record update 1234 --data 192.168.1.21 --ptr # IDs from 'record list'
|
|
jarl record update 1234 --ttl default
|
|
jarl record delete adbraun.lol www A # every A record of www
|
|
jarl record delete adbraun.lol www A 192.168.1.10 # just this one
|
|
jarl record delete --id 1234 [--ptr]
|
|
|
|
# reverse zones: name records by address; the host name needs no final dot
|
|
jarl record add 0.10.10.in-addr.arpa 10.10.0.20 PTR nas.adbraun.lol
|
|
```
|
|
|
|
Names are relative to the zone (`@` is the apex) unless they end with a
|
|
dot. Data uses zone file syntax, except that TXT/SPF take plain text: Jarl
|
|
adds the quotes and splits long values into 255-byte strings. In reverse zones the name can be the address
|
|
(`10.10.0.20`), and a PTR target such as `nas.adbraun.lol` is read as the full
|
|
host name (a bare `nas` is refused, since it would mean
|
|
`nas.0.10.10.in-addr.arpa.`). `--ptr`
|
|
keeps the PTR record in the reverse zone in step (when that zone is managed
|
|
by Jarl). Every record type BIND supports can be used. Jarl knows the
|
|
syntax of the common ones and checks them strictly, and the rest are
|
|
validated by parsing.
|
|
|
|
## Batches: `jarl apply -f`
|
|
|
|
Several changes that must succeed or fail together:
|
|
|
|
```yaml
|
|
# move-web.yaml
|
|
description: move the web server to the new VM
|
|
changes:
|
|
- add: {zone: adbraun.lol, name: web2, type: A, data: 192.168.1.40, ttl: 300, ptr: true}
|
|
- delete: {zone: adbraun.lol, name: www, type: CNAME}
|
|
- add: {zone: adbraun.lol, name: www, type: CNAME, data: web2}
|
|
- set: {zone: adbraun.lol, name: api, type: A, data: [192.168.1.41, 192.168.1.42], ttl: 60}
|
|
- update: {id: 1235, comment: "old web server, remove in November"}
|
|
- delete: {id: 1240}
|
|
- op: acl.update # any API operation (see api.md)
|
|
id: 3
|
|
data: {elements: [192.168.1.0/24, 10.10.0.0/16]}
|
|
```
|
|
|
|
```sh
|
|
jarl apply -f move-web.yaml --dry-run # check and show the diff
|
|
jarl apply -f move-web.yaml
|
|
```
|
|
|
|
The whole file is validated as one changeset, at its end state, so the
|
|
order of the entries doesn't matter (the CNAME swap above works either way
|
|
round). Add `view: NAME` to entries
|
|
when a zone exists in several views. JSON works as well as YAML, and `-f -`
|
|
reads standard input.
|
|
|
|
## Changesets: stage, review, apply
|
|
|
|
```sh
|
|
export JARL_CHANGESET=$(jarl changeset create "renumber lab" -q)
|
|
jarl record set lab.adbraun.lol gw A 10.20.0.1
|
|
jarl record set lab.adbraun.lol nas A 10.20.0.10
|
|
jarl changeset diff $JARL_CHANGESET # validation + named-checkconf + diff
|
|
jarl changeset remove $JARL_CHANGESET 2 # drop operation #2
|
|
jarl changeset apply $JARL_CHANGESET
|
|
unset JARL_CHANGESET
|
|
|
|
jarl changeset list [--status open|applied|discarded] [--mine]
|
|
jarl changeset show 42
|
|
jarl changeset discard 43
|
|
jarl history [--limit 50]
|
|
jarl changeset revert 42 -q | xargs jarl changeset apply # undo an applied changeset
|
|
```
|
|
|
|
Changesets staged in the web UI show up here too, and the other way round.
|
|
|
|
## Views, ACLs, TSIG keys, options, settings
|
|
|
|
```sh
|
|
jarl view create internal --match-clients homelab,localhost --adopt-zones
|
|
jarl view create external --match-clients any
|
|
jarl view order internal external
|
|
jarl view update external --match-clients "!homelab,any"
|
|
jarl view delete old [--cascade]
|
|
|
|
jarl acl create homelab 192.168.1.0/24 10.10.0.0/16 localhost
|
|
jarl acl update homelab --add 10.20.0.0/16 --remove 10.10.0.0/16
|
|
jarl acl update homelab --rename lan # references are updated
|
|
jarl acl delete lan # refused while in use
|
|
|
|
jarl tsig create xfer [--algorithm hmac-sha512]
|
|
jarl tsig show xfer --named-conf # paste into the secondary's named.conf
|
|
jarl tsig rotate xfer
|
|
jarl tsig delete xfer
|
|
|
|
jarl option list [--view V | --zone Z | --toplevel | --all]
|
|
jarl option set recursion yes
|
|
jarl option set allow-recursion '{ homelab; localhost; }'
|
|
jarl option set forwarders '{ 1.1.1.1; 9.9.9.9; }'
|
|
jarl option set allow-transfer '{ key xfer; }' --zone adbraun.lol
|
|
jarl option set response-policy '{ zone "rpz.local"; }' --view internal
|
|
jarl option set logging '{ channel q { file "/var/log/named/query.log" versions 3 size 20m; }; category queries { q; }; }' --toplevel
|
|
jarl option unset forwarders
|
|
jarl option describe # every option allowed at this scope
|
|
jarl option describe allow-query-cache # grammar and ARM description
|
|
|
|
jarl settings show
|
|
jarl settings set --ns ns1.adbraun.lol.,ns2.adbraun.lol. --contact hostmaster@adbraun.lol --ttl 3600
|
|
```
|
|
|
|
## Users, tokens, audit (administration)
|
|
|
|
```sh
|
|
jarl user list
|
|
jarl user create alice --role dns-editor # asks for a password
|
|
jarl user create ci --no-password --grant "records:write zones=lab.adbraun.lol"
|
|
jarl user update alice --admin
|
|
jarl user update alice --password # set a new password
|
|
jarl user update bob --disabled # logs bob out, blocks his tokens
|
|
jarl user delete bob -y
|
|
|
|
jarl token list [--user alice | --all] [--revoked]
|
|
jarl token create laptop --expires 365d
|
|
jarl token create backup-script --grant "*:read"
|
|
jarl token create acme --user certbot --role acme
|
|
jarl token revoke 7
|
|
|
|
jarl audit [--user alice] [--action auth.login_failed] [--outcome failure] [--since 2026-10-01] [--limit 100]
|
|
jarl search 192.168.1.20
|
|
jarl status # named, adoption, drift, open changesets, problems
|
|
jarl check # validation + named-checkconf of the current configuration
|
|
jarl resync [--force] [--dry-run] # rewrite every file from the database
|
|
```
|
|
|
|
Grant syntax and roles are in [permissions.md](permissions.md).
|
|
|
|
## Query statistics
|
|
|
|
```sh
|
|
jarl stats status
|
|
jarl stats summary --since 7d
|
|
jarl stats top names | clients | nxdomain | servfail | refused [--since 24h] [--limit 20]
|
|
jarl stats timeline --since 6h [--key rcode:NXDOMAIN] [--table]
|
|
jarl stats heatmap --since 28d
|
|
jarl stats breakdown qtype | rcode | view | zone | answer | rtt
|
|
```
|
|
|
|
Statistics must be enabled in `jarl.yaml`, and `-o json` exports the data.
|
|
See [stats.md](stats.md).
|
|
|
|
## ACME DNS-01
|
|
|
|
```sh
|
|
jarl acme set www.adbraun.lol <validation> # TXT at _acme-challenge.www.adbraun.lol
|
|
jarl acme set '*.adbraun.lol' <value1> <value2> # wildcard + apex use the same name
|
|
jarl acme unset www.adbraun.lol [<validation>]
|
|
jarl acme certbot-auth / certbot-cleanup # certbot manual hooks
|
|
```
|
|
|
|
See [acme.md](acme.md) for certbot, acme.sh and a dedicated ACME zone.
|
|
|
|
## Scripting tips
|
|
|
|
* `-o json` gives full API objects. Pipe them to `jq`.
|
|
* `-q` with `create` commands prints just the ID or token.
|
|
* Check `$?` against the exit code table.
|
|
* `jarl record delete ... --missing-ok` succeeds when nothing matches, which
|
|
keeps cleanup scripts idempotent. `jarl record set` is idempotent by nature.
|