Skip to main content

Development Setup

Collection of info about the codebase environment, setup and design choices.

Dev environment

Option A - Dev Container

Open in VS Code with the Dev Containers extension, then run Dev Containers: Reopen in Container. Dependencies install automatically, .env.example is copied to .env.local, and the database is seeded. Run pnpm dev and open http://localhost:3000.

Option B - Local Setup

Prerequisites: Node.js ≥ 20, pnpm, a C++ build toolchain (only if better-sqlite3 has no prebuilt for your platform).

pnpm install
cp .env.example .env.local # set BETTER_AUTH_SECRET at minimum
pnpm db:migrate
pnpm db:seed
pnpm dev

Open http://localhost:3000 and log in with the credentials from SEED_ADMIN_* in .env.local.

[!TIP] Most env vars are optional for local dev - the app degrades gracefully. BETTER_AUTH_SECRET is the only one you must set. Generate it with openssl rand -hex 32.

Commands

CommandDescription
pnpm devStart the dev server with hot reload
pnpm buildProduction build
pnpm startStart the production server (requires a prior build)
pnpm lintRun ESLint
pnpm formatAuto-format all files with Prettier
pnpm format:checkCheck formatting without writing (used in CI)
pnpm db:generateGenerate migrations from schema changes
pnpm db:migrateApply all pending migrations
pnpm db:seedSeed the 6 teams + admin user (idempotent)
pnpm db:resetDrop and recreate the local database (blocked in production)
pnpm db:studioOpen Drizzle Studio - visual database browser (dev only)
pnpm testRun the unit test suite once
pnpm test:watchRe-run tests on save (use while developing)
pnpm test:uiOpen the Vitest browser UI
pnpm test:coverageGenerate a coverage report

CI & Git Hooks

GitHub Actions

Three workflows run automatically:

WorkflowTriggerWhat it does
Code QualityPush / PR → mainESLint, Prettier check, TypeScript check
TestsPush / PR → mainVitest unit test suite
DeployPush → mainPulls latest and runs scripts/deploy.sh on the self-hosted server

Both Code Quality and Tests must pass before a PR can be merged (branch protection on main).

Git Hooks (Husky)

HookWhenWhat it does
pre-commitEvery commitRuns lint-staged — ESLint + Prettier on staged files only
commit-msgEvery commitRuns commitlint — enforces Conventional Commits format
pre-pushEvery pushRuns the full test suite — blocks the push if any test fails

Commit messages must follow the type: description format, e.g. feat: add order export, fix: correct balance calculation. Valid types: feat, fix, chore, docs, style, refactor, test, ci.

Environment Variables

VariableRequiredDescription
BETTER_AUTH_SECRETYesSession signing secret - openssl rand -hex 32
VAULT_ENCRYPTION_KEYProd onlyAES-256 key for vault secrets - openssl rand -hex 32
NEXT_PUBLIC_APP_URLNoPublic origin, e.g. http://localhost:3000
BETTER_AUTH_URLNoSame as NEXT_PUBLIC_APP_URL
BETTER_AUTH_TRUSTED_ORIGINSNoExtra origins allowed to make auth requests (needed when accessing the dev server from a second machine on the LAN)
DATABASE_PATHNoPath to the SQLite file (default: ./db/dashboard.db)
RESEND_API_KEYNoResend API key for transactional email
EMAIL_FROMNoSender address, e.g. TrickFire Robotics <[email protected]>
MINECRAFT_SERVER_HOSTNoMinecraft server hostname/IP (default: localhost)
MINECRAFT_SERVER_PORTNoMinecraft query port (default: 25565)
MINECRAFT_SERVER_PATHNoAbsolute path to the Minecraft server directory
MINECRAFT_WORLD_PATHNoAbsolute path to the world directory (for the playtime leaderboard)
MINECRAFT_RCON_PORTNoRCON port (default: 25575)
MINECRAFT_RCON_PASSWORDNoRCON password
MINECRAFT_BOT_NAMESNoComma-separated carpet bot names, optionally with a skin URL: BotA:https://skin-url.png
PL3XMAP_URLNoInternal URL of the Pl3xMap web server, e.g. http://localhost:8080
TAILSCALE_API_KEYNoTailscale API key (admin console → Settings → Keys)
TAILSCALE_TAILNETNoTailnet name, or - to auto-detect from the API key
ONSHAPE_BASE_URLNoOnShape API base URL
ONSHAPE_ACCESS_KEYNoOnShape access key
ONSHAPE_SECRET_KEYNoOnShape secret key
ONSHAPE_COMPANY_IDNoOnShape company ID (auto-detected from the access key if omitted)
SEED_ADMIN_EMAILNoEmail for the seeded admin account
SEED_ADMIN_PASSWORDNoPassword for the seeded admin account
SEED_ADMIN_NAMENoDisplay name for the seeded admin
GITHUB_ORGNoGitHub organization name (e.g. trickfirerobotics) — enables the GitHub admin page
GITHUB_TOKENNoFine-grained PAT with Organization → Members: Read and write permission
NEXT_PUBLIC_SENTRY_DSNNoSentry DSN — errors are silently dropped when unset
SENTRY_AUTH_TOKENBuildSentry auth token for source map uploads — only needed during pnpm build
SENTRY_ORGBuildSentry org slug — only needed during pnpm build
SENTRY_PROJECTBuildSentry project slug — only needed during pnpm build

[!CAUTION] Never commit .env.local or .env.production. BETTER_AUTH_SECRET lets anyone forge session tokens - if it leaks, rotate it immediately by changing the value and restarting the server (all existing sessions are invalidated). Rotating or losing VAULT_ENCRYPTION_KEY makes every existing vault entry permanently unrecoverable - back it up.

API Key Vault

The API Keys page is a shared credential vault where admins store third-party API keys and service logins. Two layers of access apply:

  • Page visibility - the Vault access toggle on the Users admin page controls who can open the vault at all (admins always can).
  • Per-secret access - reading any individual secret requires a per-person grant set from the entry's Manage access action. The global Vault-access toggle does not grant secret access on its own.

Each entry is either a login (username + password, revealed in the browser on demand) or an api_key (never shown in the UI - retrieved only via the API endpoint below).

Fetching an API key - GET /api/vault/{id}/key

Authenticated by the caller's dashboard session cookie. Returns the key only if the user is an admin or has been granted access.

StatusMeaning
200{ "name": "Resend", "key": "re_live_..." }
401Not logged in
403Logged in but no access grant for this entry
404Entry doesn't exist, or it is a login (use GET /api/vault/{id}/reveal instead)
curl -s https://dashboard.trickfirerobotics.com/api/vault/12/key \
-H "Cookie: better-auth.session_token=<your-session-cookie>"

Secrets are encrypted at rest with AES-256-GCM using VAULT_ENCRYPTION_KEY. In local dev, if that variable is unset, a key is auto-generated and saved to db/vault.key (gitignored).