# Descrybe v2 - REQUIRED bootstrap env only. # Copy to repo-root `.env` (single source of truth). Do not create apps/api/.env. # Product secrets (OpenAI, Stripe, mail, Pinecone, store connectors) # belong in the admin dashboard -- do NOT put them here: # platform: /admin/settings (stripe.* , feeds.private_url_allowlist) # tenant: /integrations/ai , /integrations/email , /stores # EPREL enrichment is on by default for all plans (public EU API). No activation # step. Optional kill-switch only: EPREL_ENABLED=false here, or eprel.enabled=false # under /admin/settings. # # How apps load env: # - Go API/worker/mailhooks: config.Load() loads this monorepo-root `.env` # (never overrides already-set process env). Override path: DOTENV_PATH=... # - npm run dev:api / scripts/run-in-dir.mjs / run-api.ps1 / migrate.*: same root `.env` # - SvelteKit: Vite envDir + kit.env.dir = monorepo root; PUBLIC_API_URL (+ optional PUBLIC_CSRF_COOKIE_NAME) # # Generate secrets locally (never commit real values): # openssl rand -hex 32 # Postgres (required). Default matches docker-compose local DB on :5433. DATABASE_URL=postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable # development | staging | production # Production fails closed: SESSION_SECURE=true, https WEB_ORIGIN, APP_ENCRYPTION_KEY, # TOKEN_SIGNING_SECRET (see config.Load validate). APP_ENV=development # API listen address (npm run dev:api forces :28471 so a stale local .env cannot bind :8080) HTTP_ADDR=:28471 # Optional Prometheus scrape (no secrets). Docs: docs/production-readiness.md#ops-prometheus-scrape # Examples: deploy/prometheus/scrape.example.yml + alerts.example.yml # METRICS_PUBLIC=1 # prod only: expose /metrics beyond loopback (private VIP/mesh only) # METRICS_ADDR=127.0.0.1:9091 # worker-only listener for sync_* series # Background worker — included in `npm run dev` (api+web+worker). # Also: npm run dev:worker | make worker | .\scripts\run-api.ps1 worker # Required for processing jobs, Woo/Shopify claim, support AI auto, billing, and GET /readyz. # Browser origin for CORS / cookies (must be https + non-loopback in production). # Dev web is Vite :28472 (strictPort). npm run dev forces matching WEB_ORIGIN / PUBLIC_API_URL. # Local: API also accepts the localhost/127.0.0.1 twin for credentialed CORS (same port). WEB_ORIGIN=http://localhost:28472 # Public API origin (web PUBLIC_API_URL should match; empty web = same-origin + Vite proxy) PUBLIC_API_URL=http://localhost:28471 # Session cookie Secure flag (required true when APP_ENV=production|prod). # Keep false on http://localhost — Secure cookies are dropped by the browser on HTTP. SESSION_SECURE=false # Browser smoke (codehelper site=local-descrybe). Never commit a real password. # Set to the local demo password from docs/demo-user.md (a1-primary / demo), # OR store it out-of-repo: printf '%s' "$DESCRYBE_SMOKE_PASS" | codehelper connections set-secret --name local-descrybe # (then password_ref=secret). Unset/empty -> headless Sign-in submits no password -> /api/auth/me 401. # DESCRYBE_SMOKE_PASS= # Session / invite token HMAC. Required in production. openssl rand -hex 32 TOKEN_SIGNING_SECRET= # AES key for credentials at rest (Woo/email/AI BYOK ciphertext). # Required in production. Prefer this over legacy CREDENTIALS_ENCRYPTION_KEY. # openssl rand -hex 32 APP_ENCRYPTION_KEY= # --- Production host (uncomment / set on the server; setup --production helps) --- # APP_ENV=production # SESSION_SECURE=true # WEB_ORIGIN=https://descrybe.io # PUBLIC_API_URL=https://api.descrybe.io # HTTP_ADDR=127.0.0.1:28471 # TRUSTED_PROXIES=127.0.0.1 # DATABASE_URL=postgres://descrybe:CHANGE_ME@127.0.0.1:5432/descrybe?sslmode=disable # TOKEN_SIGNING_SECRET and APP_ENCRYPTION_KEY: leave empty and run # npm run setup # to generate (or: openssl rand -hex 32). # Platform invite / forgot-password mail (optional env; prefer /admin/settings). # EMAIL_DRY_RUN defaults true when unset (safe). Live send needs false + SMTP. # EMAIL_DRY_RUN=true # SMTP_ENABLED=false # SMTP_HOST= # SMTP_PORT=587 # SMTP_USER= # SMTP_PASSWORD= # SMTP_FROM=noreply@descrybe.io # Cutover / ops toggles (optional) # HYPERCARE_MODE=false # STRIPE_MOCK=false # MAINTENANCE_MODE=false # READ_ONLY_MODE=false # --- Optional bootstrap (safe defaults in code; uncomment to override) --- # TRUSTED_PROXIES - hop-1 reverse-proxy / LB peers only (CIDR or IP, comma-separated). # When set, TrustedRealIP rewrites RemoteAddr from X-Forwarded-For / X-Real-IP only if # the TCP peer is on this allowlist. Empty (default) ignores client IP headers (safe # without a proxy; required behind CDN/LB so auth IP RPM + login lockout key correctly). # TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8 # RATE_LIMIT_REPLICAS=1 # RATE_LIMIT_MULTI_REPLICA=false # RATE_LIMIT_BACKEND=memory # ASSUMPTION (Product 10): in-process rate limits + email login lockout are OK on a single # API instance. Multi-replica cutover = edge/WAF hard global RPM (see deploy/examples/edge-rate-limit.md). # Optional RATE_LIMIT_REPLICAS divides HTTP middleware caps only (not lockout/StartLimiter/AI/email) # — not a shared store. RATE_LIMIT_BACKEND=redis|postgres is docs-only and forced to memory. # SESSION_COOKIE_NAME=descrybe_session # Session cookie Domain is derived automatically from WEB_ORIGIN + PUBLIC_API_URL: # sibling hosts of one parent (descrybe.io + api.descrybe.io) share the parent # domain so SvelteKit SSR receives the session cookie; localhost stays host-only. # CSRF_COOKIE_NAME=descrybe_csrf # PUBLIC_CSRF_COOKIE_NAME=descrybe_csrf # SESSION_IDLE_HOURS=24 # UPLOAD_DIR=data/uploads # MAINTENANCE_MODE=false # READ_ONLY_MODE=false # CREDENTIALS_ENCRYPTION_KEY= # legacy alias for APP_ENCRYPTION_KEY # --- Locales (not process-env; listed for agents/ops) --- # UI dashboard locales (browser localStorage key descrybe-ui-locale; default en): # en, es, fr, de, it, pt, nl, pl, ja # Source: apps/web/src/lib/i18n/locales.ts (+ message catalogs under lib/i18n/messages) # Content / AI language (companies.language via /settings Content Language; default en): # en, fr, de, es, it, nl, pt, pl, cs, sk, hu, ro, bg, hr, sl, sv, da, fi, el, et, lv, lt, mt, ga, ja # Sources: apps/web/src/lib/content-languages.ts , apps/api/internal/company/language.go # Optional EPREL fiche PDF language (prefer /admin/settings eprel.fiche_language): # EPREL_FICHE_LANGUAGE=EN # --- Prefer dashboard (optional env fallback still accepted by resolvers) --- # Platform /admin/settings (GET/PUT /api/admin/settings -> values.*): # stripe.* , feeds.private_url_allowlist # Optional EPREL overrides (not required - enrichment defaults on): # eprel.enabled=false (or EPREL_ENABLED=false in this file) to disable # eprel.base_url / eprel.timeout / eprel.fiche_language if tuning # OPENAI_*/RESEND_*/SMTP_* may still work as process-env fallbacks; prefer UI. # PROCESSING_*, DB_* pool knobs: code defaults; set in process env only if tuning. # MIGRATE_MYSQL_DSN: migrator CLI only - not API boot. # --- LLM test provider (optional; no production secrets) --- # Tiny OpenAI-compatible stub for CI/local processing proofs. See docs/mock-llm.md. # Start (separate terminal): # cd apps/api && go run ./cmd/mock-llm -addr 127.0.0.1:18767 # Stub-only knobs (read by mock-llm process, not by Descrybe API): # MOCK_LLM_API_KEY=local-test # MOCK_LLM_MODEL=mock-llm # Platform Completer fallback (root .env - restart api + worker after change): # OPENAI_API_KEY=local-test # OPENAI_BASE_URL=http://127.0.0.1:18767/v1 # OPENAI_MODEL=mock-llm # PROCESSING_RPM=60 # PROCESSING_MAX_RETRIES=3 # Prefer tenant /integrations/ai (custom base + key) over process env when possible. # Do NOT put real OpenAI/Green Chat secrets here - use dashboard or a private root .env. # Verify translation + processing: docs/mock-llm.md ("Translation + processing verification"). # --- Web analytics (optional; SvelteKit PUBLIC_* from this root .env) --- # Google Tag Manager container ID. When unset/empty, no GTM/GA tags load. # Consent Mode v2 defaults to denied until the cookie banner grants categories. # Setup: create GA4 property → GTM web container → GA4 Configuration tag with # Consent Settings (require analytics_storage; ad_* for ads) → publish. # SPA page views: Custom Event trigger `page_view` from the app. Do NOT also # enable GTM History Change / GA4 enhanced measurement page_views (double count). # PUBLIC_GTM_ID=GTM-XXXXXXX