π’ Competition leaderboard for BNP Paribas Fortis DevOps Day β tracks account scores across Immersive Labs security challenges, trivia, code duels, and physical games.
Single-process Node/Express service that proxies the Immersive Labs API, aggregates per-team scores, and serves a React dashboard for live display (TV wall or laptop).
- Format: per-team leaderboard β 30 rows, 1 fresh Immersive Labs account per team.
- Scoring:
Account.pointstrusted directly. Teams receive new accounts atEVENT_START_AT, so lifetime points = event points by construction. - Tie-break: points desc β
lastActivityAtasc (earlier finisher wins) βdisplayName. - Event scoping:
EVENT_START_AT/EVENT_END_ATdrive phase (pre/live/ended) and post-event freeze (stop rebuilds, keep last snapshot). Not used for attempt filtering. - Refresh: client polls every 30 s; server caches ~10 s. Snapshot persisted to disk so restarts serve stale instantly.
- Language: English only (UI copy independent of browser/account locale).
Browser (React + Vite)
β SSE push + 30 s poll fallback
βΌ
Node/Fastify (port 3000)
βββ /api/leaderboard β cached aggregate
βββ /api/leaderboard/stream β SSE (leaderboard-updated)
βββ /api/announcement β public banner read
βββ /api/admin/* β cookie-auth (bonuses, announcement, CSV)
βββ syncService β OAuth2 + paginated walk
βΌ
Immersive Labs API (https://api.immersivelabs.online)
Persistence: snapshot.json + token.json + bonus.sqlite (named volume)
See:
- docs/project.md β goals, API details, caveats.
- docs/dashboard-plan.md β frontend + backend architecture.
- docs/data-flow.md β aggregation rules, security invariants.
- docs/V1V2Scope.md β scope split.
- docs/implementation/ β module-level notes.
- TODO.md β open questions blocking build.
- Backend: Node 20+, Fastify, native
fetch. - Frontend: React + Vite (served as static bundle by the same Node process).
- Persistence: in-memory snapshot (10 s TTL) backed by
snapshot.json+token.json(atomic tmp + rename) plusbonus.sqlite(better-sqlite3, WAL) for admin bonuses + announcement, all on a Docker named volume. - Realtime: SSE channel
leaderboard-updated; 30 s client poll as fallback. - Deploy: single Docker image, port 3000, named volume mounted at
/app/data.
All config via environment variables. Secrets live only in the backend β never shipped to the browser.
| Variable | Purpose |
|---|---|
USE_STUB_UPSTREAM |
true β serve synthetic data, skip Immersive Labs calls. |
IMMERSIVELAB_ACCESS_KEY |
OAuth2 username for POST /v1/public/tokens. |
IMMERSIVELAB_SECRET_TOKEN |
OAuth2 password. |
IMMERSIVELAB_BASE_URL |
Upstream base URL (default https://api.immersivelabs.online). |
EVENT_START_AT |
ISO 8601 β drives phase = "pre" + pre-event gate. |
EVENT_END_AT |
ISO 8601 β drives phase = "ended" + post-event freeze. |
DATA_DIR |
Directory for snapshot.json + token.json + bonus.sqlite (default ./data; named volume in prod). |
PORT |
HTTP listen port (default 3000). |
SNAPSHOT_TTL_MS |
Aggregator cache TTL (default 10000). |
ADMIN_PASSWORD |
Required. Shared admin password for /admin. |
ADMIN_SESSION_SECRET |
Required. β₯ 32 random chars; HMAC key for the gg_admin signed cookie. |
ADMIN_SESSION_TTL_MS |
Admin cookie lifetime (default 172800000 = 48 h). |
BONUS_DB_PATH |
Override SQLite path (default ${DATA_DIR}/bonus.sqlite). |
COOKIE_SECURE |
Set false when running over plain HTTP (default true). |
# install
npm install
# run (reads .env)
npm run dev # backend + Vite dev server
npm run build # static bundle into dist/
npm start # production single-process
# docker
docker build -t greengauntlet .
docker run -p 3000:3000 -v greengauntlet-data:/app/data --env-file .env greengauntletA previous per-account leaderboard exists at devops-day-leaderboard (Immersive Lab Leaderboard v2.1). Reusable modules: immersiveLabsAuth.js, immersiveLabsClient.js, syncService.js, SQLite schema. Known gotchas: use ?? (not ||) for totalDuration; tolerate 404 on activity lookup.
v1.1 shipped on develop. Live features:
- Per-team aggregated leaderboard with SSE push (
leaderboard-updated) + 30 s client poll fallback. - Stub upstream mode for offline / pre-credential development (
USE_STUB_UPSTREAM=true). - Admin bonus panel β three categories (Mario / Crokinole / Helping), batch commit with per-category negative-guard, per-team active toggle, CSV export. See docs/implementation/admin-bonus-plan.md.
- Admin announcement banner β admin-managed message shown on the public dashboard, max 280 chars, dismissible per client (re-shows when a new message is posted).
- Mario theme toggle, per-category leaderboard tabs, team search.
- Persistence:
snapshot.json+token.json+bonus.sqliteon a named Docker volume.
See TODO.md for v2 / nice-to-haves.
Internal β BNP Paribas Fortis DevOps Day.