# LMS login and navigation LMS_URL=https://lmsug23.iiitkottayam.ac.in/ MOODLE_USERNAME= MOODLE_PASSWORD= CLASS_PAGE_URL= JOIN_LINK_TEXT=Join Online Class # Browser behavior HEADLESS=true KEEP_BROWSER_OPEN=false POST_CLICK_WAIT_MS=20000 # Worker SCHEDULE_CONFIG_PATH=config/schedule.json # Base64-encoded schedule JSON. If set, takes precedence over SCHEDULE_URL and config file. SCHEDULE_B64= # Optional remote schedule URL (e.g. a GitHub gist raw URL). If set, the worker polls it and # hot-reloads the schedule live without a restart. Invalid/unreachable URLs keep the last-good # schedule, so you can edit the schedule from a phone browser while traveling. SCHEDULE_URL= # How often to poll SCHEDULE_URL, in seconds. 0 disables polling after the initial fetch. SCHEDULE_URL_POLL_SECONDS=300 # Timeout for each SCHEDULE_URL fetch, in milliseconds. SCHEDULE_URL_TIMEOUT_MS=15000 # Path where the last successfully fetched remote schedule is cached (survives restarts). SCHEDULE_URL_CACHE_PATH=data/schedule-cache.json ENGINE_TICK_MS=5000 INTERNAL_API_PORT=8787 # Docker Compose sets INTERNAL_API_HOST and WORKER_INTERNAL_URL automatically. DRY_RUN=false # After handing off to the user, Admiral blocks auto-rejoin for this many # seconds (per slot) so it doesn't flap join/leave every ~90s while the user # is in the BBB app with the PWA backgrounded. Force-join still bypasses it. HANDOFF_GRACE_SECONDS=240 # ── Room watch: empty-room detection + room sweep ── # (Added after the 2026-07-30 stale-schedule incident, where the bot sat alone # in an empty room for hours without anyone noticing.) # Master switch for empty-room detection. EMPTY_ROOM_DETECTION_ENABLED=true # Below this headcount a room counts as empty. The count includes Admiral # itself, so alone-in-a-room reads 1; classes usually have >= 3 people. EMPTY_ROOM_MIN_PARTICIPANTS=3 # Don't evaluate emptiness for this long after entering any room (people # trickle in, classes start late). EMPTY_ROOM_GRACE_SECONDS=300 # The count must stay below the threshold continuously for this long before # Admiral acts (avoids flapping on transient dips). EMPTY_ROOM_CONFIRM_SECONDS=300 # When the scheduled room stays empty, Admiral leaves and probes the other # configured course rooms, adopting the first with enough people. If none # qualifies it rejoins the scheduled room (attendance-safe) and re-sweeps # after this many seconds, until the slot ends. ROOM_SWEEP_RETRY_SECONDS=900 # Safety cap on sweeps per scheduled slot. ROOM_SWEEP_MAX_PER_SLOT=6 # Settle time inside a probed room before counting heads (people render async). ROOM_SWEEP_PROBE_SETTLE_SECONDS=25 # Participant-count time series powering /participant-stats: how often to # sample while in a room, and how long to keep samples. PARTICIPANT_SAMPLE_SECONDS=300 PARTICIPANT_SAMPLE_RETENTION_DAYS=14 # ── Slot overtime: stay past the scheduled end while the class is still live ── SLOT_OVERTIME_ENABLED=true SLOT_OVERTIME_MAX_SECONDS=600 # Consecutive scrapes (~10s apart) below the headcount threshold before the # hold is dropped as "class over" (guards against a one-scrape transient dip). SLOT_OVERTIME_EMPTY_SCRAPES=3 # ── Overrun crossing grace: next class started while this room is STILL live ── # When the next scheduled class starts but the current room is clearly still in # session (teacher running over into the next slot), Admiral keeps holding the # overrunning room for this long (bounded) to catch late attendance, then # switches to the next class. One browser = one room, so this is a deliberate # trade-off; it never delays the next class beyond OVERRUN_GRACE_SECONDS. OVERRUN_GRACE_ENABLED=true OVERRUN_GRACE_SECONDS=600 # The overrun room is only treated as "still on" while its headcount is at or # above this many people (independent of EMPTY_ROOM_MIN_PARTICIPANTS): no way # this many people are sitting in a class that's over. OVERRUN_GRACE_PARTICIPANTS=5 # SQLite database location for durable worker state (standdowns, heartbeats, history). # In Docker this lives on the admiral_data volume mounted at /app/data. DATABASE_PATH=data/admiral.db # Public API auth PUBLIC_API_PORT=8080 ADMIRAL_DOMAIN=adm.akshatsaraswat.in ADMIRAL_ACCESS_TOKEN= SESSION_SECRET= SESSION_TTL_SECONDS=604800 COOKIE_SECURE=true # Notifications (Resend). Emails are coalesced and budget-gated against # Resend's 100/day and 3000/month limits (IST days). No per-session caps: # every real cover_resume / handoff occurrence sends (frequent flapping inside # the settle window still folds into one email). RESEND_API_KEY= RESEND_FROM= RESEND_TO= # Email budget (defaults shown). Raised to Resend's practical limits after the # overrun/overtime hold emails were added; the same account feeds the watchdog. EMAIL_DAILY_CAP=100 EMAIL_MONTHLY_CAP=3000 EMAIL_P1_DAILY_CAP=80 EMAIL_P2_DAILY_CAP=60 EMAIL_SETTLE_SECONDS=120 EMAIL_QUIET_MODE=true # Scheduled daily emails (IST). Morning plan + 4pm wrap-up. EMAIL_MORNING_PLAN_HOUR=9 EMAIL_MORNING_PLAN_MINUTE=0 EMAIL_WRAPUP_HOUR=16 EMAIL_WRAPUP_MINUTE=0