Skip to main content

GenosDB Fallback Server (GenosSRV)

GenosDB is designed to run without servers. The Fallback Server is an optional superpeer you can add to a room when you want guarantees no browser can give: being always on. It ships as a single file with zero dependenciesgenossrv.min.js, published with the GenosDB dist — and runs anywhere Bun runs.

What it does

The server behaves as just another peer in the network: it discovers and connects through the same Nostr relays as everyone else (or through its own — see Your own signaling), and every operation it emits is verified by every browser like anyone else’s. It does not centralize anything — it only adds the one guarantee no browser can offer, being always on.

Quick start

That’s it. The server joins the room, syncs bidirectionally, and persists everything in ./data.sqlite.

CLI reference

Match the transport to your app. A room where browsers use rtc: { cells: … } speaks over per-cell channels; start the server with --cells or it cannot receive browser writes (it will print a one-line warning telling you exactly this).

As a module

Options mirror gdb(name, options) — same names, same shapes:
The full query engine is available server-side (db.map with every operator, realtime subscriptions, middleware via db.use) — identical to the browser’s.

Governance: an always-on superadmin

In the browser, the governance engine runs only while a superadmin keeps a tab open. The fallback server removes that limitation: give it a signing identity and it becomes the room’s 24/7 governance authority — promotions, demotions and role expirations no longer depend on anyone being connected.

How authority works

At boot the server logs 🛡️ SM: signing as 0x… — add that address to your application’s sm.superAdmins list (see the Security Manager documentation), and every peer verifies the server’s operations like any superadmin’s: same signatures, same machinery. Revoking it is as simple as removing the address from that list.

How rules work

The server must carry the same rules your application declares — one room, one policy: the server is simply its always-on executor. Each rule’s if is a plain GenosDB query, evaluated every few seconds against the synced graph with last-match-wins resolution: order rules easy→hard, and the last matching rule decides the role. Losing a condition auto-demotes; superadmins are immune by construction. See the Governance documentation for the rule model.
The engine boots prudently (it waits for the first sync exchanges to converge before ruling on the graph) and writes a node only when its resolved role actually changes.

Key custody

The signing key lives on your server — treat it accordingly: use a dedicated governance identity (not your personal superadmin key), inject it through the environment or a secret manager, and never commit it. If the server is ever compromised, removing its address from your clients’ superAdmins revokes it entirely.

Your own signaling

By default peers discover each other through public Nostr relays — those carry only encrypted handshakes, never your data. To own that layer too, the server embeds a signaling relay:
One flag, zero extra infrastructure: an ephemeral Nostr relay (the exact subset GenosDB signaling uses) served on $PORT — nothing is ever stored, every event is schnorr-verified before retransmission, and GET / on the same port answers a JSON health status. With the relay on, the server signals through its own relay (plus anything you list in GDB_RELAY_URLS). Point your application at it and the whole stack — signaling, persistence, governance — runs on your infrastructure:
Private by construction: with your relay as the only entry on both sides, not even discovery metadata leaves your infrastructure. List public relays too (GDB_RELAY_URLS="wss://…,wss://…") if you want resilience through them as well. ws:// vs wss://: use ws://host:port for local/LAN testing (browsers exempt localhost from mixed-content rules); production pages require wss://, whose TLS is terminated by your platform or proxy — on Heroku, wss://your-app.herokuapp.com works out of the box — while the relay itself always speaks plain WebSocket behind it.

Deployment

Anywhere Bun runs. The artifact is self-contained, so deployment is: fetch one file, run one command. VPS / bare metal
Run it under your process manager of choice (systemd, pm2, …) for restarts. Docker
Any container platform (Fly.io, Railway, Render, …) can deploy that Dockerfile directly.

🚀 Deploy to Heroku in one click

Every option — room, transport, identity, governance rules — is an environment variable, so the deploy form configures the whole server: Deploy A fully governed superpeer, from nothing, in one command:
Ephemeral filesystems (Heroku and similar): the SQLite file lives only as long as the dyno — on restart the superpeer starts empty and re-syncs from whatever peers are online. For durable-at-rest storage, deploy on a platform with persistent volumes (a VPS, Fly.io volumes, …). Relay mode on Heroku: the embedded relay binds $PORT, so run it as the web process (heroku ps:scale web=1 worker=0) — the app’s URL then becomes your signaling endpoint (wss://your-app.herokuapp.com) and its root serves the relay’s health status.

Verification

The logs are the server’s interface — every state it goes through has a line. A healthy boot:
With the relay enabled, GET http://host:$PORT/ returns {"relay":…,"connections":N,"events":N,"dropped":N}dropped counts events rejected by signature verification. And in operation, the signals that prove it is doing its job: End-to-end check: open your application, write something, close every client, reopen — the data coming back can only have been served by the fallback server.