# 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. ## Shell completion `make install` and the Debian package install bash completion for `jarl` and `jarld`; it works in new shells (it needs the `bash-completion` package, which Debian and Ubuntu install by default). Elsewhere, or for zsh and fish, `jarl completion bash|zsh|fish` prints the script; for example `jarl completion bash > ~/.local/share/bash-completion/completions/jarl`. Besides commands and flags, Tab completes: * `-s/--server` and the `config` commands: the server and group names in your configuration file (and `all`; after a comma, the next name); * `ZONE`, `VIEW`, `ACL`, `USER` and TSIG `KEY` arguments, and the `--view`, `--zone` and `--user` flags: names from the server, asked with your token. A zone in several views is offered once, with its views shown. If the server can't be reached in 3 seconds, nothing is offered; * record `TYPE`s, with a short description of each; * `-o/--output`: `table`, `json`, `yaml`. ## Setup On the DNS server the CLI comes with Jarl. To use it from another machine, install just the `jarl-cli` package there, or copy the static `jarl` binary ([install.md](install.md#just-the-cli-on-another-machine)). ```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, 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` | | Changeset to stage into (and the default for `jarl changeset diff/apply/...`) | `-C/--changeset` | `JARL_CHANGESET` (checked: open and yours, see [Changesets](#changesets-stage-review-apply)) | | Flags win over the environment, and the environment wins over the file. The file is `jarl/config.yaml` in the user's configuration directory, or `--config` / `JARL_CLI_CONFIG`: | System | File | |--------|------| | Linux and other Unix | `~/.config/jarl/config.yaml` (`$XDG_CONFIG_HOME/jarl/config.yaml` if set) | | macOS | `~/Library/Application Support/jarl/config.yaml` | | Windows | `%AppData%\jarl\config.yaml` (`C:\Users\\AppData\Roaming\jarl\config.yaml`) | It is written with mode 0600 (on Windows, the AppData folder is already private to the user). `jarl config show` prints its path and the settings, with the token masked. On Windows and macOS there is no local jarld, so always set a server. Get tokens from the web UI (Account → API tokens), from `jarl token create`, or from `jarld init --token` on the server. ### On the server itself On the DNS server, root needs no setup at all: ```sh sudo jarl status sudo jarl record add adbraun.lol www A 192.0.2.10 ``` With no server configured, the CLI talks to jarld through its local unix socket, `/run/jarl/jarl.sock`, where root and the `jarl` user are administrators without a token ([permissions.md](permissions.md#the-local-socket)). The history and audit log show such changes as made by `root` via `socket`. * `-s local` (or `JARL_SERVER=local`) uses the socket even when servers are configured, and `local` can be part of a list (`-s local,ns2`). `local` is reserved and can't be a configured server's name. * `JARL_SOCKET` points at another socket path. * A token given with `--token` or `JARL_TOKEN` is sent over the socket as well, and then its permissions apply. * Other users get a clear error ("only root and the jarl user can use jarld's local socket"); they configure a server and token as above. ### 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 | |------|---------| | `-o table\|json\|yaml` | Output format. JSON and YAML have the API's full objects, for scripts. | | `-o id`, `-o field=NAME[,NAME...]` | Just the IDs, or just those fields, one line per object (fields separated by tabs). See [Scripting tips](#scripting-tips). | | `-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). | | `--accept-risk` | Confirm a change that opens a public view up, such as an open resolver ([exposure.md](exposure.md)). Without it such a change stops with exit 5. | | `-q, --quiet` | Print only errors, or just the IDs: of what a command creates, or what `show` and `list` find (like `-o id`). | ## Exit codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | Other error (bad arguments, connection failure, ...) | | 2 | A check found problems (`zone serials`, `zone delegation`, `zone dnssec check-ds`, `zone registrar status`), or a step can't be taken yet (`zone dnssec disable` before turning off has finished); the report was printed | | 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 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 10.0.10.0/24 --fill # in the view whose records use it, with their PTRs jarl zone fill-ptr 10.0.10.0/24 # add the PTRs it lacks (zone commands take a network too) jarl zone suggest-reverse [--create] # reverse zones your private views lack (--create: yours) jarl zone suggest-reverse --dismiss 185.199.108.153/32 --view internal # not yours: don't suggest it again jarl view update vps --reverse-suggestions=false # no suggestions for this view jarl zone update adbraun.lol --auto-ptr # keep PTRs in step with the A/AAAA records (docs/reverse.md) 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 disable lab.adbraun.lol [--view V] # named stops serving it; kept (and editable) in Jarl jarl zone enable lab.adbraun.lol [--view V] jarl zone file adbraun.lol # the generated zone file jarl zone import adbraun.lol db.adbraun.lol [--replace] [--use-soa] jarl zone serials adbraun.lol # are the secondaries (or primaries) on the current serial? jarl zone delegation adbraun.lol [--resolver 9.9.9.9] # does the parent zone delegate it correctly? ``` **Disabling a zone** leaves it out of named's configuration but keeps it in Jarl: records, options and history, which can still be edited. Its names are then answered by recursion where the view offers it (the internet's version of the zone, if any), and REFUSED elsewhere; `disable` says which. Enabling it serves it as it is then. A signed zone can't be disabled (turn signing off first), nor a zone another view shares with `in-view`. 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 `zone create` sets any zone option in the same step. Linked zones ([views.md](views.md#linked-zones)) keep their shared records in step across views: ```sh jarl zone link adbraun.lol --view internal --with external jarl zone create adbraun.lol --view vps --link-with external jarl zone unlink adbraun.lol --view vps jarl record add adbraun.lol nas A 10.0.10.20 --view internal --local # this view only jarl record set adbraun.lol www A 10.0.10.5 --view internal --local # override the shared www A here jarl record update 1234 --local | --shared jarl zone diff adbraun.lol [--view V --view W] [--all] # what differs between the views ``` When a zone exists in several views, add `--view NAME` to commands that name it. `zone serials` asks the zone's other name servers which serial they serve (the NS records and also-notify targets of a primary zone, the primaries of a secondary) and exits with 2 if one isn't on the current serial. `zone delegation` is for zones on the internet: it checks the parent zone's delegation and glue against the zone, and calls a zone that isn't delegated (an internal-only zone) a note, not a problem. Both only run when you run them; see [operations.md](operations.md#checking-name-servers-and-the-delegation). ### DNSSEC and the registrar ```sh jarl zone dnssec status adbraun.lol --view external # keys, DS record, what's next jarl zone dnssec enable adbraun.lol --view external [--policy NAME] jarl zone dnssec check-ds adbraun.lol --view external # has the parent got the DS? tells named jarl zone dnssec disable adbraun.lol --view external # step 1: insecure; run again to finish jarl zone dnssec wipe adbraun.lol --view external [--force] # off at once, keys deleted jarl zone registrar status adbraun.lol --view external # Porkbun: NS, glue, DS vs the zone jarl zone registrar sync-glue adbraun.lol --view external [--dry-run] jarl zone registrar set-ns adbraun.lol --view external [NS...] [--dry-run] jarl zone registrar publish-ds adbraun.lol --view external [--key TAG] jarl zone registrar remove-ds adbraun.lol 51126 --view external ``` [dnssec.md](dnssec.md) explains the steps. `enable` is safe at any time: validation only starts once the zone's DS record is at the parent, and `status` says when to publish it. `disable` never makes a zone unsigned while the parent may still have its DS: its first run switches to BIND's insecure policy, and it finishes (exit 0) only when named is done (exit 2 before). `wipe` switches off at once and deletes the keys, and refuses while the parent zone has a DS record for the zone; `--force` wipes when the parent can't be checked. The `registrar` commands need a Porkbun API key in jarl.yaml and an administrator. Each change checks first that it won't break the domain and is refused otherwise: `--dry-run` shows the checks, `--force` skips them. (Here `--force` means that; elsewhere it overwrites files changed outside Jarl.) ## Records ```sh jarl record list adbraun.lol [--name www] [-t A] [--search nas] [--disabled] 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] jarl record disable adbraun.lol www A 192.168.1.10 --ptr # out of the zone, kept in Jarl jarl record enable adbraun.lol www A 192.168.1.10 --ptr # back again jarl record disable --id 1234 # by ID; same for enable # 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 # a zone in several views (split horizon): the same change in each jarl record add adbraun.lol mx2 A 203.0.113.5 --view internal,external jarl record delete adbraun.lol old A --view all -y ``` **Several views.** When a zone exists in more than one view, the record commands that change records (`add`, `set`, `delete`, `disable`, `enable`) take `--view a,b` or `--view all` (every view the zone is in). The changes are applied together as one changeset: if one view refuses (the zone isn't in it, a CNAME conflict, ...), no view is changed. With `--changeset` they are staged there instead. Keep split-horizon data that differs per view (an internal address, say) to one view at a time. 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); `jarl record update ID --ptr` with nothing else adds the PTR record if it is missing, e.g. after creating the reverse zone. In linked zones, the reverse zones of every linked view count. 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. **Disabling** a record takes it out of the zone without deleting it: it keeps its ID, TTL and comment, the zone file lists it as a comment (`; disabled: www IN A ...`), and `enable` puts it back exactly as it was. `record list` marks disabled records `[disabled]`, and `zone show` counts them. The zone is validated as it is served: you can't disable its last NS record, and enabling a record is checked like adding it (a CNAME added at the same name in the meantime stops it). `record set` enables a disabled record whose value it is given, and leaves other disabled records alone. ## 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} - disable: {zone: adbraun.lol, name: old, type: A} # or {id: ...}; enable: likewise - 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 # validation + named-checkconf + diff jarl changeset remove 2 # drop operation #2 jarl changeset apply 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 ``` `show`, `diff`, `apply`, `discard` and `remove` work on the changeset you name, else the one in `-C`, else the one in `JARL_CHANGESET`. A changeset from `JARL_CHANGESET` is checked before anything uses it, because an exported ID easily outlives its changeset, and IDs keep growing (every change, live ones included, gets the next one), so a stale or mistyped ID can later name a changeset that isn't yours: - Staging into it, and `diff`, `apply`, `discard` and `remove` on it, need it to be open and yours. Otherwise the command stops and says why (`there is no changeset 99`, `changeset 42 is applied`, `changeset 57 is certbot's`) and what to do. Commands that only read (`jarl zone list`) and `jarl changeset create` don't care, so you can always start a new one. - Each command says on stderr which changeset it uses: `changeset 42 (renumber lab, 2 operations) from JARL_CHANGESET` (not with `-q`). - After `apply` or `discard`, a reminder to `unset JARL_CHANGESET`: the CLI can't change your shell's environment, and later changes would fail to stage. `-C ID` and an ID argument are used as given, without these checks (jarld still refuses a changeset that isn't open, or another user's unless you are an administrator). Changesets staged in the web UI show up here too, and the other way round. ## Internet exposure ```sh jarl exposure # public views, findings, fixes, rate limiting jarl exposure fix external exposure_rate_limit # rate limiting, log-only first jarl exposure fix external exposure_rate_limit_log_only # then start enforcing jarl exposure fix server exposure_version jarl exposure mail # reverse DNS of the addresses that send and receive mail jarl exposure ack external security_open_resolver --reason "public resolver, monitored" jarl exposure unack external security_open_resolver jarl view update external --exposure public|private|auto ``` See [exposure.md](exposure.md). ## Encrypted DNS ```sh jarl encrypted-dns # what is set up, what to give clients jarl encrypted-dns setup --hostname dns.adbraun.lol --address 10.0.10.5 [--address IPv6] [--doh-port 8443] [--no-doh] jarl encrypted-dns check --address 10.0.10.5 --doh-port 443 jarl encrypted-dns health # check the listeners now, as a client would jarl encrypted-dns disable ``` See [encrypted-dns.md](encrypted-dns.md). ## Certificates ```sh jarl cert check --domain ns1.adbraun.lol # where each challenge would go, and why jarl cert create ns1 --domain ns1.adbraun.lol --use web --use dns # obtained at once; waits jarl cert create wild -d adbraun.lol -d '*.adbraun.lol' --use dns --key-type ec384 jarl cert create own --cert-file /etc/ssl/ns1/fullchain.pem --key-file /etc/ssl/ns1/privkey.pem --use web jarl cert list jarl cert show ns1 jarl cert issue ns1 # renew now (--no-wait to return at once) jarl cert update ns1 --challenge porkbun # auto, jarl or porkbun jarl cert update ns1 --use dns # --use and --domain replace the list (--use none) jarl cert delete ns1 jarl cert account # the ACME account (set in jarl.yaml) ``` Administrators only. See [certificates.md](certificates.md). ## 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 view color internal green # web UI colour; "default" resets it jarl view key external [--named-conf] # the key that selects the view (dig -k), docs/views.md jarl acl create homelab 192.168.1.0/24 10.10.0.0/16 localhost jarl acl create dhcp 10.0.10.101-10.0.10.199 2001:db8::100-2001:db8::1ff # ranges (concepts.md) 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 forwarders 1.1.1.1 9.9.9.9 # the same: braces are added to a list jarl option set allow-transfer 'key xfer; 10.0.10.53' --zone adbraun.lol jarl option set allow-query '10.0.20.1-10.0.20.99; localhost' --view internal 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 option generated # what Jarl adds to named.conf by itself, and why ``` Options that end in a list (`allow-*`, `forwarders`, `listen-on`, ...) may be given without the braces: elements are separated by `;`, `,` or spaces, and `key NAME` and `port N` stay with their element. Options are always listed in one fixed order (see [concepts.md](concepts.md)). ```sh 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 bob's tokens jarl user update admin --rename adam # rename (e.g. the first account, to your own name) jarl user totp reset alice # turn off alice's two-factor login (lost phone) jarl user delete bob -y jarl token list [--user alice | --all] [--revoked] jarl token list --all --expiring [60d] # expiring within 14 days (or 60d), or expired jarl token create laptop --expires 365d jarl token create backup-script --grant "*:read" jarl token create acme --user certbot --role acme jarl token rotate laptop --save # new secret, same token and grants jarl token rotate --current --save --grace 10m # the token this CLI uses jarl token revoke 7 # 'jarl user list' and 'jarl user show' include two-factor login (2FA: on, # off, or MISSING when totp.require applies and it isn't set up). Users set # it up themselves in the web UI (Account); the CLI's tokens never need a # code (permissions.md#two-factor-login). 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, backups, expiring tokens, problems jarl check # validation + named-checkconf of the current configuration jarl resync [--force] [--dry-run] # rewrite every file from the database ``` Grant syntax, roles and token rotation are in [permissions.md](permissions.md). When the token `jarl` uses expires within 14 days, every command prints a warning on stderr (once), naming the expiry and how to rotate it. Scripts such as certbot hooks show it in their logs well before the token stops working. ## Query statistics `--view internal,vps` (or repeated) limits any `jarl stats` command to those views ([stats.md](stats.md#views)). ```sh jarl stats status jarl stats summary --since 7d jarl stats top names | clients | nxdomain | servfail | refused | blocked | blocked-clients [--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 | blocklist jarl stats server # named's memory, CPU, cache, clients; host; disk space ``` Statistics must be enabled in `jarl.yaml`, and `-o json` exports the data. See [stats.md](stats.md). ## Blocking ```sh jarl blocklist list jarl blocklist add NAME URL [--view V]... # default: every view that offers recursion jarl blocklist update NAME [--url U] [--name N] [--view V | --view auto] [--comment C] jarl blocklist disable | enable | delete NAME jarl blocklist refresh [NAME...] [--force] # download now (all lists without NAME) jarl block add DOMAIN [--exact] [--view V]... # block it (and the names below it) jarl block allow DOMAIN [--exact] [--view V]... # an exception that beats every list jarl block list jarl block delete DOMAIN [--view V] | --id ID jarl block check DOMAIN # blocked? by which rule or list? jarl block pause [DURATION] # let everything through for a while (default 10m, at most 24h) jarl block pause indefinitely # ... until 'jarl block resume' jarl block step list | add NAME | rename STEP NEW | order STEP... | delete STEP jarl block group list jarl block group add NAME MEMBER... [--step S] [--view V]... # addresses, networks or ACLs jarl block group update GROUP [--add M] [--remove M] [--step S] [--name N] jarl block group delete GROUP jarl blocklist update LIST --step STEP # devices at that step or above get it jarl block resume ``` See [blocking.md](blocking.md). ## ACME DNS-01 ```sh jarl acme set www.adbraun.lol # TXT at _acme-challenge.www.adbraun.lol jarl acme set '*.adbraun.lol' # wildcard + apex use the same name jarl acme unset www.adbraun.lol [] 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 id` prints just the IDs of what a `show` or `list` command finds, one per line, and `-o field=...` picks fields (their names are those of `-o json`; a dot reaches inside, as in `soa.serial`; a field an object doesn't have prints as empty). For changes, they apply to the changed object: ```sh ZONE=$(jarl zone show adbraun.lol --view external -o id) jarl record list adbraun.lol --name www -t A -o id | xargs -n1 jarl record disable --id jarl zone show adbraun.lol -o field=view,soa.serial # external 2026100901 jarl zone list -o field=id,name,view ID=$(jarl record add adbraun.lol www A 192.0.2.10 -o id) # the new record's ID ``` * `-q` with `create` commands prints just the ID or token, and with `show` and `list` just the IDs. Commands without IDs (status, checks) print nothing but errors. * `jarl record list` on a terminal shortens data too long for a line (`…`); `--wide` shows it in full, and so does output to a pipe or file. * Check `$?` against the exit code table. * `jarl record delete ... --missing-ok` succeeds when nothing matches, which keeps cleanup scripts idempotent. So does `record disable|enable --missing-ok`, also when the records are already disabled (enabled). `jarl record set` is idempotent by nature.