The daemon is one binary that runs in several shapes. Pick the one that matches where you're sitting. For every flag, see commands.md; for the build, setup.md.
Your node identity is a post-quantum keypair: a Dilithium (ML-DSA-65)
identity key anchors your E… address, and a Dilithium-bound Ed25519
operational key handles fast per-message signing and the QUIC/TLS transport.
Both derive deterministically from one 32-byte master seed.
On first run the daemon generates a seed automatically. To provision (and see) it up front:
ensemble init # generate a fresh identity, print the seed
ensemble init --seed <base64> # recover an existing identity from its seedBack up the printed master seed offline — it regenerates your full identity
(same address, same keys) on any machine. Anyone holding it can impersonate
you. It is separate from the web UI's admin seed (ensemble keygen),
which only authenticates a browser to the daemon.
# 1. Daemon + interactive TUI (the default).
ensemble
# 2. Headless daemon (no TUI) — servers, k8s, or when the web UI is the only frontend.
ensemble --headless
# 3. Attach a TUI to an already-running daemon (local socket by default).
ensemble attach
# 4. Attach over a remote TLS-fronted gRPC endpoint (reverse proxy with a real cert).
# --auth-key is the seed file from `ensemble keygen`.
ensemble attach --addr ensemble.example.lan:443 --tls --auth-key admin.keyensemble debug (same connection flags as attach) is the scriptable
counterpart: it prints onion address, routing table, and live connections to
stdout and exits.
The signaling layer — the rendezvous channel two peers use to complete a
contact handshake before swapping IPs and switching to direct QUIC — is
pluggable. Choose at startup with --signaling=<name>. Default is tor.
The headline configuration: every signaling connection rides a Tor hidden service, so peers' real IPs stay hidden from the network until they mutually agree to swap them.
- Pick it for: any WAN / internet-facing deployment, anything where peer IPs are sensitive. It's the only backend with the privacy properties the project was built for.
- Needs: a
torbinary in$PATHor via--tor-path(Fedora:sudo dnf install tor). The daemon can also download the Tor Expert Bundle on first run, but that path is flaky today — package-manager Tor is the reliable route (setup.md → Tor bundle). - Costs: ~6–60s Tor bootstrap on every start (circuit setup, HSDir publish); hidden-service descriptors take their own time to propagate.
Same-host rendezvous via Unix domain sockets in a shared directory. No Tor, no
DHT — each registered service binds <dir>/<E-address>.sock and peers connect
by name.
- Pick it for: same-host dev/test. Iterating on the SDK examples, a a sample matchmaker/game demo, or the multi-daemon integration suite, where the ~60s Tor bootstrap tax per restart gets in the way.
- Needs: Unix domain sockets (Unix-only — fails fast on Windows). Optional
--loopback-dir <path>; default${XDG_RUNTIME_DIR:-/tmp}/ensemble-loopback/. Tor is not started;--tor-pathis warn-and-ignored. - Costs: no anonymity (sockets visible to other host processes;
0700dir0600socket give single-user isolation, no more), same-host only, and the built-inuiHTTP service is unsupported (chat + RPC work normally).
- Boot time: ~60ms process-start to signaling-ready — three orders of magnitude faster than Tor.
# Two terminals, two daemons, same rendezvous dir.
ensemble --headless --signaling=loopback --data-dir /tmp/dev1 --api-addr 127.0.0.1:9090
ensemble --headless --signaling=loopback --data-dir /tmp/dev2 --api-addr 127.0.0.1:9091LAN-only rendezvous via mDNS / DNS-SD. Each service binds an ephemeral TCP port
and advertises <E-address>._ensemble-svc._tcp.local. with a TXT record
carrying eaddr=<E-address>. Peers on the same broadcast domain query the same
record and dial the resolved host:port. No DHT, no Tor, no static peer config.
- Pick it for: homelab / LAN where you control every device and want peers to find each other without Tor or a central rendezvous.
- Needs: a multicast-capable network. The mDNS code is pure Go and does its
own packet handling, so a system responder isn't strictly required.
Cross-platform. Optional
--mdns-iface <name>to pin one interface. - Security caveat — read this: the cryptographic trust model is unchanged
(the handshake still authenticates by signature; an mDNS record is at best an
untrusted routing hint, and an impostor can't complete a handshake without
the target's private key), but the rendezvous is no longer anonymous —
anyone on the broadcast domain can see which Ensemble addresses are present
and which ports they listen on. Do not use
--signaling=mdnson a network you don't trust. It's a different deployment shape, not a drop-in Tor replacement. - Costs: same-broadcast-domain only (does not cross routers), no anonymity,
uiHTTP service unsupported, IPv4-first.
Verify advertisements with standard tools:
avahi-browse -prt _ensemble-svc._tcp # Linux (avahi-utils)
dns-sd -B _ensemble-svc._tcp # macOSThe daemon ships an embedded Solid.js SPA. Three ways to reach it — pick by where the user is sitting:
| Where | URL | Setup |
|---|---|---|
| Same machine | https://127.0.0.1:9102 |
--ui-local-https, then make trust-local-ca once |
| Same LAN, dev | http://<host>:9101 |
--ui-addr 0.0.0.0:9101 --unsafe-plaintext-ui (plain HTTP — trusted LAN only) |
| Phone / remote | https://<ui-onion> in Tor Browser |
nothing extra — the ui service publishes its own onion at daemon start |
The SPA signs each gRPC-Web request with your Ed25519 admin seed (the same seed
ensemble keygen produces and --admin-key validates), so the same auth that
protects the gRPC endpoints protects the browser. Full SPA story — enrolment,
mobile/Tor Browser, local-HTTPS cert lifecycle, CSP, hot-reload — in
../ui/README.md.
ARM cross-builds are pure Go (no cgo) and run an external system Tor — the in-process embedded Tor needs cgo and a native build (build natively on the Pi if you want it; cgo just works there).
# On Raspberry Pi OS / Debian / Ubuntu
sudo apt install tor
./ensemble --headless --tor-path /usr/bin/tor \
--api-addr 127.0.0.1:9099 --admin-key <hex>Forget --tor-path and you get a tailored error pointing here, not a generic
"unsupported platform". LAN-only Pi setups can skip Tor entirely with
--signaling=mdns — a natural fit for a home network that sidesteps the Tor
bootstrap latency.
Metrics are off by default — they're traffic metadata on a privacy app, so nothing is collected at the source until you opt in.
ensemble --metrics # enable the subsystem (UI diagnostics light up)
ensemble --metrics --metrics-listen 127.0.0.1:9100 # also expose Prometheus text at /metricsThe --metrics-listen endpoint has no auth or TLS, so a non-loopback bind is
refused unless --trust-proxy-tls asserts a terminator/allowlist in front —
and verify that gating from an off-LAN vantage; ingress allowlists are easy to
get wrong. Rationale in ADR-0005.
A running daemon can install, supervise, and host other services (chat bots, game matchmakers, group-room hosts, registries). That whole story — the platform model, the SDKs, and the flagship services — is in services.md.