# Sealed-cookie session encryption (32+ char random string) # Generate: openssl rand -base64 32 # (Server-only, read from process.env — never PUBLIC_. Port of NUXT_SESSION_PASSWORD.) # Optional — without it a random per-instance secret is used and sessions reset on every restart/new instance; set it (32+ chars) for real deployments. SESSION_PASSWORD= # Demo mode — lets /login mint a fake-user session without configuring # OAuth or a DB. Auto-on only when NODE_ENV=development (local `npm run dev`); # off everywhere else, including previews and unset NODE_ENV. # true = always on (set explicitly on a deployment meant as a public demo) # false = always off (recommended for real production) # unset = auto (isDemoMode() in src/lib/server/env.ts) # SECURITY: while on, anyone can POST /auth/demo and get an ADMIN session. DEMO_MODE= # GitHub OAuth App credentials (arctic — port of nuxt-auth-utils GitHub handler) # Create at: https://github.com/settings/applications/new # Authorization callback URL: http://localhost:5173/auth/github/callback GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= # Postgres (Neon, Supabase pooler, or local) DATABASE_URL= # Public site URL — used for OAuth redirects + emails # (SvelteKit: PUBLIC_ prefix exposes to the client via $env/dynamic/public) PUBLIC_SITE_URL=http://localhost:5173 # Bootstrap admin logins (comma-separated GitHub usernames). # Matched users are CREATED as role='admin' on their first OAuth sign-in. # This only affects the initial row — once a user exists, the DB is the # source of truth and this env is ignored (so demotions stick). # Leave empty in production unless you actually want a new env-driven # admin to land on next first-sign-in. INITIAL_ADMIN_LOGINS= # Axiom — structured log shipping (free tier: 500GB/mo). # Sign up at https://app.axiom.co. Create a dataset, then an API token # scoped to that dataset. # Both must be set together (or both left empty — boot fails on partial). # Leave empty in dev; the logger falls back to console-only stdout. AXIOM_TOKEN= AXIOM_DATASET= AXIOM_ORG_ID= # Sentry — error monitoring + session replay. # Sign up at https://sentry.io. Create a project, copy the DSN (public). # DSN unset = @sentry/sveltekit never initialized. Leave empty in dev # unless you want errors to flow. # PUBLIC_SENTRY_DSN is read at runtime via $env/dynamic/public — no rebuild # needed after changing it, and leaving it unset never fails the build. # SENTRY_AUTH_TOKEN / SENTRY_ORG / SENTRY_PROJECT are validated by # src/lib/server/env.ts but not used yet: @sentry/vite-plugin is not # installed, so no sourcemaps are uploaded. Reserved for that. PUBLIC_SENTRY_DSN= SENTRY_AUTH_TOKEN= SENTRY_ORG= SENTRY_PROJECT= # PostHog — product analytics (free tier: 1M events/mo). # Both values are public by design — PostHog's client SDK is the one # sending events, so the project key lives in the browser. Empty key = # tracking helper no-ops + posthog-js never initializes. # HOST: 'https://us.i.posthog.com' (default) or 'https://eu.i.posthog.com'. PUBLIC_POSTHOG_KEY= PUBLIC_POSTHOG_HOST= # Resend — transactional email (free tier: 100 emails/day, 3000/mo). # Sign up at https://resend.com. Verify your sending domain first. # Without RESEND_API_KEY the mailer console-prints emails to the dev # server log instead of sending — fine for development. # EMAIL_FROM must be a verified sender on your Resend account; # 'onboarding@resend.dev' is allowed for local testing only. # EMAIL_OPS is where in-app forms (feedback, alerts) deliver to. If # unset, those messages route to EMAIL_FROM so they don't go to a # random address. RESEND_API_KEY= EMAIL_FROM= EMAIL_OPS= # Polar — billing / checkout / subscriptions. # Sign up at https://polar.sh. Create an organization, then a product # for each plan tier you want to offer. Mint an access token under # Settings → Developers → New token (scope: products:read, checkouts: # write, customer_sessions:write, customers:read). # Configure a webhook pointing to /api/webhooks/polar and # copy the signing secret. # Both ACCESS_TOKEN and WEBHOOK_SECRET must be set together (boot fails # on partial config). Without them all /api/billing/* endpoints return # an instructive INTERNAL error. # POLAR_SERVER toggles between Polar's sandbox and production # environments. Use sandbox while testing — sandbox checkouts don't # charge real money and have a separate token + product catalog. POLAR_ACCESS_TOKEN= POLAR_WEBHOOK_SECRET= POLAR_SERVER=sandbox POLAR_PRO_PRODUCT_ID= POLAR_TEAM_PRODUCT_ID= POLAR_ENTERPRISE_PRODUCT_ID= # --------------------------------------------------------------------------- # Runtime & tooling — normally set by your host / CI, not in .env. # Listed for completeness; leave commented unless you know you need them. # --------------------------------------------------------------------------- # NODE_ENV: read by src/lib/server/env.ts (secure cookies; demo mode is auto-on # only when NODE_ENV=development, off for any other value or when unset) # and hooks.server.ts (Sentry environment). `vite build` / production hosts # set it to `production`; don't set it here. # NODE_ENV= # Self-hosted adapter-node (`node build`) runtime vars — not needed on Vercel. ORIGIN is required behind a # proxy or on a custom domain so SvelteKit's CSRF check accepts form posts. # ORIGIN=https://your-domain.com # PORT=3000 # HOST=0.0.0.0 # Playwright (playwright.config.ts): BASE_URL points e2e at an already-running # server (skips starting `vite dev`); CI enables retries + forbids `.only`. # BASE_URL= # CI=