milestone 20: declarative configuration for iac
This commit is contained in:
@@ -154,11 +154,11 @@ in step 5, and step 4 has to write a file into it before then. systemd does not
|
||||
mind finding the directory already there; it adjusts the mode and ownership to
|
||||
what the unit asks for.
|
||||
|
||||
## 4. Write the seed configuration
|
||||
## 4. Write the configuration
|
||||
|
||||
nxdns starts from an empty database only if a configuration file tells it what
|
||||
to forward to. Write `/etc/nxdns/config.zon`. The smallest file that starts is
|
||||
one group named `default` and one enabled upstream:
|
||||
nxdns will not start with nothing to forward to. Write `/etc/nxdns/config.zon`.
|
||||
The smallest file that starts is one group named `default` and one enabled
|
||||
upstream:
|
||||
|
||||
```zon
|
||||
.{
|
||||
@@ -213,36 +213,49 @@ The upstream probe sends a real query, so this needs working DNS on the host at
|
||||
the time you run it. Exit 2 means `check` found something to fix and printed
|
||||
every problem it found, not only the first.
|
||||
|
||||
The file seeds the database once. From the second start onwards it is ignored
|
||||
and the database is the configuration; see
|
||||
[the configuration model](../explanation/configuration-model.md) and
|
||||
[Upgrade nxdns](upgrade.md) for how to change settings after that.
|
||||
Now load it into the database:
|
||||
|
||||
Once the seed has been consumed — after step 6 confirms you can log in — the
|
||||
plaintext in it is dead weight that only carries risk. The seed's
|
||||
`web.password` is hashed into `web.password_hash` at import time and the
|
||||
plaintext is never stored; `nxdns export` writes `.password = ""` back out
|
||||
alongside the hash. Nothing downstream ever reads the plaintext again, so
|
||||
delete the file:
|
||||
```sh
|
||||
nxdns import /etc/nxdns/config.zon
|
||||
```
|
||||
|
||||
```
|
||||
info(migrations): config.db migrated from schema version 0 to 2
|
||||
imported /etc/nxdns/config.zon
|
||||
```
|
||||
|
||||
The plaintext password is hashed into `web.password_hash` and never stored as
|
||||
plaintext; `nxdns export` writes `.password = null` beside the hash. Nothing
|
||||
downstream reads the plaintext again, so once step 6 confirms you can log in you
|
||||
can delete the file:
|
||||
|
||||
```sh
|
||||
rm /etc/nxdns/config.zon
|
||||
```
|
||||
|
||||
Keep it only if you want the seed as a record of the intended starting
|
||||
configuration, and if you keep it, leave it at 0640 root:nxdns. Note that a
|
||||
kept seed is not a backup — `nxdns export` is
|
||||
(see [Back up and restore](back-up-and-restore.md)), and the export carries the
|
||||
password hash rather than the password.
|
||||
A kept file is not a backup — `nxdns export` is (see
|
||||
[Back up and restore](back-up-and-restore.md)), and the export carries the
|
||||
password hash rather than the password. If you keep it, leave it at 0640
|
||||
root:nxdns.
|
||||
|
||||
That is the **database mode** install, which is what the packaged unit runs:
|
||||
`ExecStart=/usr/local/bin/nxdns run`, no `--config`, so nothing reads a file
|
||||
after this step. Change settings afterwards through the admin interface, the
|
||||
API, or an export–edit–import cycle.
|
||||
|
||||
If you would rather keep `/etc/nxdns/config.zon` in git and have every restart
|
||||
converge onto it, do not delete the file — go to
|
||||
[Run in file mode](#run-in-file-mode) instead, and skip the `rm`.
|
||||
|
||||
> Verified on this host, with a scratch `--config` and `--data-dir` in place of
|
||||
> `/etc/nxdns` and `/var/lib/nxdns`: a seed written under umask 022 came out
|
||||
> 0644, `nxdns check --config` on it printed `OK: no problems found` with no
|
||||
> mode warning, and after `nxdns import` of that seed an `nxdns export` wrote
|
||||
> `.password = ""` next to a populated `.password_hash =
|
||||
> "$argon2id$v=19$..."`. The `chown`, `chmod` and `rm` lines above are the
|
||||
> ordinary root-owned-file operations and were not run against a real
|
||||
> `/etc/nxdns`, which this host does not have.
|
||||
> `/etc/nxdns` and `/var/lib/nxdns` — those two paths are the only difference
|
||||
> from the blocks above. A file written under umask 022 came out 0644,
|
||||
> `nxdns check --config` on it printed `OK: no problems found` with no mode
|
||||
> warning, `nxdns import` of it printed the migration line and `imported <path>`
|
||||
> and exited 0, and a following `nxdns export` wrote `.password = null` next to
|
||||
> a populated `.password_hash = "$argon2id$v=19$m=19456,t=2,p=1$…"`. The
|
||||
> `chown`, `chmod` and `rm` lines are ordinary root-owned-file operations and
|
||||
> were not run against a real `/etc/nxdns`, which this host does not have.
|
||||
|
||||
## 5. Start it
|
||||
|
||||
@@ -263,9 +276,16 @@ nxdns writes to stderr and systemd captures that into the journal; logging
|
||||
needs no further configuration. Port 53 is privileged, and the unit grants
|
||||
`CAP_NET_BIND_SERVICE` through `AmbientCapabilities`.
|
||||
|
||||
The unit does not restart the service after exit 2 or exit 64
|
||||
(`RestartPreventExitStatus=2 64`). Those are a wrong configuration and a wrong
|
||||
command line, and neither clears on a retry — restarting every two seconds until
|
||||
`StartLimitBurst` gives up would only bury the diagnostics that are already in
|
||||
the journal. `systemctl status nxdns` shows the failed state; fix the cause and
|
||||
start it again.
|
||||
|
||||
If the start fails, read [Troubleshoot nxdns](troubleshoot.md). The two common
|
||||
first-install failures are a port 53 already held by `systemd-resolved` and a
|
||||
seed file that does not parse.
|
||||
configuration file that does not parse.
|
||||
|
||||
## 6. Confirm it answers
|
||||
|
||||
@@ -276,7 +296,7 @@ dig @<server-ip> example.com A +short
|
||||
```
|
||||
|
||||
The admin interface is on port 8080 by default; log in with the password from
|
||||
the seed file. `http://<server-ip>:8080/api/health` reports upstream
|
||||
the configuration file. `http://<server-ip>:8080/api/health` reports upstream
|
||||
availability and disk state without a login.
|
||||
|
||||
> Not verified on this host as written: `<server-ip>` is a placeholder, and a
|
||||
@@ -287,6 +307,140 @@ availability and disk state without a login.
|
||||
> port returned 200. Only the address and the port differ from the lines
|
||||
> above.
|
||||
|
||||
## Run in file mode
|
||||
|
||||
In file mode `/etc/nxdns/config.zon` is the configuration: every start converges
|
||||
the database onto it, and the admin interface refuses configuration edits with a
|
||||
403 naming the file. Use it when you want the file in git and deployed by
|
||||
Ansible. Stay in database mode when you want the UI to be the way things change.
|
||||
|
||||
The packaged unit is flagless on purpose — it is correct as shipped, and a
|
||||
commented-out alternative `ExecStart` in a unit file is documentation
|
||||
masquerading as configuration. File mode is a drop-in.
|
||||
|
||||
### Adopt file mode on a box that is already running
|
||||
|
||||
Run these in order. **Stop first**, and do not skip that: any edit made through
|
||||
the UI between an export and the restart would be silently reverted by the first
|
||||
reconcile, and `nxdns check` against a live database refuses to grade it (below).
|
||||
|
||||
If you are arriving here from an upgrade, the binary must already be the new
|
||||
one before you export. An export written by 0.0.1 carries a `.password = ""`
|
||||
line this binary refuses; see
|
||||
[the order trap](upgrade.md#the-order-trap-export-with-the-new-binary-not-the-old-one).
|
||||
|
||||
```sh
|
||||
systemctl stop nxdns
|
||||
nxdns export --out /etc/nxdns/config.zon
|
||||
nxdns check --config /etc/nxdns/config.zon
|
||||
```
|
||||
|
||||
```
|
||||
wrote /etc/nxdns/config.zon
|
||||
checking configuration file /etc/nxdns/config.zon
|
||||
OK upstreams[0] https://cloudflare-dns.com
|
||||
OK: no problems found
|
||||
```
|
||||
|
||||
Then add the drop-in and start:
|
||||
|
||||
```sh
|
||||
mkdir -p /etc/systemd/system/nxdns.service.d
|
||||
cat > /etc/systemd/system/nxdns.service.d/file-mode.conf <<'EOF'
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/local/bin/nxdns run --config=/etc/nxdns/config.zon
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl start nxdns
|
||||
```
|
||||
|
||||
The empty `ExecStart=` is required. Without it systemd appends a second command
|
||||
to the list rather than replacing the first, and the unit tries to run nxdns
|
||||
twice.
|
||||
|
||||
The first start after adoption changes nothing, because the file was rendered
|
||||
from the database it is now governing:
|
||||
|
||||
```
|
||||
reconciled '/etc/nxdns/config.zon': no changes
|
||||
info(nxdns): authority: file (/etc/nxdns/config.zon)
|
||||
info(nxdns): nxdns <version> serving on udp [::]:53 tcp [::]:53 tcp 0.0.0.0:53; 1 upstream(s); blocklist generation 1
|
||||
```
|
||||
|
||||
`authority: file` is the line that confirms the drop-in took. Blocklists,
|
||||
compiled snapshots and client history all survive, and every later start on an
|
||||
unchanged file writes nothing either.
|
||||
|
||||
The file now carries `web.password_hash`, so restrict it the same way step 4
|
||||
does — `chown root:nxdns`, `chmod 0640`. The unit's `ReadOnlyPaths=/etc/nxdns`
|
||||
denies the service write access to that directory, so the process that reads the
|
||||
file cannot modify it.
|
||||
|
||||
### Change the configuration from now on
|
||||
|
||||
Edit the file, validate it, restart:
|
||||
|
||||
```sh
|
||||
$EDITOR /etc/nxdns/config.zon
|
||||
nxdns check --config /etc/nxdns/config.zon
|
||||
systemctl restart nxdns
|
||||
```
|
||||
|
||||
Make `nxdns check --config` the precondition of any Ansible handler that
|
||||
restarts nxdns. A file-mode start reads the file on **every** boot, so a bad
|
||||
push that skips its handler does not fail at deploy time — it detonates at the
|
||||
next power cut. Validating before restarting turns that into a failed deploy at
|
||||
noon.
|
||||
|
||||
The restart prints what it changed:
|
||||
|
||||
```
|
||||
reconciled '/etc/nxdns/config.zon': upstreams +1 ~0 -0; settings +0 ~2 -0;
|
||||
settings keys changed: dns.port web.port
|
||||
```
|
||||
|
||||
### Leave file mode
|
||||
|
||||
Remove the drop-in and restart. The database already holds the last reconciled
|
||||
state, so nothing else is needed and the server comes back serving the same
|
||||
configuration:
|
||||
|
||||
```sh
|
||||
rm /etc/systemd/system/nxdns.service.d/file-mode.conf
|
||||
systemctl daemon-reload
|
||||
systemctl restart nxdns
|
||||
```
|
||||
|
||||
```
|
||||
info(nxdns): authority: database
|
||||
```
|
||||
|
||||
> Verified on this host end to end, against a scratch `--data-dir` and a scratch
|
||||
> configuration path instead of `/var/lib/nxdns` and `/etc/nxdns`, on
|
||||
> unprivileged ports — this machine has neither of those directories, no root,
|
||||
> and no installed unit. Every `nxdns` line above was run and produced the output
|
||||
> shown, with only those paths and the port numbers in the `serving on` line
|
||||
> differing.
|
||||
>
|
||||
> The run: a database-mode instance was started, a blocklist source was added
|
||||
> through the API to make it UI-configured, then `nxdns check` against the live
|
||||
> database printed the uncheckpointed-log FAIL and exited 2 (which is why this
|
||||
> section stops the service first). After the stop, `nxdns export --out` wrote
|
||||
> the file, `nxdns check --config` on it exited 0 with `OK: no problems found`,
|
||||
> and the first file-mode start printed `reconciled '<path>': no changes` and
|
||||
> `authority: file (<path>)`. A second file-mode start printed `no changes`
|
||||
> again and loaded the 3096006-byte compiled blocklist from disk with no
|
||||
> download. Dropping the flag printed `authority: database` and served the same
|
||||
> configuration.
|
||||
>
|
||||
> The `systemctl`, `mkdir`, `cat > …/file-mode.conf` and `rm` lines need root and
|
||||
> an installed unit and were **not** run. What was checked instead:
|
||||
> `systemd-analyze verify` on `deploy/systemd/nxdns.service` with those exact two
|
||||
> `ExecStart` lines appended, which reported only the usual
|
||||
> `Command /usr/local/bin/nxdns is not executable` for the absent binary and
|
||||
> nothing about the override.
|
||||
|
||||
## Raspberry Pi 5
|
||||
|
||||
The Pi 5 is aarch64. Nothing about the procedure changes except which tarball
|
||||
|
||||
Reference in New Issue
Block a user