Skip to content

NexaPipe v0.3.0

Choose a tag to compare

@open-nexa open-nexa released this 29 Sep 13:30
· 313 commits to main since this release
100629e

0.3.0 answers the question its own logs could not.

A running instance has always been able to say what happened to a request, and
nothing about itself: how many peers are connected, over which path, how many
backends are actually in rotation, how much work is still in flight. There is a
second listener for that now — off until you bind one, and reachable only from
the machine it runs on.

Health, metrics and live state behind loopback

[admin]
listen_addr = "127.0.0.1:9090"   # absent => the listener is not bound at all

[metrics]
enabled = true                   # serves /metrics on it; default: false
Path Auth Answers
GET /healthz none 200 ok for as long as the process is serving.
GET /metrics none Instance metrics in Prometheus text format.
GET /v1/status, /v1/routes, /v1/clients, /v1/connections, /v1/health token The same instance's live state, as JSON, read fresh on every request.
curl -s http://127.0.0.1:9090/healthz
curl -s http://127.0.0.1:9090/metrics
curl -s -H "authorization: Bearer $(cat config.toml.admin-token)" \
     http://127.0.0.1:9090/v1/status
nexapipe status            # the five endpoints, grouped
nexapipe status --json     # the same read, as one document

What /metrics exposes:

Metric Meaning
nexapipe_connections_total / _active Connections accepted, and how many are open.
nexapipe_connections_by_path{kind="direct"|"relayed"} Read from iroh rather than guessed from an address, so "no relaying" is a number, not a claim.
nexapipe_requests_total{status_class}, nexapipe_request_duration_ms_total Requests by 2xx/4xx/5xx, and the milliseconds they took. Divide one by the other for a mean that survives restart-free scraping.
nexapipe_l4_flows_total{proto,status_class} Raw TCP and UDP flows that ended, and how.
nexapipe_backends_up / _down Backends in and out of rotation.
nexapipe_in_flight, nexapipe_uptime_seconds, nexapipe_build_info{version} Work still in flight, uptime, and which build you are scraping.

Two deliberate edges: with the listener up but metrics off, /metrics is 404
rather than an empty
body, so a scraper can tell "disabled" from "no traffic
yet"; and a missing or wrong token is 503 rather than an open door. The token
is generated by the server on first start and written next to the config as
<config>.admin-token — there is deliberately no key for it inside the config,
because a credential in the file an operator edits, copies and commits is what
this design avoids.

Loopback only, and there is no expose. [server] expose exists because the
plaintext listener may sit behind something else that gates the port; nothing on
this one should reach the network, since it names your routes, clients and
backends. A non-loopback bind is refused at startup. To scrape from another host,
tunnel it: ssh -L 9090:127.0.0.1:9090 <host>.

Android asks before it hands out credentials

The TOTP secret of an endpoint, its otpauth export, and any change to the relay
configuration now require confirmation first — a biometric where there is one,
the screen-lock credential otherwise — and stay open for two minutes after one
confirmation. SecretStore already sealed these at rest; what was missing was
anything asking before handing them back. A device enrolled with neither a
biometric nor a screen lock is refused rather than downgraded. Proxy start and
stop are deliberately not gated: the VPN has to come back after a reboot with
nobody present.

Every request has an ID

32 hex digits per request — one per entry point, not one per connection —
appended to that request's access line as id=<hex>, carried by a request
tracing span alongside its method, URI and status, and returned to the caller as
x-request-id. A backend that sends its own is dropped rather than duplicated,
so there is one ID to quote and it is ours. Appended rather than inserted, too:
every field before it keeps the column it had.

Full changelog

Added

  • The auxiliary loopback listener described above: [admin] listen_addr, with
    GET /healthz always and GET /metrics behind [metrics] enabled.
  • Instance metrics: connections (total, active, and by direct/relayed path),
    requests by status class and duration, L4 flows by protocol and status,
    backends up and down, work in flight, and uptime. Written by hand, so no
    prometheus crate was added and Cargo.lock is untouched.
  • The read-only management surface behind a generated token: GET /v1/status,
    /v1/routes, /v1/clients, /v1/connections and /v1/health.
  • nexapipe status — a client of those same endpoints, with --json. It reads
    the running instance rather than reopening the config, so the instance stays
    the one thing that decides what its state is.
  • The Android confirmation gate over endpoint secrets, otpauth export and relay
    configuration changes.
  • A request ID per entry point: on the access line, on the tracing span, and as
    x-request-id.
  • docs/iroh-boundaries.md — what still depends on third-party infrastructure
    whatever relay_mode says. custom constrains only this endpoint, and
    Endpoint ID discovery still queries dns.iroh.link in every mode.
  • A CHANGELOG.md. 0.2.0 shipped without one, so its entry there is
    retrospective; 0.3.0 is the first release recorded in it as it happened.

Changed

  • Hot-reload rules, previously scattered across the configuration sections, are
    now one table in both READMEs: what a reload applies, and what needs a restart.
  • The access log line ends with id=<hex>.
  • HTTP responses carry x-request-id. An L4 flow and a TLS passthrough are
    tunnels rather than requests: they get an ID on the access line and the span,
    and have no HTTP response to put a header on.

Fixed

  • Both READMEs quoted the iroh version wrongly in two ways at once: as pinned at
    1.0.1, when it is declared ^1.0.1 and Cargo.lock resolves 1.2.0. The line
    now defers to docs/iroh-boundaries.md.

Upgrading

Nothing is removed and no config key changes meaning — every config that
starts under 0.2.0 still starts under 0.3.0. Three things to know:

  • Nothing new is exposed by default. Without an [admin] listen_addr the
    listener is not bound, so upgrading changes no attack surface.
  • [metrics] enabled without an [admin] section logs a warning and serves
    nothing.
  • [admin] listen_addr, [metrics] enabled and [server] listen_addr are
    restart-only: moving a listener is a restart. Everything else reloads, as
    the new table in the README spells out.

Downloads

Everything is attached below, one tag shipping all three products: the server
archives for eight targets, the desktop bundles for six platforms, and the signed
arm64-v8a APK. Each desktop bundle carries a .sig; macOS updater payloads and
a merged latest.json are what the in-app updater reads; SHA256SUMS.txt covers
every file in the release.

curl -sLO https://github.com/open-nexa/nexapipe/releases/download/v0.3.0/SHA256SUMS.txt
sha256sum -c --ignore-missing SHA256SUMS.txt

Full list with checksums and per-platform buttons:
https://open-nexa.github.io/nexapipe/v0.3.0.html · complete history in
CHANGELOG.md ·
what comes next in
docs/ROADMAP.md.