clone

a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed

docs/adopt-site-pages
lofi-node / docs / site / beyond-the-lan.md
beyond-the-lan.md 6.0K rendered
1# Deploying beyond the LAN23<!-- Source: FelineStateMachine/lofi-node README.md, docs/hosting-lofi-apps.md,4 deno.json compile task, src/native/loader.ts. -->56A node on a trusted LAN needs nothing beyond the tutorial. Leaving the LAN — a VPS, a reachable home7server, tickets that travel — adds four concerns: the address you pin into tickets, TLS, the relay8your mesh rides, and the binary you actually run.910## Pin the public URL1112```sh13lofi-node init --public-url https://sync.example.net14```1516`publicUrl` is the base embedded in every issued ticket. It must be the address enrolled devices17will reach — not `localhost`, not an internal IP that stops resolving off-network. It can be changed18per-issuance (`ticket issue --url …`), but pinning it at init keeps tickets consistent.1920## Front the gate with TLS2122The gate speaks plain HTTP; beyond a trusted LAN, put TLS in front of it (a reverse proxy such as23Caddy or nginx terminating `https://` and forwarding to the gate's port) and issue tickets with the24`https://` public URL. Two reasons, one hard and one practical: the ticket URL is a bearer25credential and should not transit networks unencrypted; and installed PWAs generally require a26secure origin, so an app enrolled over plain http may work in a tab and fail as an installed app.2728Note what TLS does **not** change: the ticket still gates access, revocation still works the same,29and the WebSocket upgrade still rides the same URL — the browser client derives its endpoints from30the http(s) base.3132## Bring your own relay3334Paired nodes hole-punch direct connections wherever possible; an35[iroh relay](https://docs.iroh.computer/concepts/relays) assists that hole-punching and carries the36traffic that cannot go direct. Out of the box a node uses n0-computer's **public relays**: shared by37every iroh developer, rate-limited, no uptime guarantees. That default exists so the tutorial works38in thirty seconds; n0 blesses it for development and testing, and a production mesh should not lean39on it.4041```sh42lofi-node init --relay https://relay.example.net43```4445The relay is part of what you self-host, and it fits the machine you already have: `iroh-relay` is a46single open-source binary with built-in Let's Encrypt support, cheap to run because it only carries47the non-direct residue of your own mesh's traffic. The publicly reachable machine hosting your root48node is its natural home. Each node elects its **own** relay (`relay` in49[config.json](configuration.md)), and the election travels inside that node's pairing tickets, so50nothing has to be coordinated across the mesh — no lofi-run infrastructure, no shared choke point.51Your mesh's availability depends only on machines you operate.5253`--relay` accepts several URLs (comma-separated or repeated) for failover. `--no-relay` opts out54entirely; defensible for nodes that are all mutually reachable (static IPs, a VPN), but it also55forgoes relay-assisted address discovery, so expect hole-punching to suffer across NATs.5657## One binary5859```sh60deno task compile # → dist/lofi-node61```6263The compiled binary embeds the prebuilt native transport layer and extracts it to a version-keyed OS64cache on first run. Artifact digests are pinned in the source, so an extraction that doesn't match65its pin fails loudly. Release binaries cover macOS arm64 and Linux x86_64.6667**Windows** is a documented gap: the native layer's build path needs a `libnode.dll` import library68the upstream toolchain doesn't ship for this configuration. On Windows the node runs LAN-only — the69Jazz server works, `status().mesh` reports `{ state: "unavailable", reason: … }`, and70`ticket()`/`pair()` throw typed errors rather than degrade silently.7172**Linux aarch64** is a second documented gap: the Jazz engine (jazz-napi) publishes no73linux-arm64-gnu build, so neither a compiled binary nor a native container image can boot on arm6474Linux. The iroh transport layer is built for the platform already; the target returns when the75upstream package appears.7677## Run it in Docker7879The container image wraps the same compiled binary in a minimal glibc base:80`ghcr.io/felinestatemachine/lofi-node`, published per release, linux/amd64 (per the platform note81above; Docker Desktop and qemu run it on arm64 hosts under emulation).8283```sh84docker run --rm -v lofi-node-data:/data ghcr.io/felinestatemachine/lofi-node \85 init --dir /data --port 4802 --public-url https://sync.example.net86docker run -d --name lofi-node --network host \87 -v lofi-node-data:/data ghcr.io/felinestatemachine/lofi-node88```8990Everything durable lives in the `/data` volume: `config.json`, the node identity key,91`tickets.json`, and the SQLite store. CLI verbs run through `docker exec` against the same directory92and take effect without a restart, exactly as outside a container:9394```sh95docker exec lofi-node lofi-node ticket issue --dir /data --label phone96```9798Two container-specific choices matter:99100- **Networking.** Host networking keeps Docker's bridge NAT out of iroh's hole-punching path. A101 bridge network with a published port (`-p 4802:4802`) serves apps through the gate without issue,102 but paired nodes behind it are more likely to fall back to relayed traffic103 ([the relay's role](pair-two-homes.md)). Host networking is a Linux feature; Docker Desktop104 supports it from version 4.34.105- **The health check** probes `http://127.0.0.1:4802/health`. A node initialized with a different106 `--port` needs the check overridden in compose.107108The image runs as uid 1000. Named volumes inherit that ownership; a bind mount must be writable by109uid 1000 (`chown 1000:1000 <dir>`) or the storage probe fails at boot, naming the path. The110repository's [compose.yaml](https://github.com/FelineStateMachine/lofi-node/blob/main/compose.yaml)111is the reference deployment.112113## The version invariant114115A node pins the **exact** Jazz alpha of the apps it serves; version bumps are coordinated changes,116never drive-by. Before pointing the apps you rely on at your node, confirm the pins match — a117mismatched pair fails in protocol-level ways no amount of network configuration fixes. See118[troubleshooting](troubleshooting.md) for what the mismatches look like.