sagittarius 0.2.0

A fast, self-hosted DNS sinkhole in a single Rust binary
Documentation
# Sagittarius

> A fast, self-hosted DNS sinkhole in a single Rust binary.

Sagittarius is an open-source, network-wide DNS sinkhole — it blocks ads,
trackers, and malicious domains for every device on your network, in the spirit
of [Pi-hole](https://pi-hole.net/) and
[AdGuard Home](https://adguard.com/en/adguard-home/overview.html). Everything —
the DNS engine, storage, and web admin interface — ships in **one
self-contained binary**.

It is named after **Sagittarius A\***, the supermassive black hole at the centre
of the Milky Way: a fitting namesake for something whose job is to make unwanted
traffic disappear.

> ⚠️ **Early development.** Sagittarius is pre-1.0 and not yet ready for general
> use. The design is described in [`SPEC.md`]SPEC.md; interfaces and features
> are still changing.

---

## Goals

- **Single binary** — no external services, interpreters, or web server to set up.
- **Fast** — a hand-written DNS codec and a `tokio`/`tower` request pipeline keep
  the hot path lean.
- **Light** — runs comfortably on small home servers, routers, and SBCs.
- **Operable** — a clean, authenticated web UI for configuration, live query
  inspection, and statistics; icon-enhanced and responsive, with a collapsing
  navigation drawer on mobile.

## Planned features (v0.1)

- 🛡️ **DNS blocking** via subscribable blocklists (hosts / domain-list formats)
  plus manual allow/deny lists. Exact-domain matching in v0.1. **Per-list
  effectiveness** shows how many blocks each source contributed (last 24h) and
  its share of the total, so you can see which lists are pulling their weight.
- ⏸️ **Pause blocking** temporarily — snooze all blocking for 5 min / 30 min /
  1 h (or a custom duration) with a *Resume now* control and a live countdown.
  Local DNS records keep answering, and a restart always resumes blocking.
- 📊 **Live query log and dashboard** — block ratio, top domains, top clients,
  and a live query log streamed over SSE, with one-click allow/deny straight
  from the log. Clients are shown as their **device hostname** (`hostname (ip)`,
  IP fallback) in both the log and top-clients, resolved internally — set up
  reverse DNS (below) so your LAN devices get names. The log and dashboard are
  backed by a **persistent query log** (configurable retention, default 30 days;
  an enable/disable toggle and a clear-now action), so history and windowed
  figures survive a restart. The dashboard also shows a **System** panel
  (version, uptime, queries/sec, cache fill, process memory) and **per-upstream
  health** (success rate, latency).
- 🔒 **Encrypted upstreams** — forward to resolvers over DNS-over-HTTPS (DoH) and
  DNS-over-TLS (DoT), with selectable **upstream strategy** (random,
  latency-weighted, or parallel-race) and per-upstream health tracking.
- 🏠 **Local DNS records** — define names for your own network, including
  wildcards (e.g. `*.home.lan`).
- 🔁 **Reverse DNS for the LAN** — answers PTR queries for your local records
  (so `nslookup <LAN IP>` resolves), and **conditional-forwards** the private
  reverse zones (`in-addr.arpa` / `ip6.arpa`) to your router/DHCP resolver with
  a one-click setup.

See the [roadmap](SPEC.md#12-roadmap) for what's planned beyond the first
milestone.

## Architecture at a glance

| Layer | Technology |
|---|---|
| Async runtime | [`tokio`]https://tokio.rs |
| Service middleware (rate limiting, backpressure) | [`tower`]https://docs.rs/tower |
| DNS wire format | Custom lazy parser/serializer over [`bytes`]https://docs.rs/bytes — shallow parse + raw passthrough |
| Upstream transport | [`hickory`]https://github.com/hickory-dns/hickory-dns — UDP/TCP/DoT/DoH |
| Persistent storage | SQLite via [`sqlx`]https://docs.rs/sqlx — compile-time-checked queries, embedded migrations; stores config and the durable query-log history |
| Logging | [`tracing`]https://docs.rs/tracing to stdout; live log streamed to the UI over SSE |
| CLI / config | [`clap`]https://docs.rs/clap flags (bind addresses, db path) with sane defaults |
| Hot-path state | `HashSet` blacklist/allowlist + a `HashMap` blocklist (domain → primary source) and `HashMap` local records, hot-swapped via [`arc-swap`]https://docs.rs/arc-swap; [`moka`]https://docs.rs/moka cache with per-entry TTL |
| Web server | [`axum`]https://docs.rs/axum |
| Templating / UI | [`askama`]https://docs.rs/askama + [Datastar]https://data-star.dev (SSE) + [Pico CSS]https://picocss.com |
| Frontend assets | Vendored and embedded via `include_str!` / `include_bytes!` — no CDN, no Node build; Lucide icons (`icondata_lu`) served as one `<symbol>` sprite |

The full design is documented in [`SPEC.md`](SPEC.md).

## Install

Pick whichever fits; all of them give the same single binary.

**Quick install (Linux + systemd).** Downloads the latest release binary
(checksum-verified), creates an unprivileged `sagittarius` user, and installs
and starts the hardened systemd service from
[`deploy/sagittarius.service`](deploy/sagittarius.service):

```sh
curl -fsSL https://raw.githubusercontent.com/LHelge/sagittarius/main/deploy/install.sh | sudo sh
```

Re-running the script upgrades an existing install in place. The admin UI is
then reachable on port 8080 of the host. (As with any `curl | sh`, feel free
to download [`deploy/install.sh`](deploy/install.sh) and read it first.)

**Prebuilt binary.** Download a Linux `x86_64` or `aarch64` tarball from the
[latest release](https://github.com/LHelge/sagittarius/releases/latest), verify
its `.sha256`, unpack, and run `./sagittarius`.

**From crates.io.**

```sh
cargo install sagittarius
```

**Docker** (multi-arch `amd64` / `arm64`, published to GHCR):

```sh
docker run -d --name sagittarius \
  -p 53:53/udp -p 53:53/tcp \
  -p 127.0.0.1:8080:8080 \
  -v sagittarius-data:/data \
  ghcr.io/lhelge/sagittarius:latest
```

The admin UI is published on loopback above; front it with a reverse proxy for
remote access. A ready-made Compose file is at
[`deploy/docker-compose.yml`](deploy/docker-compose.yml).

## Building

Requires a recent Rust toolchain (edition 2024).

```sh
git clone https://github.com/LHelge/sagittarius.git
cd sagittarius
cargo build --release
```

The resulting binary is at `target/release/sagittarius`.

## Running

```sh
# all flags shown with their defaults — `sagittarius` alone works too
sagittarius --dns-addr 0.0.0.0:53 --admin-addr 127.0.0.1:8080 --db-path sagittarius.db --session-cookie-secure auto
```

On first run, open the admin interface and complete the one-step wizard to
create the initial admin user. Everything else is pre-seeded to working defaults
(e.g. Cloudflare `1.1.1.1` / `1.0.0.1` upstreams), so the resolver is functional
immediately. All state lives in the single SQLite file at `--db-path`.

Notes:

- Binding port 53 usually needs elevated privileges or the
  `CAP_NET_BIND_SERVICE` capability.
- The admin interface serves plain HTTP and defaults to loopback. Put it behind
  a reverse proxy (Caddy, Traefik, nginx) for TLS / remote access.
- `--session-cookie-secure auto` uses secure admin cookies when the
  browser-facing request is HTTPS, and allows direct local HTTP while testing.

## Deployment

The [`deploy/`](deploy/) directory has ready-to-adapt examples:

- [`deploy/install.sh`]deploy/install.sh — the **quick-install script** (see
  [Install]#install) that downloads the latest release and sets up the
  systemd service below.
- [`deploy/sagittarius.service`]deploy/sagittarius.service — a hardened
  **systemd** unit that runs Sagittarius as a dedicated unprivileged user with
  `CAP_NET_BIND_SERVICE` for port 53, the database under
  `/var/lib/sagittarius`, graceful `SIGTERM` shutdown, and the admin UI on
  `0.0.0.0:8080` so it is reachable from the LAN out of the box.
- [`deploy/Caddyfile`]deploy/Caddyfile — a **reverse-proxy** snippet that
  terminates TLS (automatic certificates) in front of a loopback admin UI.

For TLS or exposure beyond a trusted network, bind the admin interface to
loopback and reach it only through the proxy; the forwarded scheme/host
headers it sets are trusted for secure-cookie and CSRF origin decisions.

> Detailed installation and configuration docs will be added as the project
> stabilizes.

## Contributing

Sagittarius is in early development and contributions, ideas, and feedback are
welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) for the quality gate,
branch/PR workflow, and how to set up the local git hooks.

Enable the hooks with a single command after cloning:

```sh
git config core.hooksPath .githooks
```

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as below, without any additional terms or conditions.

## License

Licensed under either of

- Apache License, Version 2.0 ([`LICENSE-APACHE`]LICENSE-APACHE or
  <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([`LICENSE-MIT`]LICENSE-MIT or
  <http://opensource.org/licenses/MIT>)

at your option.