Gates / frontend (push) Successful in 1m3s
Gates / test (push) Successful in 1m39s
Gates / test-aarch64 (push) Successful in 6m28s
Gates / container (push) Successful in 18s
Release / guard (push) Successful in 1m30s
Gates / test-aarch64 (push) Successful in 5m45s
Gates / package (push) Successful in 24s
Release / gates (push) Successful in 7m54s
Gates / package (push) Successful in 5m34s
CI / gates (push) Successful in 14m5s
Gates / frontend (push) Successful in 58s
Gates / test (push) Successful in 1m29s
Gates / container (push) Successful in 10s
Release / publish (push) Successful in 9m12s
195 lines
11 KiB
Markdown
195 lines
11 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to nxdns are recorded here. The format follows
|
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
|
Sections are written by hand. Nothing here is generated from commit messages:
|
|
the point of the file is to say what changed for an operator, which a commit
|
|
subject rarely does.
|
|
|
|
## [Unreleased]
|
|
|
|
## [0.0.3] - 2026-08-15
|
|
|
|
Devices name themselves: the clients table asks the router over reverse DNS
|
|
instead of waiting for the operator to type every name. The CI container gate
|
|
also moved from workflow shell into a compiled, tested tool, which fixed a
|
|
latent temp-directory bug shared with the release tool.
|
|
|
|
### Added
|
|
|
|
- **Client names learned over reverse DNS.** A client row that carries no
|
|
hand-typed name gets one from the network: each tracker flush pass takes up
|
|
to 16 unnamed rows, builds each address's reverse name, matches it against
|
|
the declared `forward_zones`, and on a match sends one PTR query to that
|
|
zone's resolver, storing the answer as a *learned* name. This requires a
|
|
conditional forward zone covering the LAN's reverse space — for example
|
|
`168.192.in-addr.arpa` pointed at the router; without one, nothing is sent
|
|
anywhere. A hand-typed name always wins, learned names never appear in
|
|
`nxdns export` and are never set by `nxdns import`, and each row refreshes
|
|
once a day (an hour after a failure), so a rename can show stale for up to
|
|
24 hours. The API's `Client` object gains a `learned_name` field and the
|
|
clients page shows it.
|
|
|
|
### Changed
|
|
|
|
- The container CI gate — image build, image-contents assertion against the
|
|
packaged artifacts, and the startup/shutdown smoke test — moved from
|
|
workflow shell into `tools/container_check.zig`, compiled and unit-tested by
|
|
`zig build test` and runnable on a laptop against a local docker daemon.
|
|
The health probe now runs under a real 60-second deadline (the shell loop's
|
|
"30 seconds" could stretch past three minutes), and the gate's docker
|
|
objects carry an ownership label so anything a dead runner leaks is
|
|
discoverable. The version in CI is parsed from `build.zig.zon` through the
|
|
zon grammar, once, instead of by two copies of a `sed` regex.
|
|
|
|
### Fixed
|
|
|
|
- The release tool's temporary-directory claim was not exclusive: the
|
|
"create" it relied on succeeds on a directory that already exists, so a
|
|
stale or concurrent directory could be silently adopted, written into, and
|
|
deleted on exit. Both the release tool and the new container gate now claim
|
|
their directories exclusively and retry on collision.
|
|
|
|
## [0.0.2] - 2026-08-14
|
|
|
|
Configuration can now be a file that every boot converges to, filtering gains
|
|
regex rules and honors blocklist exception lines, and two refresh bugs that
|
|
silently kept stale state are fixed. Note the three breaking changes below if
|
|
you script against `nxdns import` or run with `--config`.
|
|
|
|
### Added
|
|
|
|
- **Declarative configuration for IaC.** `nxdns run --config=<file>` makes the
|
|
file the sole source of configuration: every boot converges the database to
|
|
it in one transaction, preserving blocklist downloads, compiled lists and
|
|
client history, so an unchanged file costs zero downloads and zero writes.
|
|
Bare `nxdns run` keeps the database (and the web UI) in charge, exactly as
|
|
before. In file mode the web UI is read-only for configuration and says so;
|
|
runtime actions (pause, blocklist refresh, certificate reload) stay live.
|
|
`GET /api/settings` reports which authority governs the process.
|
|
- `nxdns import` now refuses a file whose application would delete
|
|
configuration rows, names the tables and counts, and applies it only with
|
|
the new `--allow-delete` flag. Additive and edit-in-place imports need no
|
|
flag.
|
|
- **Regex rules.** Rules gain a third kind, `regex`, beside `exact` and
|
|
`wildcard`, for per-group allow and block patterns such as `^ad[0-9]+-`. The
|
|
engine is homegrown and linear-time by construction, so no pattern can make
|
|
matching blow up; backreferences and lookaround do not exist, and a bad
|
|
pattern is refused at insert time with the limit it hit. Matches appear in
|
|
`/api/lookup` and the query log as `rule_allow_regex` / `rule_block_regex`.
|
|
Regex still comes only from you: regex lines in downloaded lists stay
|
|
counted and skipped.
|
|
- **Blocklist exception lines are honored.** An Adblock-Plus `@@||name^` line
|
|
in a downloaded list now lifts that name — and its subdomains — out of what
|
|
the attached lists block. Exceptions sit below every rule you wrote: a
|
|
downloaded list can reopen only a hole another downloaded list dug, never
|
|
override an operator decision. Each source reports how many it carried.
|
|
- **Browser-only lines are counted where you can see them.** Every source now
|
|
reports how many of its lines nxdns skipped as syntax with no DNS meaning —
|
|
cosmetic filters, `$`-modifier rules — beside the existing skipped-regex
|
|
count. Both blocklist tables show the number and the UI explains the
|
|
difference: a list whose skipped-unsupported count dwarfs its domain count
|
|
is written for browser extensions, and its DNS or hosts variant will block
|
|
more. Previously such a list compiled to almost nothing and looked clean.
|
|
|
|
### Changed
|
|
|
|
- **Breaking: `nxdns run --config <file>` changed meaning.** It used to seed
|
|
the database once and then ignore the file; it now makes the file the
|
|
authority on every boot, which deletes any configuration the file does not
|
|
declare — including edits made through the web UI since the seed. Before
|
|
upgrading a unit that carries `--config`: either drop the flag to keep the
|
|
database in charge, or adopt file mode with the sequence in the upgrade
|
|
guide. Order matters there: export the file with the NEW binary (stopped).
|
|
- **Breaking: 0.0.1 exports are refused by this version.** A 0.0.1
|
|
`nxdns export` writes both `.password = ""` and the stored
|
|
`.password_hash`, and this version refuses a file that carries both. This
|
|
bites any old export — an adoption file or a configuration backup fed to
|
|
`nxdns import` alike. Fix an existing export by deleting its
|
|
`.password = ""` line (keep the `.password_hash` line). Take fresh backups
|
|
with the new binary.
|
|
- **Breaking: the offline password-change recipe changed.** Setting
|
|
`.password = "new"` together with `.password_hash = ""` is now refused
|
|
(empty `password_hash` is an explicit "disable authentication", and the two
|
|
fields cannot both be present). To change the password in the file: set
|
|
`.password` and delete the `.password_hash` line entirely.
|
|
- `nxdns import --force` is renamed `--allow-delete`.
|
|
- A fresh install no longer seeds from `/etc/nxdns/config.zon` by presence.
|
|
Use `nxdns import` once, or run in file mode with `--config`.
|
|
- The admin UI's internals moved to TypeScript 7 and replaced Tailwind with
|
|
StyleX and React Aria. The visible change is small: selects are real
|
|
widgets with working keyboard focus; everything else renders as before.
|
|
- The `config.db` schema is a single baseline definition again; numbered
|
|
migration steps start accumulating at v0.1.
|
|
|
|
### Fixed
|
|
|
|
- **A list switching a name between its exact and wildcard forms never took
|
|
effect.** The compiled-list checksum hashed the exact and wildcard bodies as
|
|
one unseparated byte stream, so a list carrying `a.example` and the same
|
|
list carrying `*.a.example` produced the same digest, and the refresh kept
|
|
the old compiled files. The checksum now separates the bodies. Every source
|
|
recompiles once on its first refresh after the upgrade; no re-download of
|
|
unchanged content is forced beyond the refresh's normal fetch.
|
|
- **A refresh could store stale skip counts.** When a refresh found the list
|
|
content unchanged, it wrote the previously stored skip counters back to the
|
|
database while showing the fresh ones in the UI, and the next restart
|
|
reverted the numbers to the stale copy. All counters now persist from the
|
|
fresh compile.
|
|
- An Adblock-Plus entry with embedded whitespace
|
|
(`||good.example bad.example^`) compiled into an entry no query could ever
|
|
match. Such lines are now counted as unsupported instead.
|
|
|
|
## [0.0.1] - 2026-08-09
|
|
|
|
First release. Everything below is new.
|
|
|
|
### Added
|
|
|
|
- **Forwarding DNS server.** UDP and TCP listeners with a wire-format parser and
|
|
encoder written against RFC 1035 and EDNS(0), a bounded worker model, per-client
|
|
rate limiting and a `pause` control that stops filtering without stopping
|
|
resolution.
|
|
- **Encrypted upstreams.** DNS-over-HTTPS and DNS-over-TLS clients over a pool
|
|
that tracks per-upstream health and fails over, with SNI and certificate
|
|
verification driven by a per-upstream TLS name.
|
|
- **DoH and DoT endpoints.** nxdns also answers as an encrypted resolver, with a
|
|
certificate store that reloads on disk changes and through the API, so renewals
|
|
do not need a restart.
|
|
- **Blocklist filtering.** Subscriptions in hosts, plain-domain and
|
|
Adblock-Plus-style formats, compiled into a compact matcher; per-group allow
|
|
and block rules with wildcards; safe-search enforcement.
|
|
- **Per-client policy groups.** Clients are identified by address and assigned to
|
|
groups, so the filtering a device gets depends on which device it is.
|
|
- **Local DNS.** Local A/AAAA/CNAME/PTR records and conditional forwarding of
|
|
internal zones to another resolver.
|
|
- **Cache.** A bounded in-memory cache that respects upstream TTLs and expires
|
|
entries rather than serving them stale.
|
|
- **Query log.** Queries land in SQLite under a retention policy in both rows and
|
|
days, with disk-full self-protection that degrades instead of corrupting, and a
|
|
live SSE stream of the same events.
|
|
- **Web UI and REST API.** A React single-page admin UI embedded in the binary,
|
|
a REST API with a served OpenAPI document, session authentication, API rate
|
|
limiting and Prometheus-style `/metrics`.
|
|
- **Configuration.** A ZON configuration file seeds the database on first boot;
|
|
after that the database is the truth, and `nxdns export` / `nxdns import` move
|
|
configuration in and out. `nxdns check` validates a file without starting.
|
|
- **CLI.** `run`, `check`, `export`, `import`, `version` and `help`.
|
|
- **Packaging.** A hardened systemd unit with a sysusers fragment, and a
|
|
`FROM scratch` container image holding the binary, a CA bundle and the licence
|
|
files, assembled by a builder stage pinned to `alpine:3.22` by digest. Nothing
|
|
from Alpine ships in the published image except that CA bundle.
|
|
- **Releases.** Tags publish five assets — static musl tarballs for
|
|
`x86_64-linux-musl` and `aarch64-linux-musl`, `IMAGE-DIGEST.txt` naming the
|
|
multi-architecture container image by digest, `SHA256SUMS.txt` over those
|
|
three, and `SHA256SUMS.txt.asc`, a detached signature over the checksum file.
|
|
`zig build dist` and `zig build verify-dist` produce and check the same
|
|
artifacts on a laptop.
|
|
- **Licensing.** EUPL-1.2, with a `THIRD-PARTY-NOTICES` file in every tarball and
|
|
image assembled from a reviewed inventory of what the artifacts contain.
|
|
- **Documentation.** A Diátaxis split — tutorial, how-to, reference, explanation —
|
|
with drift guards that fail the build when the reference pages fall behind the
|
|
code.
|