fragJulia
Dev

Configuration System

Env var inventory across web (Vercel), voice (EC2), and mobile (EAS). Security classification, cron definition, rotation runbook, known gaps.

Status: Current as of 2026-04-18 (repo commit 06d84cb). Audience: New contributors setting up a dev environment; ops engineer rotating secrets; BfArM reviewer (security classification).

This document enumerates every environment variable across the three fragJulia runtimes, flags .env.example gaps, and classifies each secret by exposure scope.


1. Runtimes at a glance

RuntimePlatformConfig source
WebVercel (Next.js 16)Vercel dashboard env + apps/web/.env.example for dev
VoiceEC2 g6.xlarge (eu-central-1)/root/knotencheck-voice.env + IAM instance role (Bedrock)
MobileExpo / EASapps/mobile/eas.json env per channel (dev / preview / production)

Cron jobs: apps/web/vercel.json defines one β€” /api/chat/cleanup at 0 3 * * * UTC, guarded by CRON_SECRET.


2. Web (Vercel) β€” 22 vars in apps/web/.env.example

Exposure legend:

  • πŸ”’ server-only β€” never reach the browser
  • 🌐 client-exposed β€” prefixed NEXT_PUBLIC_*, visible in client bundle

Supabase (4)

VarScopeRequiredPurpose
NEXT_PUBLIC_SUPABASE_URLπŸŒβœ…Project REST endpoint
NEXT_PUBLIC_SUPABASE_ANON_KEYπŸŒβœ…Client-side anon role
SUPABASE_SERVICE_ROLE_KEYπŸ”’βœ…Admin / RLS-bypass (used by audit logger, cron cleanup, admin routes)
SUPABASE_JWT_SECRETπŸ”’β“Present in .env.example but no active process.env.SUPABASE_JWT_SECRET consumer as of last sweep β€” verify before removing

Site URLs (2 + 1 optional)

VarScopeRequired
NEXT_PUBLIC_SITE_URLπŸŒβœ…
NEXT_PUBLIC_APP_URLπŸŒβœ…
NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL🌐dev-only (auth callback on localhost)

Stripe (4)

VarScopeRequired
STRIPE_SECRET_KEYπŸ”’βœ… (prod: sk_live_*)
STRIPE_WEBHOOK_SECRETπŸ”’βœ… β€” signature verification of /api/stripe/webhook
STRIPE_PRICE_PLUS_MONTHLYπŸ”’βœ…
STRIPE_PRICE_PREMIUM_MONTHLYπŸ”’βœ…

Resend (1)

VarScopeRequired
RESEND_API_KEYπŸ”’βœ… β€” transactional + newsletter email

LiveKit β€” web caller (3)

VarScopeRequired
LIVEKIT_URLπŸ”’βœ… β€” WebRTC gateway (wss://)
LIVEKIT_API_KEYπŸ”’βœ…
LIVEKIT_API_SECRETπŸ”’βœ… β€” used to mint session tokens for voice pipeline

Knotencheck test mode (2)

VarScopeRequired
KNOTENCHECK_TEST_SECRETπŸ”’β€” test bypass
NEXT_PUBLIC_KNOTENCHECK_TEST_SECRETπŸŒβ€” must match server value

Security note: this pair intentionally lets a test UI bypass auth. Do not enable in a production-public environment. Consider gating on NODE_ENV !== 'production'.

Upstash Redis (rate limits) (2)

VarScopeRequired
KV_REST_API_URLπŸ”’βœ…
KV_REST_API_TOKENπŸ”’βœ…

Security (2)

VarScopeRequired
CRON_SECRETπŸ”’βœ… β€” cron route guard
HMAC_SECRETπŸ”’βœ… β€” signed-link / webhook payloads

Known gaps (still missing from .env.example as of 2026-04-18)

  • Turnstile (planned in #358): TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY
  • Bedrock (planned in #358): AWS_REGION=eu-central-1 + access via IAM role for web runtime, or explicit AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY if not using instance role
  • OpenAI (used today): OPENAI_API_KEY is implicit (AI SDK reads it from env) but not documented in .env.example β€” fix.

3. Voice (EC2) β€” 5 vars in voice/.env.example

Live file: /root/knotencheck-voice.env on the EC2 host. Committed example: voice/.env.example.

VarRequiredPurpose
LIVEKIT_API_KEYβœ…Generated via docker run --rm livekit/livekit-server generate-keys
LIVEKIT_API_SECRETβœ…Paired with API key
MISTRAL_API_KEYβœ…Bridge / fallback while Voxtral license is pending
VOXTRAL_VOICE_IDβœ…Default julia_knotencheck
DEEPGRAM_API_KEY❓Legacy β€” kept for STT fallback; verify still in use before rotating

Hardcoded in voice/docker-compose.yml (not env-var driven)

  • LIVEKIT_URL=ws://localhost:7880
  • LLAMA_GUARD_ENDPOINT=http://localhost:8000/v1
  • VOXTRAL_LOCAL_ENDPOINT=http://localhost:8001/v1
  • FASTER_WHISPER_MODEL_PATH=/models/faster-whisper-large-v3
  • NVIDIA_VISIBLE_DEVICES=all

If you move off host networking or relocate model files, these move into env.

Bedrock access

No env var β€” the voice-agent container assumes the EC2 instance's IAM role has bedrock:InvokeModel on eu-central-1. Document IAM role name + policy when BfArM reviewer asks.

Caddy / TLS

Cert renewal state in the caddy_data named volume (see docker-compose). No env vars.


4. Mobile (Expo / EAS) β€” 3 vars per environment

Source: apps/mobile/eas.json. No .env.example file in apps/mobile/ β€” env is configured per-channel in eas.json and injected at build time by EAS.

VarScopeChannels
EXPO_PUBLIC_API_URL🌐 clientdev β†’ staging.fragjulia.de, preview β†’ staging.fragjulia.de, production β†’ fragjulia.de
EXPO_PUBLIC_SUPABASE_URL🌐 clientdev β†’ staging-supabase.fragjulia.de, preview β†’ staging-supabase.fragjulia.de, production β†’ supabase.fragjulia.de
APP_ENVruntimedevelopment / staging / production

Note: EXPO_PUBLIC_SUPABASE_ANON_KEY is not in eas.json. Either consumed from a separate secrets source (EAS Secrets) or missing. Verify with native client before shipping next mobile release.

iOS / Android identifiers (in app.json, not env)

  • Bundle identifier: de.fragjulia.app
  • Expo scheme: fragjulia://
  • Microphone usage string (German, DSGVO-relevant)

Submit credentials (in eas.json.submit.production.ios)

All REPLACE_WITH_* placeholders as of 2026-04-18:

  • appleId, ascAppId, appleTeamId β€” fill before first store submission

5. Cron

Only one, defined in apps/web/vercel.json:

{
  "crons": [
    { "path": "/api/chat/cleanup", "schedule": "0 3 * * *" }
  ]
}
  • Schedule: 03:00 UTC daily
  • Guard: route handler checks authorization: Bearer ${CRON_SECRET} header (Vercel sets this automatically when invoking scheduled cron)
  • Purpose: prune old chat messages past tier-specific retention (7 days for free, unlimited for plus/premium)

6. Security classification summary

ClassCount (web)Examples
πŸ”’ Server-only secrets15SUPABASE_SERVICE_ROLE_KEY, STRIPE_SECRET_KEY, RESEND_API_KEY, LIVEKIT_API_SECRET, CRON_SECRET, HMAC_SECRET, …
🌐 Client-exposed (public)7NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SITE_URL, site/app URLs, test-mode key

Rules of thumb:

  • Anything NEXT_PUBLIC_* ends up in the JS bundle. Never put secrets there.
  • Anything EXPO_PUBLIC_* ends up in the RN bundle + OTA update payloads. Same rule.
  • Keep Stripe price IDs as server-only β€” they're not secrets cryptographically but moving them client-side makes price changes much harder to revert.

7. Gaps to fix (tracked here, should become follow-up issues)

  1. Mobile .env.example missing. Create apps/mobile/.env.example even if most env lives in eas.json β€” documents the shape of EAS secrets.
  2. OPENAI_API_KEY undocumented. Present in production (chat route uses it), missing from .env.example. Adds onboarding friction.
  3. SUPABASE_JWT_SECRET potentially dead. No obvious consumer β€” verify then either remove or document its use.
  4. Turnstile vars planned (#358) β€” must land in .env.example on same PR that introduces the runtime dependency.
  5. Voice .env.example lacks Bedrock config notes. Even if the live path uses IAM roles, document the expected IAM policy + fallback AWS_* vars for local dev.
  6. EAS submit placeholders. appleId / ascAppId / appleTeamId β€” fill before first App Store submission.

8. How to rotate a secret (runbook)

  1. Generate the new value (platform-specific β€” Stripe dashboard, Supabase settings, openssl rand -base64 32 for HMAC-like)
  2. Update Vercel project settings (all environments: production, preview, development) β€” NOT .env.example
  3. For voice (EC2): edit /root/knotencheck-voice.env, restart voice-agent container (docker compose restart voice-agent)
  4. For mobile: update EAS Secret via eas secret:create or web UI; trigger a new build
  5. Log the rotation in whatever audit path applies (future: audit.config_rotation event β€” see audit-system)
  6. Revoke the previous value at the provider only after confirming the new one works in each environment

#Relevance
#579Parent docs epic
#586Pillar B parent
#358Adds Turnstile + Bedrock vars (see Β§2 gaps)
apps/web/.env.exampleSource for Β§2
voice/.env.exampleSource for Β§3
apps/mobile/eas.jsonSource for Β§4
apps/web/vercel.jsonSource for Β§5

Changelog

  • 2026-04-18 β€” Initial version. Counts differ from bubbly-bubbling-quilt.md Β§2: web has 22 vars (quilt said 30+), voice has 5 (quilt said 8), mobile has 3 per EAS channel (quilt said 5). Turnstile still absent, OPENAI_API_KEY still undocumented. Single cron confirmed at 03:00 UTC.

On this page