Everything needed to get a working ensemble binary on your machine. For what
to do with it once it's built, see running.md; for the full
command surface, commands.md.
| Tool | Needed for | Notes |
|---|---|---|
| Go 1.24+ | every build | the only hard requirement for make build-dev |
| Tor | running with the default tor signaling backend |
a binary in $PATH, or pointed at via --tor-path. Not needed for --signaling=loopback/mdns |
| Node.js 18+ | make build (embeds the web UI) |
builds the Solid.js SPA |
| protoc | make build, make proto |
generates the TypeScript stubs; the Go/Python stubs are committed |
The generated Go protobuf code is checked in, so the protoc Go plugins are
only needed when you change a .proto file (make proto). A make build-dev
needs nothing but Go.
On Fedora:
sudo dnf install tor # for the default Tor backend
sudo dnf install nodejs protobuf-compiler # only for the full UI buildOn macOS:
brew install protobuf node # only for the full UI build# Go-only build — daemon + TUI, no embedded web UI. Needs just Go.
make build-dev # writes bin/ensemble with -tags noembed
# Full build — embeds the Solid.js web UI. Needs Node + protoc.
make build # runs `make ui` then `go build`make build-dev is the fast inner-loop build: it skips the SPA entirely
(-tags noembed) so you don't need Node or protoc to iterate on the daemon.
make build runs the SPA bundle through Vite, stages it under
internal/ui-service/dist, and embeds it via go:embed so the result stays a
single binary.
In-process vs external Tor. On
linux/amd64,make buildlinks Tor in-process via cgo (go-libtor) — no externaltorbinary required. Every other target (ARM, macOS, Windows, and anynoembedbuild) is pure Go and runs an external system Tor you point at with--tor-path.
The Go code cross-compiles cleanly. All cross-builds are pure Go (no cgo, statically linked) and use an external system Tor.
make build-linux-amd64 # embedded Tor (cgo, host C compiler)
make build-linux-arm64 # Raspberry Pi 3/4/5 on 64-bit OS
make build-linux-arm # Pi 2 / Zero 2 W / Pi 3 on 32-bit OS
make build-darwin-amd64
make build-darwin-arm64 # Apple Silicon
make build-windows-amd64
make build-all # all of the aboveFor embedded in-process Tor on ARM, build natively on the Pi — cgo just works there. The cross-build path deliberately stays pure-Go. See running.md → Raspberry Pi / ARM for the runtime side.
To load https://127.0.0.1:9102 without a browser warning, trust the daemon's
per-machine self-signed CA once:
make trust-local-ca # requires sudo; Linux/macOSThe CA lives at <data-dir>/ui/local-ca.crt. Firefox/Windows have their own
trust stores — see ../ui/README.md for the specifics, plus
the SPA dev/hot-reload loop.
The daemon can download the Tor Expert Bundle from dist.torproject.org on
first run and SHA-256-verifies it against a hash pinned in
internal/tor/provider.go. That hash is a TOFU pin set by whoever last
bumped the bundle. A compromised mirror at the exact moment of a bump could
substitute attacker bytes that match an attacker-supplied hash.
make verify-tor-bundle closes that gap with an out-of-band PGP check:
make verify-tor-bundle # current host's GOOS/GOARCH
make verify-tor-bundle TOR_GOOS=darwin TOR_GOARCH=arm64 # a specific targetIt derives the URL + pinned hash from the source constants (no duplication),
downloads <bundle>.tar.gz + .asc, imports the Tor Browser Developers
signing key (EF6E286DDA85EA2A4BA7DE684E2C6E8793298290) from
keys.openpgp.org, runs gpg --verify, and prints the actual vs pinned
SHA-256. Run it whenever you change torVersion or the bundles map, and
cross-check the fingerprint against
https://support.torproject.org/tbb/how-to-verify-signature/ first.
The auto-download path is currently flaky (GitHub issue
01115cc7). Installingtorfrom your package manager and passing--tor-pathis the reliable route today.
make test # unit tests (includes the native-app tests, desktop-safe tags)
make test-race # unit tests with the race detector
make test-integration # integration tests — needs network + Tor
make test-cover # coverage report
make lint # linters
make vet # go vet
make fmt # format
make proto # regenerate ALL protobuf stubs (Go + Python + TypeScript)
make proto-go # Go only
make proto-py # Python SDK stubs only
make proto-ts # TypeScript SPA stubs only
make app # build the native Gio desktop app shell
make test-app # native Gio app tests
make registry # build the registry service binary
make registry-package # build dist/registry-<version>-<os>-<arch>.tar.gzThere are two separate protobuf schemas and make proto keeps them in
lock-step: api/proto/ensemble.proto (the public gRPC API, consumed by the
server, both SDKs, and the browser via gRPC-Web) and
internal/protocol/proto/messages.proto (the internal P2P wire protocol).
The client SDKs live in-repo and build independently of the daemon:
make dotnet-build # restore + build clients/dotnet
make dotnet-test # pure-CLR unit tests (no daemon)
make dotnet-test-integration # builds bin/ensemble, runs against a real daemon
cd clients/python && pip install -e . # editable install for local workSee ../clients/dotnet/README.md and
../clients/python/README.md.