Skip to content

Latest commit

 

History

431 Commits

Folders and files

Repository files navigation

RabbitHole banner

RabbitHole

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.

What works today

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 by burrow --http.
  • TUI clients (rabbit-tui): chat/who, a radio now-playing panel with external-player handoff, and a Looking Glass server browser that opens on rabbithole.directory (INDEX/CATEGORIES/HEALTH on a named tracker, with an uptime sparkline).
  • rabbit CLI: login (password/guest, QUIC or WS), chat, boards, files, swarm, transfer queue, wishing well, --json mode.
  • looking-glass tracker: signed self-certifying descriptors, UDP gossip anti-entropy, categories, and a verifiable (not authoritative) health INDEX.
  • warren-stampede (apps/loadgen): a load harness driving real burrow sessions (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, /go teleport, 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 + updinfo metadata).

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.

Make it yours

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.

Surfaces & default ports

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.

Try it

$ 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 --guest

Raw 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   # offline

A fully-commented flagship configuration — legacy surfaces, federation, radio, rate limits, and the HTTP/PWA surface all shown — lives in examples/flagship-burrow.toml.

Terminal appearance

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.

Terminal radio

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.

Development

$ cargo test --workspace          # full suite
$ cargo clippy --workspace --all-targets
$ cargo fmt --all
$ cargo check -p rabbithole-proto -p rabbithole-core --target wasm32-unknown-unknown

See CONTRIBUTING.md. License: TBD (being decided — see PLAN.md §16; until then, all rights reserved by the project owner).

Releases

Packages

Used by

Contributors

Languages