# Assemblix — single env file for backend + frontend (VITE_*) + Docker Compose. # Copy to .env at the repo root and fill it in (.env is gitignored): cp .env.example .env # DATABASE_URL / REDIS_URL use HOST values; compose overrides them to service hostnames. # --- Docker Compose --- COMPOSE_PROFILES= # service profiles; empty=core only. "queue"=Redis+worker, add "scale"=PgBouncer POSTGRES_USER=assemblix POSTGRES_PASSWORD=assemblix # CHANGE for any non-local deploy POSTGRES_DB=assemblix POSTGRES_PORT=5412 # host DB port (dev binds 127.0.0.1 only; prod doesn't publish it) API_PORT=8000 # host port for the api container WEB_PORT=8080 # host port for the web container # --- Database --- DATABASE_URL=postgresql://assemblix:assemblix@localhost:5412/assemblix # HOST value; compose points it at `postgres` DB_POOL_SIZE=10 # persistent connections per process DB_MAX_OVERFLOW=20 # extra burst connections above pool size DB_POOL_TIMEOUT=30 # seconds to wait for a free connection DB_DISABLE_STATEMENT_CACHE=false # true only behind a transaction-mode pooler (PgBouncer/Supavisor) DB_TARGET_HOST=postgres # in-network DB host for api/worker; set `pgbouncer` on scale tier DB_TARGET_PORT=5432 # in-network DB port; 6432 for PgBouncer PGBOUNCER_MAX_DB_CONNECTIONS=50 # scale tier: real backend conns to Postgres (keep < max_connections) PGBOUNCER_MAX_CLIENT_CONN=1000 # scale tier: client conns the pooler accepts PGBOUNCER_DEFAULT_POOL_SIZE=25 # scale tier: default pool size # --- Security (REQUIRED — app won't start without these two) --- JWT_SECRET_KEY= # JWT signing secret, min 32 chars. gen: python -c "import secrets;print(secrets.token_urlsafe(48))" JWT_ALGORITHM=HS256 ACCESS_TOKEN_EXPIRE_MINUTES=1440 # access token lifetime, minutes (1440=24h) ENCRYPTION_KEY= # Fernet key for stored creds. gen: python generate_encryption_key.py # --- CORS & limits --- CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000 # comma-separated, no trailing slash APP_PUBLIC_URL= # public web app URL for links in notifications; blank = first CORS origin HTTP_NODE_ALLOW_INTERNAL=false # allow HTTP-request node to reach private IPs (SSRF opt-out) WORKFLOW_EXECUTION_TIMEOUT_SECONDS=1800 # wall-clock budget per workflow run CEL_EVALUATION_TIMEOUT_SECONDS=5 # per-expression CEL eval timeout KB_MAX_UPLOAD_BYTES=26214400 # knowledge-base max upload size (25 MB) KB_MAX_PDF_PAGES=500 # knowledge-base max PDF pages KB_PDF_PARSE_TIMEOUT_SECONDS=30 # knowledge-base PDF parse timeout # --- Billing / Payments (Enterprise; off by default) --- BILLING_ENABLED=false # false = /payments router not mounted PAYMENT_PROVIDER=paddle PADDLE_API_KEY= PADDLE_WEBHOOK_SECRET= PADDLE_ENVIRONMENT=sandbox PADDLE_PRICE_PRO= PADDLE_PRICE_BUSINESS= # One-off credit packs. A pack is listed by GET /billing/packs — and therefore # purchasable — only while its price id is set here. PADDLE_PRICE_PACK_S= PADDLE_PRICE_PACK_M= PADDLE_PRICE_PACK_L= # --- LLM providers --- # Transport base URL per provider, for chat completions and transcription. # Leave empty for the provider's own endpoint. OPENAI_API_BASE_URL=https://api.openai.com/v1 GEMINI_API_BASE_URL= # Realtime (speech-to-speech) endpoints for Voice Agents. Separate on purpose: a # REST gateway is not automatically a WebSocket gateway, and an LLM proxy that # serves /v1/chat/completions answers the realtime route with 404. Leave empty to # reach the provider directly; set only once a gateway is known to proxy these: # OpenAI wss:///realtime # Gemini wss:///ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent OPENAI_REALTIME_BASE_URL= GEMINI_LIVE_BASE_URL= SYSTEM_OPENAI_API_KEY= # system keys for FREE plan / fallback; blank = self-host BYO-key SYSTEM_GEMINI_API_KEY= SYSTEM_DEEPSEEK_API_KEY= # --- Voice output (TTS, END node) --- SYSTEM_ELEVENLABS_API_KEY= # platform ElevenLabs key for hosted voice; blank = self-host BYO-key ELEVENLABS_API_BASE_URL=https://api.elevenlabs.io/v1 # override to route ElevenLabs via a proxy TYPESAFE_API_BASE_URL=https://api.typesafe.ai # Jev node; override to route TypeSafe via a proxy VOICE_OUTPUT_MAX_CHARS=2000 # hard cap on chars synthesized per END node (over → text-only) # --- AI avatars (anam.ai, bitHuman, LiveAvatar) — BYO key per project via credentials; base URL overrides only --- ANAM_API_BASE_URL=https://api.anam.ai BITHUMAN_API_BASE_URL=https://api.bithuman.ai BITHUMAN_RUNTIME_URL=https://auth.api.bithuman.ai/v1/runtime-tokens/request LIVEAVATAR_API_BASE_URL=https://api.liveavatar.com LIVEAVATAR_SANDBOX=false # true = free ~1-minute sandbox sessions (dev only) # --- Yandex SpeechKit (voice input + output) — needs an API key AND a folder id --- SYSTEM_YANDEX_SPEECHKIT_API_KEY= # platform SpeechKit key for hosted voice; blank = self-host BYO-key SYSTEM_YANDEX_SPEECHKIT_FOLDER_ID= # platform folder id (required together with the key above) YANDEX_TTS_API_BASE_URL=https://tts.api.cloud.yandex.net/speech/v1 # override to route via a proxy YANDEX_STT_API_BASE_URL=https://stt.api.cloud.yandex.net/speech/v1 # override to route via a proxy # --- Voice-agent avatars (LiveKit; optional, all four or none) --- LIVEKIT_URL= # URL the API dials, e.g. wss://rtc.example.com (or http://livekit:7880 in compose) LIVEKIT_PUBLIC_URL= # URL for browsers AND avatar vendors: public wss:// with valid TLS LIVEKIT_API_KEY= # LiveKit API key LIVEKIT_API_SECRET= # LiveKit API secret (>= 32 chars). gen: openssl rand -base64 48 AVATAR_JOIN_TIMEOUT_SECONDS=45 # wait for caller mic + avatar video before failing the call # --- Tools --- TAVILY_API_KEY= # web search; free key at https://tavily.com # --- OAuth (blank = provider hidden) --- GOOGLE_OAUTH_CLIENT_ID= GOOGLE_OAUTH_CLIENT_SECRET= GITHUB_OAUTH_CLIENT_ID= GITHUB_OAUTH_CLIENT_SECRET= # --- Credit system (USD) --- CREDIT_VALUE_USD=0.0001 CREDIT_MARGIN_PERCENT=10 REQUEST_FEE_USD=0.0001 VOICE_PLATFORM_FEE_USD_PER_MINUTE=0.02 # --- Misc --- HOST_URL=http://localhost:8000 TELEGRAM_API_BASE_URL= # custom Telegram Bot API base (proxy); blank = api.telegram.org TASK_TIMEOUT_SECONDS=60 # foreground exec timeout before falling back to background task # --- Redis / Queue (optional; blank = single-process Postgres-only self-host) --- REDIS_URL= # set to enable queue/pubsub/checkpointing. docker queue tier: redis://redis:6379/0 EXECUTION_QUEUE_ENABLED=false # Arq distributed execution queue (needs REDIS_URL) DEBUG_EVENTS_USE_REDIS=false # publish debug events to Redis Pub/Sub (needs REDIS_URL) EXECUTION_CHECKPOINTING_ENABLED=false # persist checkpoints to Redis for crash resume (needs REDIS_URL) GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS=30 # seconds to drain in-flight execs on SIGTERM LOGIN_RATE_LIMIT_PER_5MIN=10 # max login/register attempts per 5 min per IP before 429 # --- Observability --- METRICS_ENABLED=true # enable /metrics endpoint + worker scrape server METRICS_PORT=9000 # worker Prometheus scrape port # --- Horizontal scaling (safe defaults for self-host) --- WORKER_MAX_JOBS=10 # concurrent workflow jobs per Arq worker (workflows are I/O-bound) WEB_CONCURRENCY=1 # uvicorn worker processes for the API tier # PROMETHEUS_MULTIPROC_DIR= # empty writable dir for cross-process metrics; needed when WEB_CONCURRENCY>1 WORKER_REPLICAS=1 # worker container replicas (read by compose `worker` service) WORKER_METRICS_ENABLED=true # worker exposes its scrape port; false to avoid bind clashes on multi-replica ORG_MAX_CONCURRENCY=0 # cross-worker LLM concurrency cap per org; 0=unlimited (needs REDIS_URL) PROVIDER_MAX_CONCURRENCY=0 # cross-worker LLM concurrency cap per provider; 0=unlimited (needs REDIS_URL) CONCURRENCY_ACQUIRE_TIMEOUT_SECONDS=30 # wait for a free slot before proceeding anyway # --- Frontend (VITE_* — only these reach the browser bundle) --- VITE_API_BASE_URL=/api # SPA API base; keep /api (nginx/Vite proxy forward it) VITE_APP_URL= # public app URL for API code samples; blank = current browser host VITE_APP_VERSION=1.0.0 VITE_LANGS=en,es,ru # locales to build; the two below are runtime hints VITE_DEFAULT_LANGUAGE=en VITE_SUPPORTED_LANGUAGES=en,es,ru VITE_GOOGLE_CLIENT_ID= # OAuth client IDs (public); blank to hide VITE_GITHUB_CLIENT_ID= VITE_YANDEX_CLIENT_ID= VITE_BILLING_ENABLED=false # true = show billing/payment UI (Enterprise); false for self-host VITE_PADDLE_CLIENT_TOKEN= # Paddle billing (Enterprise); blank for self-host VITE_PADDLE_ENVIRONMENT=production