milestone 20: declarative configuration for iac

This commit is contained in:
2026-08-11 23:31:40 +02:00
parent 2f29121e27
commit d76afc147a
74 changed files with 6722 additions and 1949 deletions
+99 -1
View File
@@ -29,6 +29,15 @@ info:
- Mutations to groups, blocklists, rules, local records, forward zones,
clients and client prefixes take effect live. Upstreams and
`/api/settings` are restart-required.
- nxdns runs under one of two configuration authorities. Started with
`--config=<file>`, that file is the sole declarative source, and every
operation that writes configuration answers 403 with the same error
envelope, naming the file. Operations that change runtime state —
`/api/pause`, `POST /api/blocklists/update`, `/api/certs/reload`, the
login and the logout — stay live, as does `DELETE /api/clients/{id}`
for a client the file does not declare. `GET /api/settings` reports
the live authority, so a client reads the mode rather than
discovering it from a rejection.
servers:
- url: /
@@ -391,6 +400,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -444,6 +455,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -466,6 +479,8 @@ paths:
description: Deleted; applied live.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -519,6 +534,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -575,6 +592,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -655,6 +674,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -674,6 +695,8 @@ paths:
description: Deleted; applied live.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -728,6 +751,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -780,6 +805,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -799,6 +826,8 @@ paths:
description: Deleted; applied live.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -853,6 +882,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -905,6 +936,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -924,6 +957,8 @@ paths:
description: Deleted; applied live.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -978,6 +1013,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -1030,6 +1067,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -1049,6 +1088,8 @@ paths:
description: Deleted; applied live.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -1133,6 +1174,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -1222,6 +1265,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -1277,6 +1322,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"409":
$ref: "#/components/responses/Conflict"
"413":
@@ -1330,6 +1377,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -1350,6 +1399,8 @@ paths:
description: Deleted; takes effect on restart.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"404":
$ref: "#/components/responses/NotFound"
"409":
@@ -1454,6 +1505,8 @@ paths:
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/ManagedByFile"
"413":
$ref: "#/components/responses/BodyTooLarge"
"429":
@@ -1526,6 +1579,16 @@ components:
application/json:
schema:
$ref: "#/components/schemas/Error"
ManagedByFile:
description: |
nxdns is running under file authority and this operation writes
configuration. The message names the file. Authentication is checked
first, so an unauthenticated request to a protected route still
answers 401 rather than disclosing that the route exists.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
NotFound:
description: No row has this id.
content:
@@ -2193,7 +2256,7 @@ components:
SettingsEnvelope:
type: object
required: [settings, restart_required]
required: [settings, restart_required, authority]
properties:
settings:
$ref: "#/components/schemas/Settings"
@@ -2203,6 +2266,41 @@ components:
description: |
Every `section.field` key that needs a restart to take effect —
currently all of them.
authority:
$ref: "#/components/schemas/Authority"
Authority:
type: object
description: |
Which source governs this process's configuration. This is how a
client learns that configuration is read-only; it never has to probe
a write route for a 403. The block rides this authenticated endpoint
because `path` is a filesystem path, and never appears on the open
`/api/version` or `/api/health`.
required: [mode, path, reconciled_at]
properties:
mode:
type: string
enum: [database, managed_file]
description: |
`database` when nxdns runs without `--config`; `managed_file`
when it runs with it, in which case every configuration write
answers 403.
path:
type: string
nullable: true
description: The managed file, or null in `database` mode.
reconciled_at:
type: integer
nullable: true
description: |
When this process loaded the managed file, in epoch seconds, and
null in `database` mode. It means exactly that: a file whose
mtime is newer has not been loaded by the running process. It
cannot answer whether the file matches what the server serves —
a stepped clock or a preserved mtime defeats the comparison
either way, and `nxdns import` can move the database without
moving either timestamp.
SettingsPatch:
type: object