7.3 KiB
Files and directories
Every path nxdns reads or writes, and the mode it is created with. Source of
truth: src/cli.zig (DataDir), src/storage/querylog_schema.zig (the
preserved query-log databases), src/filter/manager.zig (the blocklist
snapshots), src/platform/logging.zig (the log file).
The data directory
Default /var/lib/nxdns, overridable with --data-dir DIR. nxdns run and
nxdns import create it and its parents at mode 0700 when it is missing;
nxdns check and nxdns export do not create it. export fails if it is not
there. check opens it only on the branch that resolved to the database, so an
absent data directory is not in itself a failure: an explicit --config FILE
never looks at the directory, and without one check falls back to the default
configuration file, or prints "nothing to check" and exits 2 when neither source
exists.
No subcommand opens the directory read-only. run, import, export and a
check that resolved to the database all go through the same
DataDir.openConfigDb, which opens config.db read/write, chmods it to 0600,
enables WAL — creating config.db-wal and config.db-shm — and then runs any
pending schema migrations. So nxdns export and nxdns check write to the data
directory, and on a database one schema version behind they migrate it. export
additionally creates an empty config.db if the directory exists without one;
check reaches the database branch only when config.db is already there.
| Path | What it is | Mode |
|---|---|---|
config.db |
The configuration database — the single source of truth, including web.password_hash. |
0600 |
config.db-wal, config.db-shm |
SQLite write-ahead log and shared-memory index for config.db. Created when WAL is enabled, inheriting the main file's permissions. |
0600 |
querylog.db |
The query log: every domain every client asked for. Expendable — if it is missing or unusable it is recreated empty. | 0600 |
querylog.db-wal, querylog.db-shm |
WAL sidecars for querylog.db. |
0600 |
querylog.db.corrupt-<unix-seconds> |
A querylog.db that could not be used, moved aside before an empty one was created in its place. Kept, never overwritten. |
Whatever the renamed file had — no chmod reaches it |
querylog.db.corrupt-<unix-seconds>-<n> |
The same, when the plain name is taken — <n> counts from 1 and rises until the name is free. Two recreates within one second is the case it exists for. |
The same |
blocklists/ |
Compiled blocklist snapshots, one subdirectory of the data directory. | 0700 |
blocklists/<id>.list |
Exact domains for blocklist source <id>, one per line, behind a header. |
0600 |
blocklists/<id>.wild |
Wildcard entries for the same source. | 0600 |
blocklists/<id>.raw.tmp, <id>.list.tmp, <id>.wild.tmp |
Transient refresh state: the downloaded body and the two compile outputs before they are published by rename. | 0600 |
<id> is the blocklist_sources row id. A .list or .wild file whose id is
no longer a blocklist_sources row is deleted by the orphan sweep; files of a
live source are left alone whatever their state, and the sweep matches only the
two published suffixes, so a .tmp file left by an interrupted refresh is not
swept — the next refresh of that source overwrites it.
A querylog.db is moved aside when it is missing nothing but usability:
SQLite reports it corrupt or not a database, PRAGMA quick_check does not
answer ok, or its user_version fingerprint does not match the schema. Only
the main file is renamed — its -wal and -shm are deleted, because a stale
WAL would be replayed into the fresh database. A missing querylog.db is
created without any aside file. The rename happens inside
querylog_schema.open, before the 0600 chmod, and that chmod names
querylog.db and its two sidecars only — so an aside file keeps the mode the
file had at rename time, which for a querylog.db nxdns itself created is 0600
and for one an operator put there is whatever they left it at. Nothing prunes
the aside files; they accumulate until an operator removes them, and each one
holds the same browsing history the live query log holds.
The 0600 modes are not cosmetic. config.db holds the argon2id password hash
and querylog.db holds the browsing history of every client on the LAN, so both
are as sensitive as each other, and a WAL file holds the same rows as the
database it belongs to. SQLite creates the main database at 0644 & ~umask;
nxdns chmods it to 0600 before enabling WAL, so the sidecars inherit 0600 rather
than being created world-readable.
The configuration file
Default /etc/nxdns/config.zon, overridable with --config FILE. nxdns reads
it and never writes it: it seeds an unconfigured database once and is ignored
afterwards. nxdns does not create the file or its directory.
nxdns export --out FILE writes a ZON file at mode 0600 through a temporary
file and a rename. That file carries web.password_hash, so treat exports as
secrets. Without --out the export goes to stdout, where permissions are the
redirect's business.
The log file
Only when logging.output = .file. The path is logging.file_path, default
/var/log/nxdns/nxdns.log, and validation requires it to be absolute.
nxdns does not create the log directory. It must exist and be writable by
the user the service runs as before the process starts; under systemd the unit's
LogsDirectory=nxdns does that.
Rotation triggers at logging.max_size_mb MiB and keeps logging.max_files
files in total, the live one included, so the highest generation on disk is
logging.max_files - 1. With the default 5 that is nxdns.log plus nxdns.log.1
through nxdns.log.4. Rotation deletes the highest generation, renames each
remaining one up by one, then renames the live file to .1. A max_files of 1
or 0 deletes the live file instead of renaming it. The directory holding the log
file is also sampled by the disk monitor.
| Path | What it is | Mode |
|---|---|---|
logging.file_path (default /var/log/nxdns/nxdns.log) |
The live process log. Opened for append; created when it does not exist. | 0666 & ~umask — nxdns never chmods it |
<file_path>.1 … <file_path>.<max_files - 1> |
Rotated generations, newest first. | Inherited from the live file they were renamed from |
The log file is the one path here nxdns does not set a mode on. config.db and
querylog.db are chmodded to 0600 whatever the umask; the log file is left at
whatever the umask gives it. Under the shipped unit that is 0600, because
deploy/systemd/nxdns.service sets UMask=0077. Run from a shell with the
usual umask 022 it is 0644, and LogsDirectory=nxdns leaves the directory at
systemd's default 0755, so a log written there is readable by any local user.
With logging.output = .stderr or .syslog nxdns writes to stderr and creates
no file; under systemd the journal captures it.
Certificate and key files
doh_server.cert_path / key_path and dot_server.cert_path / key_path,
conventionally under /etc/nxdns. nxdns reads them, never writes or creates
them. Both must be readable by the user nxdns runs as; the key should be
readable by its owner only, which nxdns check warns about when it is not. A
watcher polls both files and swaps a renewed pair in without a restart. See
enable DoH and DoT.