10 Commits

Author SHA1 Message Date
Razvan Dimescu
b3f3a4f36c fix aarch64 musl build: use cross instead of musl.cc download
musl.cc was unreachable from CI. cross handles the Docker-based
cross-compilation automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:01:59 +02:00
Razvan Dimescu
14b035387b switch Linux builds to musl for static binaries
glibc-linked binaries fail on older distros (GLIBC_2.38 not found).
musl produces fully static binaries that work on any Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 12:50:34 +02:00
Razvan Dimescu
d457ffc296 remove unused rustls-pemfile dependency
Dead code — certs are generated at startup, not loaded from PEM files.
Removes RUSTSEC-2025-0134 warning. Audit now passes clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 12:03:13 +02:00
Razvan Dimescu
8ab50844c2 fix audit: update rustls-webpki, ignore unmaintained pemfile warning
RUSTSEC-2026-0049 fixed by updating rustls-webpki 0.103.9 → 0.103.10.
RUSTSEC-2025-0134 (rustls-pemfile unmaintained) ignored — no replacement
available, warning only, not a vulnerability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 11:59:52 +02:00
Razvan Dimescu
e04afe5b70 add cargo-audit to Makefile lint target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 10:53:09 +02:00
Razvan Dimescu
44113492f0 add CI/crates.io/license badges, cargo-audit in CI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 10:51:13 +02:00
Razvan Dimescu
ec41f32d4e clarify single binary — no PHP, no web server, no database
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 10:17:39 +02:00
Razvan Dimescu
a35b0ea23c updated hero 2026-03-21 04:49:18 +02:00
Razvan Dimescu
fbdb0a245f Merge pull request #6 from razvandimescu/feat/404-page
Styled 404 page for unregistered .numa domains
2026-03-21 04:33:59 +02:00
Razvan Dimescu
285778b646 add styled 404 page for unregistered .numa domains
Roman Stone themed 404 with Instrument Serif heading, JetBrains Mono
domain badge, brick pattern background, syntax-highlighted curl
example, and a delayed easter egg. Also updates dashboard link in
README to numa.numa.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 04:31:17 +02:00
8 changed files with 144 additions and 30 deletions

View File

@@ -24,3 +24,5 @@ jobs:
run: cargo clippy -- -D warnings
- name: test
run: cargo test
- name: audit
run: cargo install cargo-audit && cargo audit

View File

@@ -19,10 +19,10 @@ jobs:
- target: aarch64-apple-darwin
os: macos-latest
name: numa-macos-aarch64
- target: x86_64-unknown-linux-gnu
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
name: numa-linux-x86_64
- target: aarch64-unknown-linux-gnu
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
name: numa-linux-aarch64
@@ -35,23 +35,28 @@ jobs:
with:
targets: ${{ matrix.target }}
- name: Install cross-compilation tools
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Install musl tools (x86_64)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build
- name: Install cross (aarch64)
if: matrix.target == 'aarch64-unknown-linux-musl'
run: cargo install cross
- name: Build (native)
if: matrix.target != 'aarch64-unknown-linux-musl'
run: cargo build --release --target ${{ matrix.target }}
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
- name: Build (cross)
if: matrix.target == 'aarch64-unknown-linux-musl'
run: cross build --release --target ${{ matrix.target }}
- name: Package
run: |
cd target/${{ matrix.target }}/release
tar czf ../../../${{ matrix.name }}.tar.gz numa
cd ../../..
sha256sum ${{ matrix.name }}.tar.gz > ${{ matrix.name }}.tar.gz.sha256
sha256sum ${{ matrix.name }}.tar.gz > ${{ matrix.name }}.tar.gz.sha256 || shasum -a 256 ${{ matrix.name }}.tar.gz > ${{ matrix.name }}.tar.gz.sha256
- name: Upload artifact
uses: actions/upload-artifact@v4

14
Cargo.lock generated
View File

@@ -944,7 +944,6 @@ dependencies = [
"rcgen",
"reqwest",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"time",
@@ -1275,15 +1274,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
@@ -1296,9 +1286,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.9"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"aws-lc-rs",
"ring",

View File

@@ -26,4 +26,3 @@ rcgen = { version = "0.13", features = ["pem", "x509-parser"] }
time = "0.3"
rustls = "0.23"
tokio-rustls = "0.26"
rustls-pemfile = "2"

View File

@@ -1,11 +1,11 @@
.PHONY: all build lint fmt check test clean deploy
.PHONY: all build lint fmt check audit test clean deploy
all: lint build
build:
cargo build
lint: fmt check
lint: fmt check audit
fmt:
cargo fmt --check
@@ -13,6 +13,9 @@ fmt:
check:
cargo clippy -- -D warnings
audit:
cargo audit
test:
cargo test

View File

@@ -1,10 +1,14 @@
# Numa
[![CI](https://github.com/razvandimescu/numa/actions/workflows/ci.yml/badge.svg)](https://github.com/razvandimescu/numa/actions)
[![crates.io](https://img.shields.io/crates/v/numa.svg)](https://crates.io/crates/numa)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
**DNS you own. Everywhere you go.**
A portable DNS resolver in a single binary. Block ads on any network, name your local services (`frontend.numa`), and override any hostname with auto-revert — all from your laptop, no cloud account or Raspberry Pi required.
Built from scratch in Rust. Zero DNS libraries. RFC 1035 wire protocol parsed by hand.
Built from scratch in Rust. Zero DNS libraries. RFC 1035 wire protocol parsed by hand. One ~8MB binary, no PHP, no web server, no database — everything is embedded.
![Numa dashboard](assets/hero-demo.gif)
@@ -22,7 +26,7 @@ dig @127.0.0.1 google.com # ✓ resolves normally
dig @127.0.0.1 ads.google.com # ✗ blocked → 0.0.0.0
```
Open the dashboard: **http://localhost:5380**
Open the dashboard: **http://numa.numa** (or `http://localhost:5380`)
Or build from source:
```bash

Binary file not shown.

Before

Width:  |  Height:  |  Size: 808 KiB

After

Width:  |  Height:  |  Size: 775 KiB

View File

@@ -141,9 +141,120 @@ async fn proxy_handler(State(state): State<ProxyState>, req: Request) -> axum::r
Some(entry) => entry.target_port,
None => {
return (
StatusCode::BAD_GATEWAY,
StatusCode::NOT_FOUND,
[(hyper::header::CONTENT_TYPE, "text/html; charset=utf-8")],
format!(
"unknown service: {}{}",
r##"<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>404 — {0}{1}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=DM+Sans:opsz,wght@9..40,400;9..40,500&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
<style>
*,*::before,*::after {{ margin:0;padding:0;box-sizing:border-box }}
body {{
font-family: 'DM Sans', system-ui, sans-serif;
background: #f5f0e8;
color: #2c2418;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
-webkit-font-smoothing: antialiased;
position: relative;
overflow: hidden;
}}
body::before {{
content: '';
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg width='120' height='60' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='56' height='27' rx='1' fill='none' stroke='%23a39888' stroke-width='0.5' opacity='0.12'/%3E%3Crect x='61' y='1' width='56' height='27' rx='1' fill='none' stroke='%23a39888' stroke-width='0.5' opacity='0.12'/%3E%3Crect x='31' y='31' width='56' height='27' rx='1' fill='none' stroke='%23a39888' stroke-width='0.5' opacity='0.12'/%3E%3C/svg%3E");
background-size: 120px 60px;
pointer-events: none;
opacity: 0.5;
-webkit-mask-image: radial-gradient(ellipse at center, transparent 20%, rgba(0,0,0,0.4) 70%);
mask-image: radial-gradient(ellipse at center, transparent 20%, rgba(0,0,0,0.4) 70%);
}}
.container {{
position: relative;
z-index: 1;
text-align: center;
max-width: 480px;
padding: 2rem;
animation: rise 0.6s cubic-bezier(0.22,1,0.36,1);
}}
@keyframes rise {{
from {{ opacity:0; transform:translateY(20px) }}
to {{ opacity:1; transform:translateY(0) }}
}}
.code {{
font-family: 'Instrument Serif', Georgia, serif;
font-size: 6rem;
line-height: 1;
color: #c0623a;
letter-spacing: 0.04em;
opacity: 0.85;
}}
.domain {{
font-family: 'JetBrains Mono', monospace;
font-size: 1.1rem;
color: #2c2418;
margin-top: 1rem;
padding: 0.4rem 1rem;
background: rgba(192,98,58,0.08);
border: 1px solid rgba(192,98,58,0.15);
border-radius: 6px;
display: inline-block;
}}
.message {{
color: #6b5e4f;
margin-top: 1.2rem;
line-height: 1.7;
font-size: 0.95rem;
}}
.message a {{
color: #c0623a;
text-decoration: none;
border-bottom: 1px solid rgba(192,98,58,0.3);
}}
.message a:hover {{ border-bottom-color: #c0623a }}
pre {{
text-align: left;
background: #1a1814;
color: #e8e0d4;
padding: 1rem 1.2rem;
border-radius: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.78rem;
line-height: 1.7;
margin-top: 1.2rem;
overflow-x: auto;
}}
pre .prompt {{ color: #8baa6e }}
pre .flag {{ color: #8b9fbb }}
pre .str {{ color: #d48a5a }}
.lyrics {{
margin-top: 2.5rem;
font-family: 'Instrument Serif', Georgia, serif;
font-style: italic;
font-size: 0.85rem;
color: #a39888;
letter-spacing: 0.03em;
opacity: 0;
animation: fade 0.8s 1.5s forwards;
}}
@keyframes fade {{ to {{ opacity: 1 }} }}
</style></head><body>
<div class="container">
<div class="code">404</div>
<div class="domain">{0}{1}</div>
<p class="message">This service isn't registered yet.<br>Add it from the <a href="http://numa.numa">dashboard</a> or:</p>
<pre><span class="prompt">$</span> <span class="str">curl</span> <span class="flag">-X POST</span> numa.numa:5380/services \
<span class="flag">-H</span> 'Content-Type: application/json' \
<span class="flag">-d</span> '<span class="str">{{"name":"{0}","target_port":3000}}</span>'</pre>
<div class="lyrics">ma-ia hii, ma-ia huu, ma-ia haa, ma-ia ha-ha</div>
</div>
</body></html>"##,
service_name, state.ctx.proxy_tld_suffix
),
)