Files
nxdns/docs/how-to/upgrade.md
T

8.7 KiB
Raw Blame History

Upgrade nxdns

Replaces a running nxdns with a newer build without losing its configuration. The database is migrated in place on the first start of the new binary.

Verification: the export, the migration behaviour and the version/check steps below were run on the machine that wrote this page, against a populated scratch data directory and with an explicit --data-dir, since that machine has no /var/lib/nxdns. The commands were not run exactly as printed — the page uses the defaults and placeholders a real operator would have (/var/lib/nxdns, /some/backup, a target host), and every block where the substitution matters, or which was not run at all, carries its own note. Nothing here was verified except where a note says so.

1. Take an export first

There is no downgrade path, so the export is what you fall back to:

nxdns export --out /some/backup/nxdns-config.zon

/some/backup is a stand-in for a directory you keep backups in, and the command relies on the default --data-dir /var/lib/nxdns that a systemd install has.

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:

$ nxdns export --data-dir $SCRATCH/data --out $SCRATCH/nxdns-config.zon
wrote /…/scratchpad/nxdns-config.zon
$ stat -c '%a %n' $SCRATCH/nxdns-config.zon
600 /…/scratchpad/nxdns-config.zon
$ grep password $SCRATCH/nxdns-config.zon
        .password = "",
        .password_hash = "$argon2id$v=19$m=19456,t=2,p=1$mCNEo…$i3DMz…",

The shell umask was 022, so the 0600 is export setting it, not the umask. Only the two paths differ from the command above.

The file is written atomically at mode 0600 and carries web.password_hash, so treat it as a secret. See Back up and restore for the full backup story. The query log is deliberately not part of it.

2. Build the new binary

(cd web && npm ci && npm run build)
zig build cross -Dweb-dist=web/dist -Doptimize=ReleaseSafe

Rebuild web/dist before the binary on every upgrade. The admin interface is embedded at build time, and an old bundle against a new API is a broken settings page.

3. Replace the binary

systemd

Step 2 leaves the new binary under zig-out/cross, one per target. Copy the one that matches the host — aarch64-linux-musl for a Raspberry Pi 5:

scp zig-out/cross/x86_64-linux-musl/nxdns target:/tmp/nxdns

Not run on this host: target is a placeholder for the machine running nxdns, and this host has no such second machine to copy to. What exists here is the local half — zig build cross produced zig-out/cross/x86_64-linux-musl/nxdns.

Then, as root on the target:

install -m 0755 /tmp/nxdns /usr/local/bin/nxdns
systemctl restart nxdns
journalctl -u nxdns -f

Not run on this host: all three lines need root and an installed service. The migration half of what a restart does is checkable without either, and was — see the note under What happens to the database. The swap of an older binary for a newer one on a live service was not reproduced here.

Docker

cd deploy/docker
docker compose build
docker compose 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.

Verified on this host for the first two lines: docker compose config -q exited 0, and docker compose build finished with Image nxdns Built. docker compose up -d was not run — it publishes host ports 53/udp, 53/tcp and 8080, which this workstation is not a deploy target for.

4. Confirm the upgrade

nxdns version
nxdns export --out /tmp/after-upgrade.zon
nxdns check --config /tmp/after-upgrade.zon
dig @127.0.0.1 example.com A +short

The restart in step 3 is what migrated the database, so by now the schema is current and the service is answering. Confirming with nxdns check alone would not work here, and the reason is worth knowing: check opens config.db immutable so it can never write to it, and the migration you just performed is sitting in config.db-wal waiting to be checkpointed. Rather than read around the log and grade older settings, check reports it:

FAIL /var/lib/nxdns/config.db: uncheckpointed changes are waiting in /var/lib/nxdns/config.db-wal, and reading without writing would answer from the older settings in the main file; `nxdns run` applies them. A running nxdns normally holds this log, which is the usual reason to see this line.

export opens the database read/write and does see the log, so exporting and then checking the export validates what is actually in force:

checking configuration file /tmp/after-upgrade.zon
OK upstreams[0] https://cloudflare-dns.com
OK: no problems found

Verified on this host for the first three commands, with --data-dir pointing at a scratch data directory instead of /var/lib/nxdns — that path is the only difference from the blocks above:

$ nxdns version
nxdns 0.1.0-dev (unknown)
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.

What happens to the database

Migrations run at startup, and also before export and import, so whichever of those you run first performs the upgrade. nxdns check is the exception: it opens the database immutable and never migrates, so on a database still one version behind it reports the mismatch and exits 2 rather than fixing it:

FAIL /var/lib/nxdns/config.db: schema version 0, this nxdns expects 2; `nxdns run` migrates it, `check` will not

That line was reproduced here against a database stamped at version 0; the path and the version numbers are what vary.

A fresh database is created at the current schema version; an older one is stepped up to it. The log line names both versions:

info(migrations): config.db migrated from schema version 0 to 2

Verified on this host: that exact line is what nxdns import printed when it created the scratch database used throughout this page. An empty data directory is schema version 0, which is why a first run reports a migration rather than nothing. The step from a populated older schema to 2 was not reproduced here — it needs a database written by an older binary, which this host does not have.

Rolling back is the case that has no answer. A database stamped by a newer binary refuses to open, so an older binary against an upgraded data directory fails to start:

warning(migrations): config.db is at schema version 99; this nxdns binary supports 2
nxdns run failed: SchemaTooNew

Not reproduced on this host: the same missing ingredient as above, a database at a schema version this binary does not support. The two lines are the messages src/storage/migrations.zig emits, not a run captured here.

That run exits 1. Recovering means importing the export you took in step 1 into a fresh data directory with the older binary.

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:

info(config_bootstrap): configuration file ignored; the database is already configured

Change settings through the admin interface, through the API, or with an exporteditimport cycle against a stopped server:

nxdns export --out config-backup.zon
$EDITOR config-backup.zon
systemctl stop nxdns
nxdns import config-backup.zon --force
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.

Verified on this host for the two nxdns lines, against a populated scratch data directory:

$ nxdns import $SCRATCH/nxdns-config.zon --data-dir $SCRATCH/data
import failed: DatabaseNotEmpty
(exit 2)
$ nxdns import $SCRATCH/nxdns-config.zon --data-dir $SCRATCH/data --force
imported /…/scratchpad/nxdns-config.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.