# --- Server --- # BACKEND_PORT takes precedence over PORT. Prefer it: many tools inject PORT # for the frontend, and Flask would otherwise take the React dev server's port. BACKEND_PORT=5000 # --- Database: sqlite (default) or mariadb --- DB_TYPE=sqlite # SQLite file (used when DB_TYPE=sqlite) DATABASE_PATH=./data/health.db # MariaDB (used when DB_TYPE=mariadb) — production DB on the Oracle server # (129.146.26.249, local MariaDB 10.3). Dedicated account over TCP 127.0.0.1; # MARIADB_SOCKET is only needed if TCP auth is disabled for the app user. # MARIADB_HOST=127.0.0.1 # MARIADB_PORT=3306 # MARIADB_USER=garmin # MARIADB_PASSWORD=your_production_mariadb_password # MARIADB_DATABASE=garmin_health_lab # --- Auth --- # CHANGE THIS in production! Used to sign JWTs (7-day expiry by default). JWT_SECRET=dev_secret_change_me JWT_EXPIRY_DAYS=7 # --- auth-hub OAuth2 provider (centralized SSO) --- # See docs/AUTH_HUB_INTEGRATION.md for setup instructions. # # Base URL of the auth-hub service AUTH_HUB_BASE_URL=http://129.146.26.249:5300 # # OAuth2 client credentials (obtain from auth-hub.manage_clients create) # NOTE: these must be registered against the auth-hub instance AUTH_HUB_BASE_URL # actually points to (dev vs prod are separate databases with separate clients). # Put the REAL values in backend/.env (gitignored) — never here. AUTH_HUB_CLIENT_ID=your_client_id AUTH_HUB_CLIENT_SECRET=your_client_secret # # Callback URL (must exactly match what's registered in auth-hub) AUTH_HUB_REDIRECT_URI=http://129.146.26.249:8123/auth/callback # --- CORS (comma-separated allowed front-end origins) --- # localhost stays in the production list on purpose: CORS is not an auth # boundary — every data route requires a valid JWT — so allowing a developer's # dev server costs nothing and saves toggling this on every session. CORS_ORIGIN=http://localhost:3000,http://localhost:5173 # --- AI models (text-only, large context) --- # Put REAL keys in backend/.env — that file is gitignored. Never commit keys. # Any model whose credentials are absent is skipped automatically. # Self-hosted AI gateway (model id "gateway"). OpenAI-compatible; it fans out # over nvidia/gemini/ollama itself and rotates several Gemini keys, so it # absorbs single-vendor quota limits. Reached directly, bypassing any local # HTTP proxy. NOTE: its NVIDIA upstream is a large reasoning model — replies # can take 2-3 minutes, so set AI_TIMEOUT_SECONDS accordingly. # HTTPS (Caddy, strips the /ai prefix) rather than http://…:5100 — the token # rides in an Authorization header and should not cross the internet in clear. AI_GATEWAY_BASE_URL=https://oracle.zichuan.xyz/ai/v1 AI_GATEWAY_TOKEN= AI_GATEWAY_MODEL=ai-gateway-auto # Google AI Studio -> "gemini-flash". Free-tier quota is small; 429s are common. GEMINI_API_KEY= # NVIDIA NIM -> "llama-70b", "nemotron-49b", "mistral-large". # Model ids come from that account's live GET /v1/models — do not guess them. NVIDIA_API_KEY= # NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1 # Preference order. The first configured model answers; if it fails or times # out, the next is tried. Read per request, so changes need no restart. AI_MODEL_CHAIN=gateway,gemini-flash,llama-70b # Max days of history sent (CSV-encoded). Trimmed further per model so the # payload always fits that model's own context window. AI_DAY_BUDGET=365 # Measured against the gateway, not guessed: a trivial prompt took 138s end to # end, because its primary upstream emits a full chain of thought before the # answer. Nothing user-facing blocks on this (the briefing generates in a # background thread), but the timeout still has to clear the real latency. AI_TIMEOUT_SECONDS=300 # Output cap. Reasoning models spend part of it thinking before they answer; # entries that need more declare their own budget in services/ai.py. AI_MAX_TOKENS=1024 # Output cap for the AI coach (晨报 / 趋势归因 / Copilot). Larger than # AI_MAX_TOKENS above: the same reasoning trace is spent from this budget # before the answer starts, and at 1024 the reply was all thinking with the # JSON truncated away. AI_COACH_MAX_TOKENS=4000 # --- AI coach job queue --- # The gateway runs `gunicorn -w 1 --threads 4` and is shared with fam-edge and # the camera project: four concurrent requests for everyone, while one of ours # holds a thread for 2-5 minutes. So this consumer runs one job at a time # across the whole deployment (not one per Gunicorn worker) and waits between # jobs. Raising either of these makes the backfill finish sooner at the cost of # the shared box — a 502 there is a 502 for the other two projects as well. AI_JOB_CONCURRENCY=1 AI_JOB_GAP_SECONDS=20 # Set AI_JOBS=false to stop consuming entirely (screens then show the computed # figures with no model reading). AI_JOBS=true