milestone 20: declarative configuration for iac
This commit is contained in:
+179
-27
@@ -21,6 +21,105 @@ Upgrading a build you made yourself is the last section of this page.
|
||||
> shapes and the verification commands are covered by
|
||||
> [Verify a release](verify-a-release.md), which says what was probed and how.
|
||||
|
||||
## Breaking change: `run --config` now means file authority
|
||||
|
||||
**Read this before upgrading if anything on your box passes `--config` to
|
||||
`nxdns run`** — a systemd drop-in, a wrapper script, or a `command:` in a
|
||||
compose file.
|
||||
|
||||
`run --config FILE` used to mean *seed once*: the file was read only while the
|
||||
database was still empty, and ignored on every start after that. It now means
|
||||
*the file is the configuration*: every start reconciles the database onto it.
|
||||
|
||||
For a box that was seeded once and then configured through the admin interface,
|
||||
the first start after the upgrade converges the database back to that old seed
|
||||
file. **Every change made through the UI since seeding is deleted.**
|
||||
|
||||
There are two ways out, and you pick before you restart:
|
||||
|
||||
- **Keep the database.** Drop the flag. `nxdns run` with no `--config` serves
|
||||
the database exactly as it did before, and nothing reads a file. This is the
|
||||
right answer if the UI is how you change things.
|
||||
- **Adopt file mode cleanly.** Install the new binary, stop the service, export
|
||||
the current database over the file path, check it, then start with the flag.
|
||||
The first reconcile is then a no-op, because the file was rendered from the
|
||||
database it governs. **Install the new binary first** — see the order trap
|
||||
below. The full procedure is
|
||||
[Adopt file mode](install-with-systemd.md#adopt-file-mode-on-a-box-that-is-already-running).
|
||||
|
||||
`nxdns check --config FILE` is unchanged: it graded that file before and it
|
||||
grades that file now.
|
||||
|
||||
### The order trap: export with the new binary, not the old one
|
||||
|
||||
Take the export **after** you have replaced the binary, with the service
|
||||
stopped. Exporting first — the instinctive order, and the one step 1 of this
|
||||
page tells you to take for a backup — produces a file the new binary refuses.
|
||||
|
||||
A 0.0.1 `nxdns export` writes both fields:
|
||||
|
||||
```zon
|
||||
.password = "",
|
||||
.password_hash = "$argon2id$v=19$m=19456,t=2,p=1$…",
|
||||
```
|
||||
|
||||
An empty `password_hash` used to mean "unset". It now means "disable
|
||||
authentication", so it is a *present* value — and a file that carries both
|
||||
fields states two different things about the password and is refused:
|
||||
|
||||
```
|
||||
FAIL web.password: password and password_hash are both set; ambiguity in a security setting is refused
|
||||
FAIL web.password: password is set to the empty string; omit the field to keep the stored password, or set password_hash = "" to disable authentication
|
||||
nxdns run failed: PasswordAndHashBothSet
|
||||
```
|
||||
|
||||
The old `nxdns check` passes that file, because the old binary agreed with the
|
||||
old rule. So the failure lands at the first start after the upgrade, with the
|
||||
resolver stopped and the unit refusing to retry it
|
||||
(`RestartPreventExitStatus=2 64`). A new `nxdns export` writes
|
||||
`.password = null` instead and has no such problem.
|
||||
|
||||
**If you already have an old export you want to adopt**, you do not need to
|
||||
redo it. Delete the empty-password line and the file is valid:
|
||||
|
||||
```sh
|
||||
sed -i '/^ \.password = "",$/d' /etc/nxdns/config.zon
|
||||
nxdns check --config /etc/nxdns/config.zon
|
||||
```
|
||||
|
||||
Keep the `.password_hash` line — that is the password, and deleting it as well
|
||||
would leave the file saying nothing about authentication, which means "keep
|
||||
whatever is stored" rather than anything you would notice.
|
||||
|
||||
The same trap has nothing to do with file mode as such: it is any 0.0.1 export
|
||||
fed to the new binary, so it also applies to a restore through `nxdns import`.
|
||||
Backups taken with 0.0.1 need that one line removed before they will load.
|
||||
|
||||
> Verified on this host, with one substitution stated: the repository has no
|
||||
> 0.0.1 binary to hand, so the old export was **simulated** by taking a current
|
||||
> `nxdns export` and rewriting `.password = null` to `.password = ""`, which is
|
||||
> the one byte-level difference between the two formats. Against that file,
|
||||
> `nxdns check --config` printed both FAIL lines above and exited 2, and
|
||||
> `nxdns run --config` printed them and failed `PasswordAndHashBothSet`. After
|
||||
> the `sed` above, `nxdns check --config` exited 0 with `OK: no problems found`
|
||||
> and `nxdns import` of the same file exited 0, keeping the hash. The claim
|
||||
> about what 0.0.1's `export` emitted is read from that release's source —
|
||||
> `git show v0.0.1:src/config/export.zig` line 71 is `cfg.web.password = "";` —
|
||||
> not from running that binary.
|
||||
|
||||
A database-mode install that never passed `--config` needs nothing. Under
|
||||
Docker, a fresh database-mode install must either take the new compose file or
|
||||
run `import` once — see
|
||||
[Database mode in Docker](install-with-docker.md#database-mode-in-docker-instead).
|
||||
|
||||
Two smaller renames in the same release: `nxdns import --force` is now
|
||||
`--allow-delete`, and it is required only when the file's diff would delete
|
||||
rows rather than whenever the database is non-empty. `nxdns check` no longer
|
||||
falls back to a default file path when there is no database; it reports the
|
||||
absent database and names the two ways to get one.
|
||||
|
||||
There is no schema migration in this change.
|
||||
|
||||
## 1. Take an export first
|
||||
|
||||
There is no downgrade path, so the export is what you fall back to:
|
||||
@@ -33,6 +132,13 @@ nxdns export --out /some/backup/nxdns-config.zon
|
||||
command relies on the default `--data-dir /var/lib/nxdns` that a systemd
|
||||
install has.
|
||||
|
||||
This export is a fallback, not a file to deploy. If you are adopting file mode,
|
||||
take a *second* export after the binary swap and use that one — an export
|
||||
written by 0.0.1 carries a `.password = ""` line the new binary refuses, as
|
||||
[the order trap](#the-order-trap-export-with-the-new-binary-not-the-old-one)
|
||||
explains. The same line has to come out of this backup before the new binary
|
||||
will import it.
|
||||
|
||||
> Verified on this host with both paths substituted, since it has neither
|
||||
> `/var/lib/nxdns` nor `/some/backup`. `SCRATCH` below is a scratch directory,
|
||||
> and its `data/` was populated beforehand with `nxdns import`:
|
||||
@@ -131,9 +237,11 @@ NXDNS_VERSION=$VERSION docker compose -f deploy/docker/compose.yaml pull
|
||||
NXDNS_VERSION=$VERSION docker compose -f deploy/docker/compose.yaml up -d
|
||||
```
|
||||
|
||||
Compose recreates the container against the same `nxdns-data` volume. The seed
|
||||
file in `etc-nxdns` is not read again; the database in the volume is the
|
||||
configuration.
|
||||
Compose recreates the container against the same `nxdns-data` volume. What
|
||||
happens to the file in `etc-nxdns` depends on the `command:` in your compose
|
||||
file: with the shipped `run --config=/etc/nxdns/config.zon` the file is the
|
||||
configuration and the restart reconciles onto it; without it, the database in
|
||||
the volume is the configuration and the file is read by nothing.
|
||||
|
||||
Set `NXDNS_VERSION` on both lines, or export it. Without it the compose file
|
||||
falls back to `:latest`, and `pull` and `up` could then land on different
|
||||
@@ -184,11 +292,12 @@ OK: no problems found
|
||||
> zig 0.16.0
|
||||
> ```
|
||||
>
|
||||
> Against a running server whose database had just been migrated and seeded,
|
||||
> `nxdns check --data-dir` printed the uncheckpointed-log line above and exited
|
||||
> 2, while `nxdns export --out` followed by `nxdns check --config` on the result
|
||||
> exited 0 with `OK: no problems found`. The `dig` line was not run in this
|
||||
> round: nothing is listening on 127.0.0.1:53 here, and port 53 needs root.
|
||||
> Against a running server whose database had just taken a configuration write
|
||||
> through the API, `nxdns check --data-dir` printed the uncheckpointed-log line
|
||||
> above and exited 2, while `nxdns export --out` followed by
|
||||
> `nxdns check --config` on the result exited 0 with `OK: no problems found`.
|
||||
> Both were re-run for this revision. The `dig` line was not run in this round:
|
||||
> nothing is listening on 127.0.0.1:53 here, and port 53 needs root.
|
||||
|
||||
## What happens to the database
|
||||
|
||||
@@ -235,46 +344,89 @@ nxdns run failed: SchemaTooNew
|
||||
That run exits 1. Recovering means importing the export you took in step 1 into
|
||||
a fresh data directory with the older binary.
|
||||
|
||||
### Rolling back from file mode
|
||||
|
||||
Putting an older binary back needs no unit edit. The old binary accepts
|
||||
`run --config` — it just reads it as the old seed-once flag — and against a
|
||||
database that already holds configuration it ignores the file entirely and
|
||||
serves the last state the new binary reconciled. So the service comes back up
|
||||
on the configuration it was running.
|
||||
|
||||
The consequence is worth stating plainly: **file edits stop applying.** The old
|
||||
binary will not re-read the file, so every change made to `config.zon` after the
|
||||
rollback does nothing at all, silently, until the newer binary is back. If you
|
||||
have to stay on the old binary, use `nxdns import` to apply file changes, or drop
|
||||
the flag so the invocation matches what the binary actually does.
|
||||
|
||||
The schema note above still governs: a database stamped by a newer binary
|
||||
refuses to open, whatever mode either binary runs in.
|
||||
|
||||
## Changing settings, not the binary
|
||||
|
||||
An upgrade never re-reads `/etc/nxdns/config.zon`. After the first successful
|
||||
seed the file is ignored, and the start log says so:
|
||||
How you change a setting depends on which authority the service runs under.
|
||||
`nxdns run` in `ExecStart` means the database; `nxdns run --config FILE` means
|
||||
the file. The start log names it either way:
|
||||
|
||||
```
|
||||
info(config_bootstrap): configuration file ignored; the database is already configured
|
||||
info(nxdns): authority: database
|
||||
info(nxdns): authority: file (/etc/nxdns/config.zon)
|
||||
```
|
||||
|
||||
Change settings through the admin interface, through the API, or with an
|
||||
export–edit–import cycle against a stopped server:
|
||||
**In file mode**, edit the file, validate it, restart. The admin interface will
|
||||
refuse the change with a 403 naming the file, so there is nothing to get wrong:
|
||||
|
||||
```sh
|
||||
$EDITOR /etc/nxdns/config.zon
|
||||
nxdns check --config /etc/nxdns/config.zon
|
||||
systemctl restart nxdns
|
||||
```
|
||||
|
||||
**In database mode**, change settings through the admin interface, through the
|
||||
API, or with an export–edit–import cycle against a stopped server:
|
||||
|
||||
```sh
|
||||
nxdns export --out config-backup.zon
|
||||
$EDITOR config-backup.zon
|
||||
systemctl stop nxdns
|
||||
nxdns import config-backup.zon --force
|
||||
nxdns import config-backup.zon
|
||||
systemctl start nxdns
|
||||
```
|
||||
|
||||
`--force` is required here. A plain `import` into a database that already holds
|
||||
configuration fails with `import failed: DatabaseNotEmpty` and exits 2, so it
|
||||
cannot clobber a configured server by accident. What counts is what an operator
|
||||
set: client rows the DNS path materialised from traffic never trigger the
|
||||
refusal on their own.
|
||||
`import` needs no flag to add rows or to edit them. It needs `--allow-delete`
|
||||
only when applying the file would delete rows the database holds — including the
|
||||
case where you renamed something, since changing a group's name or an upstream's
|
||||
URL is a delete and an insert to the engine, not an edit. The refusal names the
|
||||
tables and rolls back:
|
||||
|
||||
> Verified on this host for the two `nxdns` lines, against a populated scratch
|
||||
> data directory:
|
||||
```
|
||||
FAIL import: this file would delete rows the database holds (upstreams 1); re-run with --allow-delete to apply it
|
||||
import failed: DestructiveImport
|
||||
```
|
||||
|
||||
Stop the server first either way. `import` rewrites configuration underneath a
|
||||
process that read it at startup, and a running server picks up only some of it.
|
||||
|
||||
> Verified on this host against a populated scratch data directory, with
|
||||
> `--data-dir` pointing at it — that path is the only difference from the blocks
|
||||
> above:
|
||||
>
|
||||
> ```
|
||||
> $ nxdns import $SCRATCH/nxdns-config.zon --data-dir $SCRATCH/data
|
||||
> import failed: DatabaseNotEmpty
|
||||
> $ nxdns import $SCRATCH/etc/config.zon --data-dir $SCRATCH/dbmode
|
||||
> imported /…/config.zon
|
||||
> (exit 0)
|
||||
> $ nxdns import $SCRATCH/etc/smaller.zon --data-dir $SCRATCH/dbmode
|
||||
> FAIL import: this file would delete rows the database holds (upstreams 1); re-run with --allow-delete to apply it
|
||||
> import failed: DestructiveImport
|
||||
> (exit 2)
|
||||
> $ nxdns import $SCRATCH/nxdns-config.zon --data-dir $SCRATCH/data --force
|
||||
> imported /…/scratchpad/nxdns-config.zon
|
||||
> $ nxdns import $SCRATCH/etc/smaller.zon --data-dir $SCRATCH/dbmode --allow-delete
|
||||
> imported /…/smaller.zon
|
||||
> (exit 0)
|
||||
> ```
|
||||
>
|
||||
> The `systemctl stop`/`start` lines around them need root and an installed
|
||||
> service and were not run; `$EDITOR` is yours to run.
|
||||
> The first of those three is the additive case that needs no flag; the second
|
||||
> file replaced the upstream, which is an identity change and therefore a
|
||||
> delete. The `systemctl stop`/`start` lines need root and an installed service
|
||||
> and were not run; `$EDITOR` is yours to run.
|
||||
|
||||
## Upgrading to a build of your own
|
||||
|
||||
|
||||
Reference in New Issue
Block a user