Down the rabbit hole: a modern Rust revival of the golden age of online communities — Hotline, KDX, BBSes, and AOL — one server, many doors.
A Burrow (server) hosts chat rooms, message boards, file libraries, direct messages, a swarm file-distribution layer (the Warren), pirate radio, and a request board (the Wishing Well) — reachable from native apps, terminals, browsers, telnet BBS clients, newsreaders, offline mail readers, and (yes) real classic Hotline clients. Servers federate over Tunnels and are discoverable through Looking Glass directories.
Status: 0.268.0 — waves 0–14 largely landed. The native server and its
surfaces are feature-complete for a public flagship; remaining work is 1.0
hardening (E2EE wiring, cross-server flood-fill, GUI/mobile shells) and the
post-1.0 Reticulum mesh. See TODO.md for the exact per-wave state.
PLAN.md— the full-vision phased roadmapTODO.md— the authoritative wave-by-wave trackerdocs/— the documentation site (build withmdbook build docs): protocol spec, operator guides, and design recordsdocs/protocol/— the RabbitHole Protocol (RHP) specdocs/legacy-surfaces.md— the operator matrix for every legacy listenerdocs/research/— the research briefs behind the designexamples/flagship-burrow.toml— a fully-commented sample config
The native RHP server (burrow) is the heart of it: QUIC (primary) and
WebSocket (fallback) transports, accounts with Argon2id passwords, TOTP 2FA and
recovery codes, multiple personas per account, and an ACL evaluator (roles +
classes + capability bitmask, nearest-ancestor / deny-wins) governing every
surface.
| Subsystem | State at 0.268.0 |
|---|---|
| Accounts & identity | Ed25519 identity keys, Argon2id passwords, hashed session tokens + resume, TOTP + recovery codes, key enrollment, registration gating (open/invite/closed) |
| Personas & presence | Multiple personas per account, profiles/.plan/avatars+banners, buddy lists, presence states (away/idle/invisible), member directory + locate |
| Chat | Lobby + categorized public rooms, ad-hoc and private (invite-only) rooms, topics/subjects, kick/ban, per-room mute + slow-mode (W13) |
| Direct messages | Threads, offline queueing, quoting, away auto-response, privacy-gated receipts, content-addressed attachments |
| Boards | Category/bundle/board tree, posts as dual-signed blake3 events, threading, edit-as-event + tombstones, retention, read pointers; offline sync (cache, delta download, outbox) |
| Wishing Well | Request board: CRUD, voting, claim → fulfilled linkage, requester notifications |
| File libraries | Areas + folder trees, metadata/icons/ratings, drop boxes, aliases, background indexer + search, quotas + per-transfer bandwidth caps |
| Transfers | Ticketed resumable transfers on dedicated QUIC bulk streams (WS ranged-chunk fallback), whole-file + per-chunk Bao verification, folder pipelining, persistent client queue |
| The Warren (swarm) | Manifests + rabbit:// links, advertise-without-upload catalogs, server-signed capability tokens, Bao-verified peer wire, multi-source work-stealing scheduler with .rhstate resume, LRU/mirror cache policies (NAT hole-punching + WebRTC deferred) |
| Federation (Tunnels) | S2S QUIC endpoint, nonce-bound Ed25519 handshake + admin approval, signed file catalogs with pull fan-out + blake3 dedupe shipped; flood-fill, redaction propagation, and persona@server attestation are model-only crates awaiting the S2S service wiring |
| Moderation | Report queues, quarantine-for-review, blake3 hash-deny lists, audit trail; enforced on native RHP file/board/DM paths |
| Rate limiting | Token buckets across six classes (conn/auth/msg/post/transfer/legacy), per-IP + per-account, live-tunable, on by default |
| Backups | Consistent snapshot (ctl backup, or the console's Backups pane into backup_dir), verify, and offline burrow restore (stop → restore → start), migration notes in module docs |
| E2EE | Crypto core complete (rabbithole-e2ee: Double Ratchet, X3DH-lite, sealed sender, sender-key groups); per-thread DM/room wiring is pending (model-only) |
Clients & tooling
- SPA web client (
crates/ui-web, Leptos): files, boards, chat, DMs, directory, admin console, theme editor (live light/dark/retro preview), and a radio player — an installable PWA with an offline app-shell, served byburrow --http. - TUI clients (
rabbit-tui): chat/who, a radio now-playing panel with external-player handoff, and a Looking Glass server browser that opens onrabbithole.directory(INDEX/CATEGORIES/HEALTH on a named tracker, with an uptime sparkline). rabbitCLI: login (password/guest, QUIC or WS), chat, boards, files, swarm, transfer queue, wishing well,--jsonmode.looking-glasstracker: signed self-certifying descriptors, UDP gossip anti-entropy, categories, and a verifiable (not authoritative) health INDEX.warren-stampede(apps/loadgen): a load harness driving realburrowsessions (idle/chat/mixed scenarios, p50/p95/p99,--json).
Legacy surfaces — every one off by default, gated by *_enabled and a
*_min_role, and rate-limited:
- Telnet BBS — CP437/ANSI art pipeline, welcome screen, boards/chat/DM
shells,
/goteleport, a files browser (HTTP-link handoff or ZMODEM in/out with resume), and door games (DOOR32.SYS/DOOR.SYS/DORINFO1.DEF). - Finger (RFC 1288), NNTP (reader + peer-feed, NNTPS + STARTTLS),
Hotline (private rooms, admin transactions, HTXF upload/download with
fork-offset resume), FidoNet/binkp (tosser/scanner/AreaFix/nodelist),
RSS/Atom inbound syndication, and Icecast radio (ICY delivery + DJ
SOURCE ingest +
updinfometadata).
Model-only / deferred (present as tested crates, not yet wired to the wire):
rabbithole-reticulum (RNS/LXMF data model + crypto, 99 tests, no transport
— see docs/research/reticulum-decision.md);
E2EE thread wiring; federation flood-fill/attestation; Tauri desktop + mobile
shells. QWK offline-mail delivery is wired to the telnet [M] menu and ctl,
not to a standalone listener.
The shared desktop and web client now has personal appearance and sound controls in Settings: accent colours, chat font and text size, compact spacing, timestamps, profile icons in chat, reduced motion, and three chime voices with volume and preview. Changes are remembered on this device. System appearance follows light/dark changes live; High Contrast keeps its own palette.
In You → Your burrow profile, signed-in accounts can edit pronouns, location,
interests, a short quote and .plan, control directory visibility, and publish
one of 16 icons in eight colours. Save applies to the active persona on that
burrow; other personas and burrows keep their own profiles. The local identity
mark remains separate, with an explicit Use my local mark action for sharing
it as a public profile icon. Unsaved profile drafts survive navigation during the
current app session; Revert changes restores the saved profile.
On macOS, the Go menu exposes app destinations and Back/Forward; View → Appearance opens personal settings. Settings and navigation always target the main window, leaving About in its own window.
Native transports and rate limiting are the only things on out of the box;
every legacy listener is off by default. Listener addresses need a restart;
*_min_role gates apply live. Exact keys and semantics live in
docs/legacy-surfaces.md.
| Surface | Default port | Enable key | Default |
|---|---|---|---|
| Native RHP over QUIC | 4653 (quic_addr) |
(always on) | on |
| Native RHP over WebSocket | 4654 (ws_addr) |
(always on) | loopback only |
| S2S federation (Tunnels) | 4655 (federation_addr) |
federation_enabled |
off |
| Embedded HTTP + PWA | 8080 (http_addr) |
http_enabled / --http |
off |
| Telnet BBS (+ doors, files) | 2323 (telnet_addr) |
telnet_enabled |
off |
| Finger (RFC 1288) | 7979 (finger_addr) |
finger_enabled |
off |
| NNTP reader | 1119 (nntp_addr) |
nntp_enabled |
off |
| NNTPS reader (implicit TLS) | 563 (nntp_tls_addr) |
nntp_tls_enabled |
off |
| NNTP peer feed | 1120 (nntp_feed_addr) |
nntp_feed_enabled |
off |
| NNTP peer feed (implicit TLS) | 1563 (nntp_feed_tls_addr) |
nntp_feed_tls_enabled |
off |
| Hotline (+ HTXF on port+1) | 5500 / 5501 (hotline_addr) |
hotline_enabled |
off |
| FidoNet / binkp mailer | 24554 (ftn_addr) |
ftn_enabled |
off |
| Radio delivery (Icecast/ICY) | 8000 (radio_addr) |
radio_enabled or burrow --radio |
off |
Radio DJ source + updinfo |
8001 (radio_source_addr) |
radio_source_enabled |
off |
| RSS/Atom syndication | — (outbound fetcher) | syndication_enabled |
off |
| QWK/QWKE offline mail | — (telnet [M] + ctl) |
qwk_enabled |
off |
| Looking Glass tracker | 5498 TCP / 5499 UDP + 4656 UDP gossip; status INDEX on 4655 (public default). just up binds status to 5497 so it does not collide with federation 4655. |
(looking-glass daemon) |
— |
| Looking Glass announce | outbound HTTPS POST /api/announce |
announce_enabled (needs advertise_host) |
on, inert until a public host is set |
Federation also requires a restart-only federation_origin. This immutable
lowercase namespace is bound to the server key in protocol-v2 handshakes and
signed events; changing the display name does not change it. Peer approvals
are exact origin/key tuples, and relayed events require an independently
trusted origin binding. See docs/protocol/federation.md.
$ cargo run -p burrow -- run
INFO burrow: burrow is up quic=0.0.0.0:4653 ws=127.0.0.1:4654 fingerprint=<hex>
# Admin from another terminal (unix ctl socket, no network):
$ burrow ctl account-create alice wonderland user
$ burrow ctl config-set motd "Down the rabbit hole we go"
$ burrow ctl status
# Serve the web SPA + HTTP file links without touching burrow.toml
# (build the SPA once with `trunk build` in crates/ui-web):
$ burrow --http --web-root crates/ui-web/dist run
INFO burrow: http listening http=0.0.0.0:8080
# Sign in and chat:
$ rabbit login ws://127.0.0.1:4654 --user alice --password wonderland
$ rabbit say "oh my ears and whiskers"
$ rabbit who
$ rabbit tail # stream the lobby
# QUIC with certificate pinning (fingerprint from `burrow ctl status`):
$ rabbit login 127.0.0.1:4653 --fingerprint <hex> --server-name localhost --guestRaw ws:// carries passwords, TOTP codes, and resume bearer tokens, so the
built-in listener refuses non-loopback binds by default. Public browser access
must terminate TLS as wss:// at a reverse proxy, forward to the loopback
listener, set an exact HTTPS ws_allowed_origins allowlist, and publish the
external endpoint through ws_public_url. See
docs/deployment.md.
In the desktop and web connect window, Save sign-in to bookmark remembers the account after a successful sign-in. Different handles on the same burrow get separate bookmarks, each with its own name and saved sign-in. Selecting one lets you reconnect without entering a password; Use password instead lets you replace an expired sign-in. Only one account per burrow is active at a time. Passwords are never saved: the app keeps a revocable server-issued session token in this app/browser's local storage. Forget saved sign-in clears that bookmark's credential; Remove bookmark removes the bookmark and credential. Expired sessions keep the bookmark and handle, and ask you to sign in again.
Back up and restore (the server must be stopped before a restore):
$ burrow ctl backup ./snapshots/2026-07-03 # live, consistent snapshot
$ burrow ctl backup-verify ./snapshots/2026-07-03
$ burrow restore ./snapshots/2026-07-03 --data-dir ./burrow-data # offlineA fully-commented flagship configuration — legacy surfaces, federation, radio,
rate limits, and the HTTP/PWA surface all shown — lives in
examples/flagship-burrow.toml.
In rabbit-tui, Ctrl-R cycles Clean, Retro, and High Contrast; Ctrl-T
cycles Auto, Light, and Dark. Choices apply immediately and are saved in
rabbithole/tui.toml under the platform configuration directory. Use
--preferences FILE to choose another location. A save failure is reported
in the status line; the selected appearance still lasts for the session.
Auto reads the terminal's COLORFGBG hint at startup: standard ANSI background
indices 0–6 and 8 select Dark; 7 and 9–15 select Light. Missing, malformed, or
extended-color hints fall back to Dark. This is a terminal hint, not live OS
appearance detection; choose Light or Dark manually for a customized terminal
palette. High Contrast keeps its own colors even when a burrow publishes an
accent. The preference file also holds the radio delivery settings below;
sign-in passwords, session tokens, and player commands are not saved there.
Open Radio with Ctrl-N. Press b to edit the delivery base, then Enter
to save it for this burrow; an empty value clears it, and Esc cancels the edit.
Saved bases follow the burrow's identity and stay separate from other burrows.
They share the appearance preference file and its --preferences FILE override.
If saving fails, the valid new base works for this session and the status line
reports that it was not saved.
--radio-base https://radio.example.org overrides the saved base for this
session. Changing appearance does not save that override; saving with b then
Enter does. Without an override or saved base, the address starts unset.
Select a station and press Enter or p to show its URL. o opens it using
RABBIT_PLAYER, for example RABBIT_PLAYER="mpv --no-video". This environment
setting is read when opening the player and is never saved. Arguments are split
on whitespace and the URL is appended as one argument, without a shell. With
an unset or blank RABBIT_PLAYER, the URL remains available to copy.
$ cargo test --workspace # full suite
$ cargo clippy --workspace --all-targets
$ cargo fmt --all
$ cargo check -p rabbithole-proto -p rabbithole-core --target wasm32-unknown-unknownSee CONTRIBUTING.md. License: TBD (being decided —
see PLAN.md §16; until then, all rights reserved by the project owner).