215 lines
9.7 KiB
Markdown
215 lines
9.7 KiB
Markdown
# CLI reference
|
|
|
|
```
|
|
nxdns <command> [options]
|
|
```
|
|
|
|
Six subcommands: `run`, `check`, `export`, `import`, `version`, `help`. Source
|
|
of truth: `src/cli.zig`.
|
|
|
|
Every flag takes both spellings, `--flag value` and `--flag=value`. An attached
|
|
value that is empty (`--config=`) is a missing value, not an empty path.
|
|
`--force` is boolean and takes no value at all, so `--force=1` is not a spelling
|
|
of any flag this program has. A flag is rejected by the subcommand that has no
|
|
use for it: `--web-dev` outside `run` is an unknown flag, not a no-op.
|
|
|
|
## `run`
|
|
|
|
Serves DNS until SIGINT or SIGTERM.
|
|
|
|
| Flag | Meaning |
|
|
| --- | --- |
|
|
| `--data-dir DIR` | Data directory (default `/var/lib/nxdns`). Created at mode 0700 if missing. |
|
|
| `--config FILE` | Seed configuration file (default `/etc/nxdns/config.zon`). Read only when the database has never been configured. |
|
|
| `--web-dev DIR` | Serve the web interface from DIR instead of the embedded assets, with no cache headers. Development only. |
|
|
|
|
A seed file that is unparseable, oversized or invalid prints its diagnostics and
|
|
exits 2 — the same code `check` and `import` give for the same file. See
|
|
[exit codes](#exit-codes).
|
|
|
|
## `check`
|
|
|
|
Validates the configuration and probes the upstreams. Exit 0 when it found no
|
|
failures, 2 when it found one. It always reports every problem, not just the
|
|
first. What it checks, in order:
|
|
|
|
1. Which source to check (see [source selection](#source-selection)).
|
|
2. Full validation — the same rules `import` enforces.
|
|
3. For each enabled DoH/DoT listener: both PEM files are read and the key is
|
|
tested against the certificate, through the same `CertStore.init` the
|
|
listeners boot with. A file that is missing, unreadable, too large or
|
|
unparseable, and a key that does not belong to the certificate, are all FAIL.
|
|
The key's permissions are a separate finding: WARN when any group or other
|
|
bit is set, which does not change the exit code.
|
|
4. A live probe: one real A query for `example.com` through every enabled
|
|
upstream, driving the same pool and failover machinery the server uses, with
|
|
the same two deadlines: `upstream.attempt_timeout_ms` bounds one try against
|
|
one upstream, `upstream.total_timeout_ms` the whole probe. A FAIL line names
|
|
the upstream and the concrete cause recorded in its health. This probe leaves
|
|
the machine, so `check` needs network access to pass. It runs from the
|
|
command line but not from unit tests.
|
|
|
|
| Flag | Meaning |
|
|
| --- | --- |
|
|
| `--data-dir DIR` | Data directory to look for `config.db` in. |
|
|
| `--config FILE` | Check this file instead of the database. |
|
|
|
|
### Failures and warnings
|
|
|
|
Every finding carries a severity. `FAIL` is a problem that sets exit 2. `WARN`
|
|
is legal configuration that is almost certainly not what was meant — a blocklist
|
|
source no group links to, a TLS key readable beyond its owner — and never
|
|
changes an exit code, because the service starts either way.
|
|
|
|
The last line is a summary, and it never contradicts the lines above it:
|
|
|
|
| What was found | Last line | Exit |
|
|
| --- | --- | --- |
|
|
| Nothing | `OK: no problems found` | 0 |
|
|
| Warnings only | `OK: no failures found, 2 warnings` — `1 warning` in the singular | 0 |
|
|
| At least one failure | No summary line; the FAIL lines are the report | 2 |
|
|
|
|
### Source selection
|
|
|
|
- `--config FILE` given explicitly: check that file, nothing else.
|
|
- Otherwise, if `<data-dir>/config.db` exists: check the database — the right
|
|
default, since the database is the truth on a configured server. It is opened
|
|
immutable, so `check` writes nothing to it; see
|
|
[what `check` does not do](#what-check-does-not-do).
|
|
- Otherwise, if the default config file path exists: check it.
|
|
- Otherwise: "nothing to check", exit 2.
|
|
|
|
The first line of output always names which source was checked. A file larger
|
|
than 4 MiB fails with `larger than 4194304 bytes`; a ZON syntax error is
|
|
reported with its line and column.
|
|
|
|
A named file that is missing or unreadable is a finding like any other, not an
|
|
I/O failure that escapes the run: `FAIL <path>: no such file` or
|
|
`FAIL <path>: not readable`, exit 2. That is the same code the implicit path
|
|
gives when there is nothing to check, so naming the file does not change what a
|
|
missing file costs.
|
|
|
|
### What `check` does not do
|
|
|
|
`check` never writes to `config.db`. It opens the file immutable, which means
|
|
SQLite refuses every statement that would write and builds no write-ahead log,
|
|
so no `config.db-wal` and no `config.db-shm` appear beside it. It does not chmod
|
|
the file and it does not migrate the schema.
|
|
|
|
Two consequences are worth knowing before you read a FAIL line as damage:
|
|
|
|
- A database behind this binary's schema is reported, not upgraded. Start the
|
|
service to migrate it.
|
|
|
|
```
|
|
FAIL <db>: schema version <n>, this nxdns expects <m>; `nxdns run` migrates it, `check` will not
|
|
```
|
|
|
|
- A database with an unapplied write-ahead log cannot be graded without writing,
|
|
because the newest settings are in the log and the main file holds older ones.
|
|
`check` says so rather than reading the stale values:
|
|
|
|
```
|
|
FAIL <db>: uncheckpointed changes are waiting in <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.
|
|
```
|
|
|
|
A running nxdns is the usual holder of that log, so this is a common answer
|
|
when checking a live server rather than a sign of damage. It depends on what
|
|
is in the log, not on whether a server is up: a configuration write that has
|
|
not been checkpointed puts bytes there, and a server that has only been
|
|
answering queries leaves `config.db-wal` empty and reads normally. Check an
|
|
`export` with `--config`, or stop the service first.
|
|
|
|
## `export`
|
|
|
|
Writes the configuration as ZON to stdout, or atomically at mode 0600 to
|
|
`--out FILE`. `--out` paths are relative to the shell's working directory, not
|
|
to the data directory. The output is canonical: every default emitted,
|
|
deterministic ordering, no timestamps, so `export` → `import` → `export` is
|
|
byte-identical.
|
|
|
|
`export` does not create the data directory; it fails if the directory is not
|
|
there. A directory that exists without a `config.db` is not that case: the
|
|
database is opened with create semantics, so an empty `config.db` is created and
|
|
migrated, and the export is of a default configuration.
|
|
|
|
| Flag | Meaning |
|
|
| --- | --- |
|
|
| `--data-dir DIR` | Data directory holding `config.db`. |
|
|
| `--out FILE` | Write to FILE instead of stdout. |
|
|
|
|
See [back up and restore](../how-to/back-up-and-restore.md).
|
|
|
|
## `import FILE`
|
|
|
|
Validates FILE and replaces the whole configuration with it in one transaction.
|
|
Prints every validation problem; a failed import leaves the database untouched.
|
|
Refuses a database that already holds configuration unless `--force` is given
|
|
(`DatabaseNotEmpty`, exit 2). The check measures what an operator set, not what
|
|
the network did: client rows the DNS path materialised from traffic never
|
|
trigger the refusal on their own. Creates the data directory at mode 0700 if it
|
|
is missing.
|
|
|
|
Client history survives the replacement. An address the database already knew
|
|
keeps its first-seen and last-seen even when FILE names it; only an address it
|
|
has never seen takes the import's clock. A client FILE leaves out is removed,
|
|
history included.
|
|
|
|
`FILE` is positional and may appear before or after the flags.
|
|
|
|
| Flag | Meaning |
|
|
| --- | --- |
|
|
| `--data-dir DIR` | Data directory holding `config.db` (created if missing). |
|
|
| `--force` | Replace a database that already holds configuration. |
|
|
|
|
## `version`
|
|
|
|
Prints two lines: the nxdns version with the git commit, then the Zig version
|
|
the binary was built with. Takes no flags and no arguments.
|
|
|
|
## `help`
|
|
|
|
Prints the usage text to stdout and exits 0. `nxdns --help` and `nxdns -h` do
|
|
the same. A usage error prints the same text to stderr and exits 64.
|
|
|
|
## Exit codes
|
|
|
|
| Code | Meaning |
|
|
| --- | --- |
|
|
| 0 | Success. |
|
|
| 1 | Runtime failure — I/O, database, out of memory. A partial diagnostic report caused by an allocation failure is a runtime failure, not a verdict on the configuration. |
|
|
| 2 | A configuration problem the operator can fix, or a `check` that found one. |
|
|
| 64 | Usage error — unknown command or flag, a flag without its value, a missing or extra argument. |
|
|
|
|
Code 2 means the same thing from every subcommand. `src/config/faults.zig`
|
|
holds the one list of errors that mean "the configuration the operator supplied
|
|
is wrong", and `run`, `check` and `import` all ask it, so a rejected file exits
|
|
2 whichever command read it. The list is every error the validator raises, plus
|
|
`ParseZon`, `ConfigTooLarge`, `NoUsableUpstreams` and `BadCertificate`. In
|
|
practice that covers a seed file with a syntax error, one larger than 4 MiB, one
|
|
with no `default` group (`MissingDefaultGroup`), one with no enabled upstream
|
|
(`NoUpstreams`), a bad bind address, a bad rate limit, an unusable certificate,
|
|
and `password` and `password_hash` set together.
|
|
|
|
When `run` exits 2 it points at the diagnosis on stderr, whether the fault came
|
|
from the seed file or from the database it loaded:
|
|
|
|
```
|
|
run `nxdns check` to see the configuration in full
|
|
```
|
|
|
|
`check` exits 2 for those faults and also when a probed upstream failed, when a
|
|
named configuration file is missing or unreadable, when the database cannot be
|
|
read or is not at this binary's schema version, and when there was nothing to
|
|
check. Warnings never contribute.
|
|
|
|
`import` exits 2 for those faults and for `DatabaseNotEmpty`. That last one is
|
|
deliberately not a configuration fault — it reports the state of the database
|
|
rather than the content of a file — and `import` decides it for itself; the
|
|
answer to it is `--force`, not an edit.
|
|
|
|
`OutOfMemory` is exit 1 even when problems were recorded, because the report is
|
|
then incomplete. Every other error is 1.
|
|
|
|
Where an exit code sends you next: [troubleshoot](../how-to/troubleshoot.md).
|