mokhtar 22abcd9b7b release: nix flake with tag-pinned hashes, reproducible tarballs (milestone 40)
flake.nix fetches the release tarballs and carries their SRI hashes in a generated block. The cut tool builds the release locally with the toolchain gates.yml pins, in a normalized nine-variable environment, writes the hashes into flake.nix, and commits it with build.zig.zon as the single bump commit. The package job verifies the pins on the bump commit and the publish job verifies them again on the tag, before anything is uploaded.

The tarballs are written by dist_stage (std.tar.Writer, flate gzip) instead of the runner's tar and gzip, and -ffile-prefix-map keeps checkout paths out of the C objects; two checkouts at different absolute paths produce byte-identical archives. nxdns version, /api/version and the admin footer report the version only: the bump commit cannot know its own sha.
2026-09-08 21:45:22 +02:00

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.

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 and Adblock Plus lists — whose @@ exception lines are honoured — plus your own allow and block rules, exact, wildcard (*.example.com) or regular expression
  • 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, 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:

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 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.

Full install instructions, including the systemd path and the Pi 5 recipe, are in docs/how-to/install-with-systemd.md and docs/how-to/install-with-docker.md.

Building from source

Requires Zig 0.16.0 and Node.js 24 (for the web UI). C dependencies (SQLite, mbedTLS) are vendored and built by zig build.

(cd admin && npm ci && npm run build)          # web UI -> admin/dist
zig build -Dadmin-dist=admin/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:

(cd admin && 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" \
    -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe     # tarballs -> zig-out/dist/
zig build verify-dist -Dversion-string="$VERSION" \
    -Dadmin-dist=admin/dist -Doptimize=ReleaseSafe     # the release checks

The version comes from build.zig.zon because verify-dist asserts the two agree; a tag sets both.

On the toolchain versions pinned at the top of .gitea/workflows/gates.yml, your tarballs hash the same as the published ones of that version. docs/how-to/verify-a-release.md has the rebuild recipe and what to check when a hash differs.

Documentation

Start at docs/README.md, which splits the documentation into a tutorial, how-to guides, reference and explanation.

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.

Every released tarball and image carries a THIRD-PARTY-NOTICES file assembled from the reviewed inventory in 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.

S
Description
No description provided
Readme EUPL-1.2
6.9 MiB
v0.0.21
Latest
2026-09-12 19:12:55 +00:00
Languages
Zig 82.2%
TypeScript 17.2%
JavaScript 0.3%