docs: unwrap hand-wrapped prose repo-wide
Gates / frontend (push) Successful in 1m2s
Gates / test (push) Successful in 1m38s
Gates / package (push) Successful in 5m5s
Gates / test-aarch64 (push) Successful in 6m30s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 13m30s
Gates / frontend (push) Successful in 1m2s
Gates / test (push) Successful in 1m38s
Gates / package (push) Successful in 5m5s
Gates / test-aarch64 (push) Successful in 6m30s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 13m30s
This commit is contained in:
+48
-184
@@ -4,15 +4,9 @@
|
||||
nxdns <command> [options]
|
||||
```
|
||||
|
||||
Six subcommands: `run`, `check`, `export`, `import`, `version`, `help`. Source
|
||||
of truth: `src/cli.zig`.
|
||||
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.
|
||||
`--allow-delete` is boolean and takes no value at all, so `--allow-delete=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.
|
||||
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. `--allow-delete` is boolean and takes no value at all, so `--allow-delete=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`
|
||||
|
||||
@@ -26,27 +20,21 @@ Serves DNS until SIGINT or SIGTERM.
|
||||
|
||||
### Which authority the invocation selects
|
||||
|
||||
The presence of `--config` picks the authority, and nothing else does. There is
|
||||
no default path, no probe of `/etc/nxdns`, and nothing recorded in the database:
|
||||
a configuration file sitting at `/etc/nxdns/config.zon` that no flag names
|
||||
changes nothing at all.
|
||||
The presence of `--config` picks the authority, and nothing else does. There is no default path, no probe of `/etc/nxdns`, and nothing recorded in the database: a configuration file sitting at `/etc/nxdns/config.zon` that no flag names changes nothing at all.
|
||||
|
||||
| Invocation | Authority | What a start does |
|
||||
| --- | --- | --- |
|
||||
| `nxdns run` | The database | Serves what `config.db` holds. Nothing reads a file. |
|
||||
| `nxdns run --config FILE` | FILE | Reads and validates FILE, reconciles the database onto it, then serves. |
|
||||
|
||||
The first log line after the migrations names the mode, so a journal says which
|
||||
authority was live:
|
||||
The first log line after the migrations names the mode, so a journal says which authority was live:
|
||||
|
||||
```
|
||||
info(nxdns): authority: database
|
||||
info(nxdns): authority: file (/etc/nxdns/config.zon)
|
||||
```
|
||||
|
||||
In file mode the reconcile prints what it changed before that line — per-table
|
||||
inserted (`+`), updated (`~`) and deleted (`-`) counts, the settings keys whose
|
||||
values changed, and any change to whether the admin password is set:
|
||||
In file mode the reconcile prints what it changed before that line — per-table inserted (`+`), updated (`~`) and deleted (`-`) counts, the settings keys whose values changed, and any change to whether the admin password is set:
|
||||
|
||||
```
|
||||
reconciled '/etc/nxdns/config.zon': upstreams +1 ~0 -0; settings +45 ~0 -0;
|
||||
@@ -60,17 +48,11 @@ A start whose file matches the database writes nothing and says so:
|
||||
reconciled '/etc/nxdns/config.zon': no changes
|
||||
```
|
||||
|
||||
Blocklist state is not declarative and survives every reconcile: a source whose
|
||||
URL the file still names keeps its row id, its checksum, its counters and its
|
||||
compiled `<id>.list`, `<id>.wild` and `<id>.allow`, so a restart in file mode
|
||||
downloads nothing. Editing a source's URL is a new identity — a new row, a new id, and a
|
||||
fresh download.
|
||||
Blocklist state is not declarative and survives every reconcile: a source whose URL the file still names keeps its row id, its checksum, its counters and its compiled `<id>.list`, `<id>.wild` and `<id>.allow`, so a restart in file mode downloads nothing. Editing a source's URL is a new identity — a new row, a new id, and a fresh download.
|
||||
|
||||
### Failing to start in file mode
|
||||
|
||||
File mode fails closed. A file that is missing, unreadable, unparseable,
|
||||
oversized or invalid stops the start; nxdns never falls back to the database,
|
||||
because a fallback turns a deploy typo into a silently stale configuration.
|
||||
File mode fails closed. A file that is missing, unreadable, unparseable, oversized or invalid stops the start; nxdns never falls back to the database, because a fallback turns a deploy typo into a silently stale configuration.
|
||||
|
||||
```
|
||||
FAIL /etc/nxdns/config.zon: no such file
|
||||
@@ -78,46 +60,21 @@ nxdns run failed: ManagedConfigUnreadable
|
||||
run `nxdns check` to see the configuration in full
|
||||
```
|
||||
|
||||
That is exit 2, and `check --config` on the same path agrees. Only path-class
|
||||
open failures map that way — the file is not there, or the process may not read
|
||||
it. An open that fails for a reason a retry could clear, such as
|
||||
file-descriptor exhaustion or an I/O error, is exit 1: the box is wrong, not the
|
||||
configuration. See [exit codes](#exit-codes).
|
||||
That is exit 2, and `check --config` on the same path agrees. Only path-class open failures map that way — the file is not there, or the process may not read it. An open that fails for a reason a retry could clear, such as file-descriptor exhaustion or an I/O error, is exit 1: the box is wrong, not the configuration. See [exit codes](#exit-codes).
|
||||
|
||||
The exit-2 agreement covers `run --config` and `check --config`, which read the
|
||||
managed file through one shared helper. It does **not** extend to `import`'s
|
||||
positional argument: a missing file there is `import failed: FileNotFound`, exit
|
||||
1. That is deliberate rather than an oversight — the managed file is a
|
||||
declarative input an operator deploys, so its absence is a fact about the
|
||||
configuration, while `import`'s argument is a path typed at a prompt, and a
|
||||
mistyped path is a failed command rather than a verdict on anything.
|
||||
The exit-2 agreement covers `run --config` and `check --config`, which read the managed file through one shared helper. It does **not** extend to `import`'s positional argument: a missing file there is `import failed: FileNotFound`, exit
|
||||
1. That is deliberate rather than an oversight — the managed file is a declarative input an operator deploys, so its absence is a fact about the configuration, while `import`'s argument is a path typed at a prompt, and a mistyped path is a failed command rather than a verdict on anything.
|
||||
|
||||
Run `nxdns check --config FILE` before restarting anything that deploys a file.
|
||||
It grades every declarative fault `run` would hit — read, parse, size,
|
||||
validation — through the same code, which is what makes it a usable precondition
|
||||
in an Ansible handler.
|
||||
Run `nxdns check --config FILE` before restarting anything that deploys a file. It grades every declarative fault `run` would hit — read, parse, size, validation — through the same code, which is what makes it a usable precondition in an Ansible handler.
|
||||
|
||||
## `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:
|
||||
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.
|
||||
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 |
|
||||
| --- | --- |
|
||||
@@ -126,10 +83,7 @@ first. What it checks, in order:
|
||||
|
||||
### 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.
|
||||
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:
|
||||
|
||||
@@ -141,91 +95,55 @@ The last line is a summary, and it never contradicts the lines above it:
|
||||
|
||||
### Source selection
|
||||
|
||||
The flag decides, exactly as it does for `run`. There is no fallback and no
|
||||
probing of a default path:
|
||||
The flag decides, exactly as it does for `run`. There is no fallback and no probing of a default path:
|
||||
|
||||
- `--config FILE`: grade that file, and never open the database.
|
||||
- No `--config`: grade `<data-dir>/config.db`. It is opened immutable, so
|
||||
`check` writes nothing to it; see
|
||||
[what `check` does not do](#what-check-does-not-do).
|
||||
- No `--config`: grade `<data-dir>/config.db`. It is opened immutable, so `check` writes nothing to it; see [what `check` does not do](#what-check-does-not-do).
|
||||
|
||||
So `nxdns check` and `nxdns check --config FILE` grade what the matching `run`
|
||||
invocation would serve. That is what makes `check` a pre-restart gate rather
|
||||
than an approximation of one.
|
||||
So `nxdns check` and `nxdns check --config FILE` grade what the matching `run` invocation would serve. That is what makes `check` a pre-restart gate rather than an approximation of one.
|
||||
|
||||
A bare `check` on a box with no database says so and names both ways out of the
|
||||
state, exit 2:
|
||||
A bare `check` on a box with no database says so and names both ways out of the state, exit 2:
|
||||
|
||||
```
|
||||
no config database at /var/lib/nxdns/config.db
|
||||
load one with `nxdns import <file>`, or make a file the source of truth with `nxdns run --config <file>`
|
||||
```
|
||||
|
||||
The first line of output otherwise 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.
|
||||
The first line of output otherwise 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.
|
||||
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.
|
||||
|
||||
What `check --config` cannot see is the reconcile itself. It needs no database,
|
||||
so faults that only a write can produce — a disk that is full, a lock held by a
|
||||
restart that started first — are invisible to it. Those are runtime failures,
|
||||
exit 1, and they are not verdicts on the file.
|
||||
What `check --config` cannot see is the reconcile itself. It needs no database, so faults that only a write can produce — a disk that is full, a lock held by a restart that started first — are invisible to it. Those are runtime failures, exit 1, and they are not verdicts on the file.
|
||||
|
||||
### 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.
|
||||
`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.
|
||||
- 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:
|
||||
- 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.
|
||||
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.
|
||||
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.
|
||||
`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.
|
||||
|
||||
The output is the same in both authority modes — nothing marks a file as
|
||||
exported from a file-mode box. That is what lets `export` be the adoption tool:
|
||||
the file you check is the file you deploy.
|
||||
The output is the same in both authority modes — nothing marks a file as exported from a file-mode box. That is what lets `export` be the adoption tool: the file you check is the file you deploy.
|
||||
|
||||
`web.password` is always written as `null` and `web.password_hash` carries the
|
||||
stored value, so an export re-imports without anyone knowing the password. A
|
||||
`null` password is not the same statement as an empty one: see
|
||||
[`web.password` and `web.password_hash`](configuration.md#password-and-hash).
|
||||
`web.password` is always written as `null` and `web.password_hash` carries the stored value, so an export re-imports without anyone knowing the password. A `null` password is not the same statement as an empty one: see [`web.password` and `web.password_hash`](configuration.md#password-and-hash).
|
||||
|
||||
| Flag | Meaning |
|
||||
| --- | --- |
|
||||
@@ -236,10 +154,7 @@ See [back up and restore](../how-to/back-up-and-restore.md).
|
||||
|
||||
## `import FILE`
|
||||
|
||||
Converges the database onto FILE in one transaction — the same reconcile a
|
||||
file-mode `run` performs, done once from the command line. Prints every
|
||||
validation problem; a failed import leaves the database untouched. Creates the
|
||||
data directory at mode 0700 if it is missing.
|
||||
Converges the database onto FILE in one transaction — the same reconcile a file-mode `run` performs, done once from the command line. Prints every validation problem; a failed import leaves the database untouched. Creates the data directory at mode 0700 if it is missing.
|
||||
|
||||
`FILE` is positional and may appear before or after the flags.
|
||||
|
||||
@@ -248,29 +163,20 @@ data directory at mode 0700 if it is missing.
|
||||
| `--data-dir DIR` | Data directory holding `config.db` (created if missing). |
|
||||
| `--allow-delete` | Apply a file whose diff deletes rows. |
|
||||
|
||||
**`import` is a stop-first operation.** It rewrites configuration underneath a
|
||||
process that read it at startup, and a running server notices only some of it:
|
||||
filtering picks up the imported rows at the next reload, while upstreams,
|
||||
listeners and settings stay at their boot values until a restart.
|
||||
**`import` is a stop-first operation.** It rewrites configuration underneath a process that read it at startup, and a running server notices only some of it: filtering picks up the imported rows at the next reload, while upstreams, listeners and settings stay at their boot values until a restart.
|
||||
|
||||
### The delete gate
|
||||
|
||||
Rows the database holds and FILE does not name are deleted. That is the point of
|
||||
a declarative apply, and it is also how a mistaken `nxdns import ./wrong.zon`
|
||||
empties a configured server, so it takes a flag:
|
||||
Rows the database holds and FILE does not name are deleted. That is the point of a declarative apply, and it is also how a mistaken `nxdns import ./wrong.zon` empties a configured server, so it takes a flag:
|
||||
|
||||
```
|
||||
FAIL import: this file would delete rows the database holds (upstreams 1); re-run with --allow-delete to apply it
|
||||
import failed: DestructiveImport
|
||||
```
|
||||
|
||||
That is exit 2, and the transaction rolls back. The message names every table
|
||||
with a non-zero delete count, so you can tell an intended pruning from a wrong
|
||||
file before applying anything.
|
||||
That is exit 2, and the transaction rolls back. The message names every table with a non-zero delete count, so you can tell an intended pruning from a wrong file before applying anything.
|
||||
|
||||
An import that only adds rows, or only edits them, needs no flag. "Edit" here
|
||||
means a change to a row nxdns can still recognise as the same row. Each table
|
||||
has one column, or one tuple, that establishes identity:
|
||||
An import that only adds rows, or only edits them, needs no flag. "Edit" here means a change to a row nxdns can still recognise as the same row. Each table has one column, or one tuple, that establishes identity:
|
||||
|
||||
| Table | Identity |
|
||||
| --- | --- |
|
||||
@@ -282,43 +188,25 @@ has one column, or one tuple, that establishes identity:
|
||||
| `local_records` | `(name, rtype, value)` |
|
||||
| `rules` | `(group, pattern, kind, action)` |
|
||||
|
||||
Change anything else on a row — a source's name, a group's `safe_search`, a
|
||||
client's group — and it is an edit, applied without a flag. Change the identity
|
||||
itself, such as renaming a group or correcting a typo in an upstream URL, and
|
||||
the engine sees a row that vanished and a row that appeared: that needs
|
||||
`--allow-delete`.
|
||||
Change anything else on a row — a source's name, a group's `safe_search`, a client's group — and it is an edit, applied without a flag. Change the identity itself, such as renaming a group or correcting a typo in an upstream URL, and the engine sees a row that vanished and a row that appeared: that needs `--allow-delete`.
|
||||
|
||||
### What survives an import
|
||||
|
||||
Runtime state is not declarative and is preserved by identity, not by luck. A
|
||||
blocklist source whose URL is unchanged keeps its row id, its checksum, its
|
||||
counters and its compiled files, so an import costs no downloads. Clients the
|
||||
DNS path materialised from traffic are kept whole; naming one in the file
|
||||
promotes that row in place, keeping its first-seen and last-seen. Observed
|
||||
clients whose group the file no longer declares are moved to the `default`
|
||||
group rather than deleted with it, and none of that ever trips the delete gate.
|
||||
Runtime state is not declarative and is preserved by identity, not by luck. A blocklist source whose URL is unchanged keeps its row id, its checksum, its counters and its compiled files, so an import costs no downloads. Clients the DNS path materialised from traffic are kept whole; naming one in the file promotes that row in place, keeping its first-seen and last-seen. Observed clients whose group the file no longer declares are moved to the `default` group rather than deleted with it, and none of that ever trips the delete gate.
|
||||
|
||||
### `import` against a file-mode box
|
||||
|
||||
It behaves like any other import. Nothing in the database records that a file
|
||||
governs it — authority lives in the invocation — so `import` neither detects nor
|
||||
refuses that case. The next restart's reconcile converges the database back to
|
||||
the file and its summary reports what it corrected. A source the import deleted
|
||||
comes back with a new row id, which means a fresh download of the whole list.
|
||||
It behaves like any other import. Nothing in the database records that a file governs it — authority lives in the invocation — so `import` neither detects nor refuses that case. The next restart's reconcile converges the database back to the file and its summary reports what it corrected. A source the import deleted comes back with a new row id, which means a fresh download of the whole list.
|
||||
|
||||
If a restart and an import race for the write lock, one of them simply wins: both
|
||||
take `BEGIN IMMEDIATE` under a 5-second busy timeout, so the outcome is an
|
||||
ordering, never a corrupted database.
|
||||
If a restart and an import race for the write lock, one of them simply wins: both take `BEGIN IMMEDIATE` under a 5-second busy timeout, so the outcome is an ordering, never a corrupted database.
|
||||
|
||||
## `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.
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -329,50 +217,26 @@ the same. A usage error prints the same text to stderr and exits 64.
|
||||
| 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`, `BadCertificate` and
|
||||
`ManagedConfigUnreadable`. In practice that covers a 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, `password` and `password_hash` set together, and a
|
||||
`--config` path that is absent or unreadable.
|
||||
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`, `BadCertificate` and `ManagedConfigUnreadable`. In practice that covers a 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, `password` and `password_hash` set together, and a `--config` path that is absent or unreadable.
|
||||
|
||||
The last of those is the one deliberate seam. A file nxdns cannot open is a
|
||||
configuration fault only when the *path* is the problem — the file is missing,
|
||||
permissions deny it, a path component is not a directory. Every other open
|
||||
failure, such as running out of file descriptors, is exit 1. The distinction
|
||||
earns its keep under the shipped systemd unit, which stops the service on exit 2
|
||||
rather than restarting it: a transient box fault graded as a configuration fault
|
||||
would take the resolver down until someone noticed.
|
||||
The last of those is the one deliberate seam. A file nxdns cannot open is a configuration fault only when the *path* is the problem — the file is missing, permissions deny it, a path component is not a directory. Every other open failure, such as running out of file descriptors, is exit 1. The distinction earns its keep under the shipped systemd unit, which stops the service on exit 2 rather than restarting it: a transient box fault graded as a configuration fault would take the resolver down until someone noticed.
|
||||
|
||||
When `run` exits 2 it points at the diagnosis on stderr, whichever authority the
|
||||
fault came from:
|
||||
When `run` exits 2 it points at the diagnosis on stderr, whichever authority the fault came from:
|
||||
|
||||
```
|
||||
run `nxdns check` to see the configuration in full
|
||||
```
|
||||
|
||||
On a box with no configuration at all, `run` and `check` add the line that names
|
||||
both ways to get one:
|
||||
On a box with no configuration at all, `run` and `check` add the line that names both ways to get one:
|
||||
|
||||
```
|
||||
load one with `nxdns import <file>`, or make a file the source of truth with `nxdns run --config <file>`
|
||||
```
|
||||
|
||||
`check` exits 2 for those faults and also when a probed upstream failed, when a
|
||||
named configuration file is missing or unreadable, and when the database cannot
|
||||
be read, is absent, or is not at this binary's schema version. Warnings never
|
||||
contribute.
|
||||
`check` exits 2 for those faults and also when a probed upstream failed, when a named configuration file is missing or unreadable, and when the database cannot be read, is absent, or is not at this binary's schema version. Warnings never contribute.
|
||||
|
||||
`import` exits 2 for those faults and for `DestructiveImport`. That last one is
|
||||
deliberately not a configuration fault — it reports what applying the file would
|
||||
delete, rather than anything wrong with its content — and `import` decides it
|
||||
for itself; the answer to it is `--allow-delete`, not an edit.
|
||||
`import` exits 2 for those faults and for `DestructiveImport`. That last one is deliberately not a configuration fault — it reports what applying the file would delete, rather than anything wrong with its content — and `import` decides it for itself; the answer to it is `--allow-delete`, not an edit.
|
||||
|
||||
`OutOfMemory` is exit 1 even when problems were recorded, because the report is
|
||||
then incomplete. Every other error is 1.
|
||||
`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).
|
||||
|
||||
Reference in New Issue
Block a user