rabbit.direct — A modern Rust revival of the golden age of online communities: Hotline, KDX, BBSes, and AOL. One server, many doors.
This repository hosts the code for the RabbitHole Showcase & Looking Glass Directory (rabbit.direct), the central hub for the RabbitHole network. Built as a highly interactive, cyber-mystical Single-Page Application (SPA), it embodies the design language of Mirrorward and helps users discover active Burrows, download clients, and join the decentralized collective.
🌐 Production Site: rabbit.direct (Aliases: rabbithole.directory | rabbithole.direct)
A realtime, filterable status list of active, self-certifying Burrows, aggregated from Looking Glass publishes and streamed to every open browser via Server-Sent Events.
How data flows — the directory sits at the top of a two-tier gossip fabric:
Burrows ──(signed descriptors, RHP gossip)──▶ Looking Glass indexes ──(signed publishes, HTTPS)──▶ rabbithole.directory
tracker.rabbit.direct:4650 official: tracker.rabbit.direct (this project)
plus community-run glasses
- Burrows never talk to this directory directly. They announce to a Looking Glass — the official coordinator at
tracker.rabbit.direct(theglass.rabbit.directproject) viagossip_coordinatorsinburrow.toml. - Looking Glasses publish here. The official glass and any community-run glass POST their verified index views to
POST /api/publish. Glass names are self-certifying Ed25519 identities (first-key-wins); the official glass is designated by name (tracker.rabbit.direct, overridable via theOFFICIAL_GLASS_NAMESenv var) and labeled OFFICIAL in the UI — everyone else is COMMUNITY. - Double signature verification. Each publish is signed by the glass, and every relayed burrow entry carries the burrow's own signed descriptor envelope. The directory verifies both layers, so burrow identity continuity holds even across different glasses (first-key-wins per burrow name).
- Realtime updates: the page subscribes to
GET /api/events(SSE). New burrows appear, publishes flash rows, status flips online/offline without a reload, and gossip events print into the on-page terminal. - Real in-browser verification: for live burrows, "Verify Signature" re-verifies the actual relayed burrow envelope in your browser using WebCrypto Ed25519. The inspector also shows which glasses reported the burrow.
- Observed uptime sparklines: live burrows get 7-day presence sparklines computed from report history (2-hour slots). A burrow is online if at least one fresh glass (~180s window) reports it online.
| Endpoint | Description |
|---|---|
POST /api/publish |
Looking Glass publish: { publish: { glass, burrows[] }, publicKey, signature } |
GET /api/burrows |
Full directory snapshot (JSON): burrows, glasses, version, gossip log |
GET /api/events |
SSE stream — emits a directory event (full snapshot) whenever the registry changes |
Publish envelope — the outer signature is an Ed25519 signature (hex) over the canonical JSON of publish (keys sorted recursively, no whitespace); publicKey is the glass's raw 32-byte Ed25519 public key (hex). Each burrow entry independently carries the burrow's own key and signature over its descriptor:
Publish every 60s; reports from a glass silent for ~180s go stale, and a burrow with no fresh online report is shown offline. Publishes are throttled to one per 10s per (glass, key). Invalid burrow entries are rejected individually (reported in the response) without failing the whole publish.
Run a community Looking Glass publisher with the zero-dependency reference client (generates and persists a glass Ed25519 identity in ~/.config/rabbithole/glass-keys/):
# Publish your glass's verified index (a JSON array of burrow envelopes):
node scripts/glass-publish.mjs --name glass.your-domain.net \
--operator "You" --url https://glass.your-domain.net \
--burrows ./verified-index.json --directory https://rabbithole.directory
# Or simulate a glass end-to-end (N locally-signed demo burrows):
node scripts/glass-publish.mjs --name glass.your-domain.net --demo 3 --onceThe registry picks the best available backend at runtime (api/_lib/store.js):
- Upstash Redis (Vercel Marketplace) — durable, atomic. Attach with
vercel integration add upstash/upstash-kv(requires one-time marketplace terms acceptance in the dashboard), then redeploy. - Vercel Runtime Cache — used on deployments when Redis env vars are absent. Shared across functions per region but ephemeral (LRU) — the UI labels this
EPHEMERAL STORE. - Shared tmp file — local
vercel devfallback so full-stack dev works offline.
Seed showcase burrows (Wonderland/Matrix lore) populate the index on first boot and are labeled distinctly from live announcers; a real signed announce for a seed name takes the entry over.
A fully simulated virtual CLI client mimicking the official rabbit client shell.
- Burrow Connection Hook: Clicking "Connect via Console" on any active Burrow in the directory automatically scrolls to the console and initiates a connection prompt (
ws://[burrow_uri]:4654). - Persona Authentication: Sign in (e.g. username
alice, passwordwonderland) to enter the chat lobby and talk with virtual network personas (Neo, Morpheus, Hatter, Cheshire, etc.) specific to each Burrow. - Commands: Run standard commands like
help,status,tunnels,warren,download,matrix,mirrors, andclearto control console themes and queries.
Introduces the core concepts of the RabbitHole protocol (RHP):
- Burrows: The self-hosted, sovereign node daemon written in Rust.
- Tunnels: Peered server-to-server (S2S) secure replication links.
- The Warren: Bao-verified, work-stealing P2P file swarming.
- Legacy Doors: Backwards compatibility gate listeners for Telnet BBS (with ZMODEM & Door games), Hotline, Finger (RFC 1288), Icecast Radio (ICY DJ feeds), and NNTP/FidoNet newsfeeds.
- Structure: Semantic HTML5 (incorporating standard ARIA states and
<dialog closedby="any">for dismissible overlays) - Styling: Modern vanilla CSS (OKLCH color system, glassmorphism templates, CSS view timelines for scroll-driven reveals)
- Logic: Vanilla ES Modules (canvas-based matrix rain, terminal emulator state engine, Gossip filter, EventSource live feed, WebCrypto Ed25519 verification)
- API: Vercel Functions (
api/) — signed-descriptor ingest, directory snapshots, SSE realtime stream - Storage: Upstash Redis via Vercel Marketplace (with Runtime Cache / tmp-file degraded modes)
- Build System: Vite (asset bundling)
- Deployment: Vercel
- Node.js (v18+)
- npm
-
Clone the repository:
git clone https://github.com/mirrorward/rabbit.direct.git cd rabbit.direct -
Install dependencies:
npm install
-
Spin up the full stack locally (static site + directory API + SSE):
npx vercel dev
(
npm run devruns the Vite static site only — the directory then runs in SHOWCASE mode without the live API.) -
Publish a simulated Looking Glass view against your local directory and watch its burrows appear live:
node scripts/glass-publish.mjs --name glass.local-test.net \ --demo 3 --directory http://localhost:3000 --once
-
Build the optimized production assets:
npm run build
The project is configured for serverless hosting on Vercel:
-
Production CLI Deploy Command:
npx vercel --prod
-
Configured Domains:
rabbithole.directoryrabbit.directrabbithole.direct
-
DNS Setup: To delegate your custom domain, set an
Arecord pointing@to76.76.21.21.
RabbitHole is a flagship collective project under Mirrorward. Learn more about Mirrorward's cyber-mystical manifesto and software stack at:
{ "publish": { "glass": { "name": "glass.example.net", // required: hostname-like glass identity "ts": 1786500000000, // required: unix ms, ±5 min of server time "operator": "Your Name", // optional "url": "https://glass.example.net" // optional }, "burrows": [ // ≤200 entries per publish { "descriptor": { "name": "alice@wonderland", // persona@burrow "ts": 1786499990000, // signed by the burrow ≤15 min ago "sysop": "Alice Liddell", "description": "…", "plan": "…", "listeners": ["quic", "ws"], // subset of: quic ws tunnels telnet hotline finger radio nntp "quicUri": "quic://host:4653", "wsUri": "ws://host:4654" }, "publicKey": "burrow key, 64-char hex", "signature": "burrow sig over descriptor, 128-char hex", "observed": { "status": "online" } // optional glass-side observation } ] }, "publicKey": "glass key, 64-char hex", "signature": "glass sig over publish, 128-char hex" }