db-mode config changes apply live in-process
Gates / frontend (push) Successful in 1m43s
Gates / test (push) Successful in 2m14s
Gates / test-aarch64 (push) Successful in 8m3s
Gates / package (push) Successful in 5m42s
Gates / container (push) Successful in 54s
CI / gates (push) Successful in 50m24s

settings and upstream writes now follow a prepare, commit, publish, retire
contract: candidates are built and validated before the database transaction,
published as infallible pointer swaps, and old generations retire after their
readers drain. per-query policy values snapshot once per query; upstream pool,
cache, rate limiter, sessions, api limiter, log sink, blocklist scheduler and
the query-log queue each gained one named live operation. restart_required
shrinks from every scalar key to the bind keys and web.enabled; the admin ui
drops its restart notices for everything else. file mode is unchanged.
This commit is contained in:
2026-08-24 00:04:28 +02:00
parent f7f4c8be09
commit ce143d1d87
47 changed files with 7698 additions and 926 deletions
+30 -19
View File
@@ -26,9 +26,11 @@ info:
Prometheus scrape can never be throttled; `/api/queries/live` is
exempt because one long-lived stream is bounded by
`web.sse_max_connections_per_ip` instead.
- Mutations to groups, blocklists, rules, local records, forward zones,
clients and client prefixes take effect live. Upstreams and
`/api/settings` are restart-required.
- Mutations take effect live, including upstreams and `/api/settings`:
the owner of every changed setting is told in-process by the write
that changes it. The exceptions are the keys that create or destroy a
socket — the listener addresses and ports, and `web.enabled` — which
`/api/settings` reports in its `restart_required` list.
- 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
@@ -1544,7 +1546,7 @@ paths:
$ref: "#/components/responses/Unavailable"
post:
summary: Add an upstream
description: Restart-required; the running pool is not changed.
description: Applies live; the resolver pool is rebuilt in-process.
requestBody:
required: true
content:
@@ -1553,7 +1555,7 @@ paths:
$ref: "#/components/schemas/UpstreamInput"
responses:
"201":
description: Created; takes effect on restart.
description: Created; live at once.
content:
application/json:
schema:
@@ -1599,7 +1601,7 @@ paths:
$ref: "#/components/responses/Unavailable"
put:
summary: Update an upstream
description: Restart-required; the running pool is not changed.
description: Applies live; the resolver pool is rebuilt in-process.
requestBody:
required: true
content:
@@ -1608,7 +1610,7 @@ paths:
$ref: "#/components/schemas/UpstreamInput"
responses:
"200":
description: Updated; takes effect on restart.
description: Updated; live at once.
content:
application/json:
schema:
@@ -1636,7 +1638,7 @@ paths:
description: The last enabled upstream cannot be removed (409).
responses:
"204":
description: Deleted; takes effect on restart.
description: Deleted; live at once.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
@@ -1701,10 +1703,10 @@ paths:
summary: Read the scalar settings
description: |
Every stored settings key, plus the derived `web.auth_enabled`.
`restart_required` lists every key, because all scalar settings are
restart-required this milestone; live behavior comes from the
resource endpoints and `/api/pause`. Passwords and hashes are never
serialized.
`restart_required` lists only the keys that create or destroy a
socket — the DNS, web, DoH and DoT bind addresses, ports and
enabled flags. Every other setting is applied in-process by the
write that changes it. Passwords and hashes are never serialized.
responses:
"200":
description: The settings and the restart-required key list.
@@ -2756,8 +2758,12 @@ components:
enabled: { type: boolean }
tls_name: { type: string }
restart_required:
description: |
Always false: an upstream write rebuilds the resolver pool
in-process. The field stays in the shape so every mutation
response answers the same question the same way.
type: boolean
enum: [true]
enum: [false]
Pause:
type: object
@@ -2922,9 +2928,12 @@ components:
type: array
items: { type: string }
description: |
Every `section.field` key that needs a restart to take effect
currently all of them. Whether a restart is *owed* right now is
process state, and lives on `/api/config/status`.
The `section.field` keys that need a restart to take effect: the
DNS, web, DoH and DoT bind addresses, ports and enabled flags,
and nothing else. Every key absent from this list is applied
in-process by the write that changes it. Whether a restart is
*owed* right now is process state, and lives on
`/api/config/status`.
ConfigStatus:
type: object
@@ -2963,9 +2972,11 @@ components:
type: boolean
description: |
True once this process has committed a configuration change that
takes effect only at the next start — an upstream write or a
settings key. Nothing clears it but process exit, and it is
never persisted, so a false after a restart is the truth.
takes effect only at the next start: a DNS, web, DoH or DoT bind
address, port or enabled flag. No other write raises it —
everything else, upstreams included, is applied in-process.
Nothing clears it but process exit, and it is never persisted,
so a false after a restart is the truth.
SettingsPatch:
type: object