158 lines
7.0 KiB
Markdown
158 lines
7.0 KiB
Markdown
# nxdns
|
|
|
|
nxdns is a DNS sinkhole for your LAN. It blocks the names you do not want, and
|
|
forwards the rest over an encrypted connection.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
devices["Your devices"] -- "DNS query" --> nxdns["nxdns"]
|
|
nxdns -- "answer" --> devices
|
|
nxdns -- "allowed" --> upstream["Upstream resolvers<br/>DoH / DoT"]
|
|
upstream -- "answer" --> nxdns
|
|
nxdns -- "blocked" --> sink["0.0.0.0 / NXDOMAIN"]
|
|
```
|
|
|
|
One static Zig binary. SQLite holds the state and the query log, which the web
|
|
UI, the REST API and `/metrics` read.
|
|
|
|
## Features
|
|
|
|
- Blocklist filtering: subscribe to hosts/domain lists, plus your own allow
|
|
and block rules with wildcard support (`*.example.com`)
|
|
- Two configuration modes: a database the web UI edits, or a ZON file you keep
|
|
in git and converge onto at every start
|
|
- Per-client policy groups: different filtering for the kids' tablet and
|
|
your workstation
|
|
- Local DNS records and conditional forwarding for internal zones
|
|
- Encrypted upstreams: DNS-over-HTTPS and DNS-over-TLS with failover
|
|
- Built-in DoH and DoT server endpoints, with certificate hot-reload
|
|
- Bounded in-memory DNS cache with TTL-respecting expiry
|
|
- Query log with retention limits, live-streamed over SSE
|
|
- Web UI (embedded in the binary) and a REST API with a served OpenAPI spec
|
|
- Prometheus-style `/metrics`, per-client rate limiting, disk-full
|
|
self-protection
|
|
|
|
## Install
|
|
|
|
**No release exists yet.** This repository has no tags, nothing has been
|
|
published to <https://git.mial.net/mokhtar/nxdns/releases>, and no container
|
|
image has been pushed. Every release URL on this page and in the how-to guides
|
|
is a 404 today, and `docker pull` finds nothing. Until the first tag ships,
|
|
building from source is the only way to get nxdns.
|
|
|
|
What a tag will publish, once one exists: five assets — two static musl
|
|
tarballs (`nxdns-<version>-x86_64-linux-musl.tar.gz`,
|
|
`nxdns-<version>-aarch64-linux-musl.tar.gz`), `IMAGE-DIGEST.txt` naming the
|
|
multi-architecture container image by digest, `SHA256SUMS.txt` covering those
|
|
three files, and `SHA256SUMS.txt.asc`, a detached OpenPGP signature over the
|
|
checksum file. Verify what you downloaded before you run it:
|
|
[docs/how-to/verify-a-release.md](docs/how-to/verify-a-release.md), which also
|
|
says what that signature does and does not prove.
|
|
|
|
## Quickstart (docker compose)
|
|
|
|
Write a minimal configuration and start the published image. This is what the
|
|
first release will make possible; it does not work today, because there is no
|
|
image in the registry to pull:
|
|
|
|
```sh
|
|
cd deploy/docker
|
|
mkdir -p etc-nxdns
|
|
cat > etc-nxdns/config.zon <<'EOF'
|
|
.{
|
|
.groups = .{ .{ .name = "default" } },
|
|
.upstreams = .{ .{ .url = "https://cloudflare-dns.com/dns-query" } },
|
|
.web = .{ .password = "choose-a-real-password" },
|
|
}
|
|
EOF
|
|
NXDNS_VERSION=<version> docker compose up -d
|
|
```
|
|
|
|
The compose file defaults to `:latest`; pin a version for anything you intend
|
|
to keep running. To run it before a release exists, build the image yourself and
|
|
name it — `NXDNS_IMAGE=nxdns docker compose up -d` — as
|
|
[docs/how-to/install-with-docker.md](docs/how-to/install-with-docker.md)
|
|
describes. DNS is on port 53, the web UI on <http://localhost:8080>.
|
|
|
|
The compose file runs `nxdns run --config=/etc/nxdns/config.zon`, which makes
|
|
that file the configuration: every start reconciles the database onto it, and
|
|
the UI refuses configuration edits. Edit the file and restart to change
|
|
anything. Drop the `command:` line to run bare `nxdns run` instead, where the
|
|
database is the configuration and changes go through the UI, the API, or
|
|
`nxdns export` / `nxdns import` — the packaged systemd unit does that. Which
|
|
mode is live is printed at every start (`authority: database` /
|
|
`authority: file (<path>)`); see
|
|
[docs/explanation/configuration-model.md](docs/explanation/configuration-model.md).
|
|
|
|
Full install instructions, including the systemd path and the Pi 5 recipe, are
|
|
in
|
|
[docs/how-to/install-with-systemd.md](docs/how-to/install-with-systemd.md) and
|
|
[docs/how-to/install-with-docker.md](docs/how-to/install-with-docker.md).
|
|
|
|
## Building from source
|
|
|
|
Requires [Zig 0.16.0](https://ziglang.org/download/) and Node.js 24 (for
|
|
the web UI). C dependencies (SQLite, mbedTLS) are vendored and built by
|
|
`zig build`.
|
|
|
|
```sh
|
|
(cd web && npm ci && npm run build) # web UI -> web/dist
|
|
zig build -Dweb-dist=web/dist # native binary -> zig-out/bin/nxdns
|
|
zig build test --summary all # unit tests
|
|
```
|
|
|
|
The release artifacts come out of the same build graph, so the whole release
|
|
build runs on a laptop exactly as it runs on the CI runner:
|
|
|
|
```sh
|
|
(cd web && npm ci && npm run build) # required: dist refuses the placeholder
|
|
VERSION=$(sed -n 's/^[[:space:]]*\.version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' build.zig.zon)
|
|
zig build dist -Dversion-string="$VERSION" -Dgit-commit=$(git rev-parse HEAD) \
|
|
-Dweb-dist=web/dist -Doptimize=ReleaseSafe # tarballs -> zig-out/dist/
|
|
zig build verify-dist -Dversion-string="$VERSION" -Dgit-commit=$(git rev-parse HEAD) \
|
|
-Dweb-dist=web/dist -Doptimize=ReleaseSafe # the release checks
|
|
```
|
|
|
|
The version comes from `build.zig.zon` because `verify-dist` asserts the two
|
|
agree; a tag sets both.
|
|
|
|
That is not a claim that your tarball will hash the same as a published one.
|
|
Nothing in this project measures whether two builds of the same commit on two
|
|
different machines land on the same bytes, so no document here describes the
|
|
build as reproducible. The gate that would settle it is a recorded deferral —
|
|
`specs/milestone-14.md` ruling 12 — and
|
|
[docs/how-to/verify-a-release.md](docs/how-to/verify-a-release.md) explains what
|
|
a matching or differing hash is worth in the meantime.
|
|
|
|
## Documentation
|
|
|
|
Start at [docs/README.md](docs/README.md), which splits the documentation
|
|
into a tutorial, how-to guides, reference and explanation.
|
|
|
|
- [docs/tutorial/first-run.md](docs/tutorial/first-run.md) — build it, resolve
|
|
a name, block a domain, on a scratch directory
|
|
- [docs/how-to/install-with-systemd.md](docs/how-to/install-with-systemd.md) —
|
|
a real install, including the Raspberry Pi 5
|
|
- [docs/how-to/verify-a-release.md](docs/how-to/verify-a-release.md) — checking
|
|
the hashes and the signature before you install
|
|
- [docs/reference/configuration.md](docs/reference/configuration.md) — every
|
|
configuration field
|
|
- [docs/reference/api.md](docs/reference/api.md) — REST API, auth and SSE
|
|
- [docs/reference/cli.md](docs/reference/cli.md) — subcommands, flags and exit
|
|
codes
|
|
- [docs/explanation/architecture.md](docs/explanation/architecture.md) — module
|
|
map and design
|
|
- [PLAN.md](PLAN.md) and [specs/](specs/) — scope, design decisions and
|
|
per-milestone contracts
|
|
|
|
## Licence
|
|
|
|
Copyright (c) 2026 Mokhtar Mial. nxdns is licensed under the European Union
|
|
Public Licence v. 1.2 (`EUPL-1.2`); the full text is in [LICENSE](LICENSE).
|
|
|
|
Every released tarball and image carries a `THIRD-PARTY-NOTICES` file assembled
|
|
from the reviewed inventory in [licenses/](licenses/), which covers what the
|
|
artifacts actually contain: musl, the Zig runtime, SQLite, Mbed TLS and its
|
|
vendored Everest and p256-m code, and the JavaScript and CSS bundled into the
|
|
admin UI.
|