8.7 KiB
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/checksteps 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, atargethost), 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/nxdnsnor/some/backup.SCRATCHbelow is a scratch directory, and itsdata/was populated beforehand withnxdns 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
exportsetting 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:
targetis 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 crossproducedzig-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 -qexited 0, anddocker compose buildfinished withImage nxdns Built.docker compose up -dwas 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-dirpointing 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.0Against a running server whose database had just been migrated and seeded,
nxdns check --data-dirprinted the uncheckpointed-log line above and exited 2, whilenxdns export --outfollowed bynxdns check --configon the result exited 0 withOK: no problems found. Thedigline 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 importprinted 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.zigemits, 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 export–edit–import 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
nxdnslines, 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/startlines around them need root and an installed service and were not run;$EDITORis yours to run.