clone

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

feat/connection-observability
lofi-node / docs / site / self-host-a-sync-node.md
self-host-a-sync-node.md 4.2K rendered
1# Self-host your first sync node23<!-- Source: FelineStateMachine/lofi-node docs/hosting-lofi-apps.md and cli.ts; validated flow. -->45By the end of this tutorial you will have a sync node running on your own machine, an app-connect6ticket in hand, and a lofi app you use syncing against it. You are the app's user here, not its7developer: the app stays exactly as it was deployed, and pointing it at your node is entirely your8call. Nothing below requires a cloud account, a static IP, or TLS on a trusted LAN — one binary and9one paste.1011## 1. Install lofi-node1213Install the tool from [JSR](https://jsr.io/@nzip/lofi-node), which puts `lofi-node` on your PATH:1415```sh16deno install -g -A -n lofi-node jsr:@nzip/lofi-node/cli17```1819For a single self-contained binary instead, compile from a checkout of20[lofi-node](https://github.com/FelineStateMachine/lofi-node):2122```sh23deno task compile # → dist/lofi-node24```2526Either way, full support covers macOS arm64 and Linux x86_64 (including the27[container image](beyond-the-lan.md)); on Windows and arm64 Linux the node is limited28([why](troubleshooting.md)).2930## 2. Initialize and start3132```sh33lofi-node init --dir ./data --port 4802 --public-url http://192.168.1.10:480234lofi-node start --dir ./data35```3637`init` writes `data/config.json`: a generated Jazz app id, the node's secrets, your access mode, and38storage choice. New inits are **ticket-gated** — only requests carrying an issued app-ticket secret39reach the store; `--open` opts out for dev setups. The embedded Jazz server binds loopback-only; the40node's access gate owns the public port, so the gate is both enforcement and reachability.4142`--public-url` is the base address that will be embedded in the tickets you issue — use the address43other devices on your network can reach. `--storage-path /mnt/nas/lofi` puts the store on any44mounted location; `--memory` keeps everything ephemeral ([storage choices](storage-choices.md)).4546`start` prints the gate URL and, when the mesh is up, the node-pairing ticket for47[pairing a second node](pair-two-homes.md).4849## 3. Issue an app ticket5051```sh52lofi-node ticket issue --dir ./data --label phone53# → lofisync1.eyJ2IjoxLCJhcHBJZCI6… (shown once; the secret is never stored)54```5556The ticket is one string carrying the store's app id and the gate URL with an access secret embedded57in its path. It is displayed exactly once — the node keeps only a digest. Issue one per device or58context (`--label` is for your own bookkeeping), so revocation stays scoped:5960```sh61lofi-node ticket list --dir ./data62lofi-node ticket revoke <id> --dir ./data # live connections close within seconds63```6465Scopes matter later: a plain ticket is transport-only; `ticket issue --provision` mints one that can66also [set up the store's schema](provision-a-store.md). What the string actually contains and why67the secret lives in the URL path: [Tickets explained](tickets-explained.md).6869## 4. Enroll the ticket in a lofi app7071Paste the ticket into the app — a lofi app with sync exposes enrollment in its account UI. That72declares your node as the device's sync location and elects sync in one step; the data you already73made in the app pushes up under the same account identity. Nothing about the app changes or74redeploys — pointing it at your node is your call, not the app developer's.7576(If you are the app developer: the framework side is one call, `enrollSyncTicket`, documented in77[Sync and recovery](/docs/sync-and-recovery).)7879## 5. Verify8081- `GET <gate-url>/health` answers without a ticket — safe for liveness probes.82- `GET <ticket-url>/store-status` answers for any valid ticket with metadata about the store's83 schema. A fresh node reports `{ "schema": { "deployed": false } }` — that is expected, and it is84 exactly the state the [store provisioning tutorial](provision-a-store.md) resolves. An app should85 not sync against a store with no schema; its writes would hang rather than fail.86- `lofi-node status --dir ./data` shows configuration, tickets, upstream, and mesh state without87 starting anything.8889## Where you are9091You have a node, a ticket, and an enrolled app — but the store may not have a schema yet. Continue92with [Provision a store](provision-a-store.md), or connect a second node with93[Pair two homes](pair-two-homes.md).