Configuration¶
Configuration is split across two gitignored files:
.env— read bydocker composeitself. HoldsPOSTGRES_PASSWORD; compose uses it for thedbservice and to buildDATABASE_URL..env.mirror— the app's own secrets (loaded by both the API and the daemon).
Environment Variables¶
| Variable | Where | Default | What It Does |
|---|---|---|---|
POSTGRES_PASSWORD |
.env (compose) |
(none) | password for the db service; compose folds it into DATABASE_URL |
DATABASE_URL |
app | (compose sets it) | Postgres connection string. Under compose it points at the db service; set it yourself only for a non-compose venv run |
PROD_USERNAME / PROD_PASSWORD |
daemon | (none) | auth to the prod server (unrelated to the dashboard's own bot gate) |
VEX_PROD_API_BASE |
daemon | https://inviteinstitutehub.org |
prod server base URL |
TURNSTILE_SECRET |
API | (none) | Cloudflare Turnstile secret key, verified against the public site key baked into the frontend build |
SESSION_SECRET |
API | insecure dev default | signs the "this browser solved Turnstile" cookie; set a long random value in any real deployment |
CORS_ORIGINS |
API | http://localhost:3000,http://localhost:5173 |
allowed dashboard origins (dev only; prod is same-origin) |
PIPELINE_INTERVAL |
daemon | 0.5 |
base seconds per tick while events are flowing |
PIPELINE_IDLE_MAX |
daemon | 5.0 |
idle-backoff ceiling (how far the poll gap stretches when it's quiet) |
PIPELINE_PAGE_LIMIT |
daemon | 500 |
events fetched per page |
PIPELINE_BACKFILL_HOURS |
daemon | 24 |
on the first run only, how far back the initial drain goes (<= 0 = replay all history) |
PIPELINE_REQUIRE_VIEWER |
daemon | 0 (dev) / 1 (prod compose) |
arms the per-board dead-man's switch (below) |
Access¶
The dashboard is public — reachable by anyone who knows a board's student IDs — so
there's no login. The only gate is Cloudflare Turnstile, which keeps bots (not
people) off the API: a browser solves the widget once, and success is remembered via
a signed cookie for 12 hours (app/turnstile.py). Losing/clearing that cookie just
means solving the widget again, not signing back in — there's nothing to log out of.
Each browser is isolated into its own board via a persistent id it stores in
localStorage and sends as X-Board-Id; a request with no board id falls back to
one shared default workspace.
CLI Flags (daemon)¶
The daemon's settings are also flags, and a flag wins over the matching env var:
python -m app.pipeline --interval 1 --idle-max 8 --backfill-hours 2
The full set is --interval, --idle-max, --limit (events per page), --overlap
(cursor overlap seconds), --backfill-hours, and --require-viewer. Run
python -m app.pipeline --help for the inline reference.
Polling And Idle Backoff¶
While students are active, the daemon polls every PIPELINE_INTERVAL seconds. When
nothing's happening, it backs off exponentially toward PIPELINE_IDLE_MAX (so
0.5 → 1 → 2 → 4 → 5s) instead of hammering prod, and snaps back to fast the moment
activity returns.
Tip
Poll load tracks event volume, not roster size — and a student watched by several boards is still polled only once (the daemon ingests the union of all boards' rosters into one shared mirror).
The Dead-Man's Switch¶
Per board: the dead-man's switch stops prod polling for a board while nobody is watching it.
With --require-viewer (or PIPELINE_REQUIRE_VIEWER=1, the prod default) the daemon
only polls a board's students while that board's dashboard is actually open.
It works off a heartbeat. The read API stamps a per-board viewer_last_seen while a
dashboard holds the live stream open (on connect and every ~10s) and on each grid
fetch; the frontend closes the stream when its tab is hidden. Each tick the daemon
polls prod only for the students on live boards (polling enabled and a fresh
viewer). The staleness window is the VIEWER_PRESENT_SECONDS constant in
app/constants.py (90 seconds).
In the prod compose file the daemon runs with PIPELINE_REQUIRE_VIEWER=1, so a
served deployment stops hitting prod for any board whose dashboard is closed; dev
leaves it off so a local run polls as soon as a board has students.