No description
  • TypeScript 81.1%
  • CSS 17.8%
  • Shell 0.5%
  • Dockerfile 0.4%
  • HTML 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Feror 3ea2708743
All checks were successful
Validate and publish container / validate (push) Successful in 13s
Validate and publish container / publish (push) Successful in 1m5s
ci: use scoped registry credentials and document runner requirements
2026-10-25 00:07:44 +02:00
.forgejo/workflows ci: use scoped registry credentials and document runner requirements 2026-10-25 00:07:44 +02:00
docs ci: use scoped registry credentials and document runner requirements 2026-10-25 00:07:44 +02:00
scripts ci: validate LFS assets and publish tested containers to Forgejo 2026-10-25 00:02:08 +02:00
src feat: integrate phase-aware game music with fades and media priority 2026-10-24 22:18:36 +02:00
.dockerignore chore: add production deployment, operator tools and validation guide 2026-10-24 20:05:51 +02:00
.env.example chore: add production deployment, operator tools and validation guide 2026-10-24 20:05:51 +02:00
.gitattributes feat: establish quiz contracts, persistent game engine, auth and brand assets 2026-10-24 19:29:55 +02:00
.gitignore feat: establish quiz contracts, persistent game engine, auth and brand assets 2026-10-24 19:29:55 +02:00
bun.lock feat: establish quiz contracts, persistent game engine, auth and brand assets 2026-10-24 19:29:55 +02:00
CLAUDE.md Initial Commit, project scaffolded 2026-10-24 18:52:27 +02:00
compose.yaml chore: add production deployment, operator tools and validation guide 2026-10-24 20:05:51 +02:00
Dockerfile feat: integrate phase-aware game music with fades and media priority 2026-10-24 22:18:36 +02:00
LICENSE chore: add production deployment, operator tools and validation guide 2026-10-24 20:05:51 +02:00
package.json feat: prepare and bundle the eight Buzzr game music tracks 2026-10-24 22:06:02 +02:00
README.md ci: validate LFS assets and publish tested containers to Forgejo 2026-10-25 00:02:08 +02:00
tsconfig.json fix: harden LAN play, role credentials and quiz validation 2026-10-24 20:03:06 +02:00

Buzzr

Good questions. Great company. An open-source, self-hosted live quiz contest built with Bun, React Router, WebSockets, BetterAuth and SQLite.

Create privately, publish when ready, then bring everyone into a room with a code or QR. Questions appear on participants' phones, answers are scored by the server, and the top ten take the stage between questions. English and French interfaces are included.

Run locally

Requires Bun 1.4.4 or newer and Git LFS. Install LFS before cloning so the music and brand images are downloaded:

git lfs install
git clone https://forge.feror.fr/feror/Buzzr.git
cd Buzzr
git lfs pull
bun install
bun run setup
bun run dev

Open http://localhost:3000. Use the invitation code from your generated .env when creating an account. To allow public creator registration, set REGISTRATION=open. Players never need accounts. No SMTP, Redis or external storage is required.

For another device to join, set PUBLIC_URL to the address reachable by every participant, such as http://192.168.1.20:3000 on a trusted LAN, and restart. QR codes use this public origin. Use HTTPS when serving publicly.

Create and play

  1. Create a quiz or import docs/examples/welcome.buzzr.json from the library.
  2. Add choice (two to four options, one or multiple correct), true/false, slider, typed-answer or two-pair matching questions. Configure time, scoring, media and explanations.
  3. Drafts autosave. Publish creates a validated playable snapshot; later draft edits do not affect published or running games.
  4. Host from the editor/library or create a reusable secret launch link for presentation slides. The link starts a fresh lobby without login. Anyone holding it can host that quiz; revoke links from the editor.
  5. Players join by QR or six-digit code and choose a nickname. Late arrivals start on the next question; the host can lock admission or remove players.
  6. Start, present the question, then open a three-second countdown. Question audio/video plays on the host screen, with answering opened after playback by the host. Autoplay handles text/image questions, reveal and standings; media playback still needs a host gesture.
  7. Review final standings and export per-answer CSV from Game reports. Reclaim unfinished host sessions from the same page.

One final answer is accepted per player per question. Scores use correctness and optionally speed; partial grading is available for choice, slider and matching. Correct answers are withheld until reveal. Equal scores share a rank. Hosts can pause, skip without points, or end early.

Portable quiz files

Export JSON from the editor. Files contain a versioned quiz definition and base64 media, with no accounts, reports or instance-specific secrets. The importer previews the title, question count and size before creating a new private draft.

  • Schema: docs/quiz.schema.json; runtime validation adds cross-field/reference checks.
  • Example: docs/examples/welcome.buzzr.json covers all five question types.
  • Limits: 200 questions, 200 media assets, 50 MiB per asset, 100 MiB total decoded media, 145 MiB encoded request. Base64 adds roughly one third to binary size. Large files need enough browser and server memory.
  • Formats: PNG/JPEG/WebP/GIF, MP3/OGG/WAV, MP4/WebM. Browser codec support still applies; H.264/AAC MP4 is the broadest video compatibility target. Media signatures are validated. SVG, remote URLs and embedded HTML are not accepted.
  • Media is deduplicated as SQLite blobs. Active game-state writes contain media references, not repeated binary payloads.

Deploy

bun run setup # if .env does not already exist
# Set PUBLIC_URL and optional Discord credentials in .env.
docker compose up -d --build

The named buzzr-data volume holds everything, including accounts and media. The single process must remain the sole writer serving this instance: SQLite game persistence does not provide multi-server coordination. Docker is optional:

bun run build
bun run start:production

Set a unique BETTER_AUTH_SECRET of at least 32 characters. Terminate HTTPS at your reverse proxy, preserve the public Host/Origin, allow WebSocket upgrades at /ws/*, set idle timeouts above 60 seconds, and allow uploads up to 145 MiB. Rate limits use the connection's IP; a reverse proxy gives clients a shared bucket, so tune deployment accordingly. Never expose the SQLite file as a static asset. /health reports readiness after migrations.

Discord sign-in is enabled only with both credentials. In the Discord developer portal register PUBLIC_URL/api/auth/callback/discord. No bot scope is needed. Invite-only registration applies to Discord as well. Password account emails are unverified; automatic cross-provider account linking is disabled. Phone-only Discord accounts without an email must use email/password instead.

Recovery and maintenance

Players and hosts reclaim their browser identity on reconnect; credentials are stored in that browser. A host absent for 30 seconds pauses the game, including autoplay. Following a server restart, unfinished games are paused. An interrupted answering phase restarts that question without its unscored submissions; previously awarded points survive. A creator can reclaim host control through Game reports, rotating the prior host credential.

There are no password-reset emails. An operator with database access can reset a password account using a temporary environment variable:

read -s BUZZR_NEW_PASSWORD
export BUZZR_NEW_PASSWORD
bun scripts/reset-password.ts creator@example.com
unset BUZZR_NEW_PASSWORD

The command revokes existing creator sessions. Use your shell's private/history controls for maintenance. To take a consistent backup including media and authentication:

bun scripts/backup.ts /safe/location/buzzr-backup.sqlite

For Docker, run the same script through docker compose exec with a target under /data, then copy the backup out. To restore, stop Buzzr, preserve the current database, replace it with the backup, remove stale -wal/-shm sidecars while stopped, and restart. Do not copy only a live .sqlite file while WAL writes are running.

Completed reports expire after REPORT_RETENTION_DAYS (default 90); abandoned lobbies expire after 24 hours. Creators can delete completed reports manually. Backups may retain deleted information.

Design and music

src/assets/brand/IDENTITY.md documents the palette, self-hosted typography, accessibility and component direction. The generated emblem and game-piece illustration live alongside their original prompts in GENERATION.md. Fontsource includes the fonts' OFL license files in its packages.

src/assets/music/ includes all eight supplied Suno tracks as level-matched, web-ready MP3s. The host enables music with the Sound button; volume is remembered. Lobby, answering, leaderboard, podium and intermission beds loop, with 350 ms phase fades. Countdown, urgency and reveal cues are edited to 3, 5 and 6 seconds; the game clock remains authoritative. Late short cues seek forward instead of overrunning their window. Playing question audio/video silences music, which resumes afterward. Missing tracks never block play.

See src/assets/music/MUSIC.md for the source mapping, exact edits, loudness and regeneration instructions, and SUNO-PROMPTS.md for the original briefs. Prepared files ship in both direct Bun and Docker deployments; FFmpeg is needed only to regenerate them.

Development

Binary images, audio, video and font files are tracked with Git LFS through .gitattributes. Commit them normally after installing LFS; keep editable text assets such as SVG, JSON and prompts in ordinary Git. Existing clones should run git lfs install and git lfs pull. CI fetches and verifies LFS content before building.

Forgejo validates pull requests and publishes tested linux/amd64 container images from main and v* tags to forge.feror.fr/feror/buzzr. See container publishing for tags, runner requirements and deployment.

bun run typecheck
bun run test
bun run build
bun run test:load
bun run test:production
bun run format
# Optional: regenerate music from your original WAV folder (requires FFmpeg/FFprobe)
bun run music:prepare /path/to/wavs

Unit/integration tests use an in-memory SQLite database. The load smoke test starts an isolated server and temporary database, creates three games with 100 real WebSocket players each, and checks acknowledged answers, scores and p95 latency. It removes its data on exit. test:production repeats the same checks against the built production launcher and verifies the frontend HTML/JavaScript assets. This is a local capacity check, not a hosted network or memory-soak certification.

The four workspaces are @buzzr/client, @buzzr/server, @buzzr/common, and @buzzr/assets. Domain contracts and runtime schemas live in common; UI and transport adapters stay out of it so a future React Native player can share the game protocol. Details and protocol rules are in docs/ARCHITECTURE.md.

Licensed under MIT. Contributions welcome.