Pair two homes
Two nodes in two places — a home server and a studio machine, say — can hold the same store and converge over iroh: dialed by public key, hole-punched through NATs, no static IPs, no port forwarding, no cloud dependency. Devices keep syncing against whichever node is closest; the nodes reconcile with each other.
Pairing
Every node with the mesh up prints a node-pairing ticket on start — an iroh endpoint… string,
distinct from the lofisync1. app tickets users enroll. On the second node:
lofi-node pair endpoint… --dir ./data
or at runtime, without a restart and without the public port changing:
await node.pair(otherNodeTicket);
Pairing is an upstream election: the paired node becomes a leaf that forwards to the root. Sync WebSockets and catalogue HTTP (schema reads, deploys) tunnel over iroh, so administering either node reaches the root — provisioning through a leaf lands on the root's store, verified end to end in lofi-node's test suite.
What the tunnel carries
One iroh connection carries exactly one WebSocket or one HTTP request. Each connection opens with a
small HELLO frame naming what it carries (ws with path and subprotocol, or http with method,
path, and headers); frames then bridge both ways until either side closes. This is why the
browser-facing protocol never changes: the leaf re-originates exactly what Jazz expects.
Observing the mesh
node.status().mesh;
// { state: "up", nodeId, ticket, connections: [{ direction, rtt, paths }] }
connections reports each live tunnel with its round-trip time and path counts — direct versus
relay — so you can see whether hole-punching succeeded or traffic is riding an iroh relay. The other
two states are honest degradations: { state: "off" } when the mesh is disabled, and
{ state: "unavailable", reason } when the native layer could not load — the Jazz server still runs
LAN-only, and ticket()/pair() throw MeshUnavailableError rather than pretending.
The relay's role
Relays assist that hole-punching (address discovery) and carry traffic only for the connections that
cannot go direct; in practice the large majority of iroh connections are direct. The relay is a
per-node election (relay in config.json): whichever relay a node elects
travels inside its pairing ticket, so the dialing side needs no matching configuration. The default
is n0-computer's public relays, which are rate-limited and meant for development. For a production
mesh, bring your own.
Choosing a root
The root holds the authoritative store; leaves relay to it. Put the root on the machine with the
most reliable storage (storage choices) and pair outward from there. A leaf's
election can be redone at runtime (pair again) if the topology changes.