# Database POSTGRES_DB=multica POSTGRES_USER=multica POSTGRES_PASSWORD=multica POSTGRES_PORT=5432 DATABASE_URL=postgres://multica:multica@localhost:5432/multica?sslmode=disable # Optional pgxpool tuning. Defaults are 25 / 5 per pod and are usually fine. # You can also set pool_max_conns / pool_min_conns as query params on # DATABASE_URL; env vars below take precedence over URL params. # DATABASE_MAX_CONNS=25 # DATABASE_MIN_CONNS=5 # Server # APP_ENV gates production safety checks. Docker self-host pins APP_ENV to # "production" by default. Local dev can leave it unset. # See SELF_HOSTING.md for the full login setup. APP_ENV= # Optional local/testing shortcut. Empty by default, so there is no fixed # verification code. Without RESEND_API_KEY, generated codes print to stdout. # If you need deterministic local automation, set a 6-digit value such as # 888888 and keep APP_ENV non-production. This is ignored when APP_ENV=production. MULTICA_DEV_VERIFICATION_CODE= PORT=8080 # Docker Compose consumes flat port values. Set BACKEND_PORT directly to # override the backend host port. BACKEND_PORT=8080 # Optional aliases for local/self-host backend port helpers outside compose. # API_PORT=8080 # SERVER_PORT=8080 FRONTEND_PORT=3000 # Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT. # Set explicitly only when serving frontend on a different origin/domain. FRONTEND_ORIGIN=http://localhost:${FRONTEND_PORT} # Prometheus metrics are disabled by default. When enabled, bind to loopback # unless you protect the listener with private networking, allowlists, or # proxy auth. Do not expose this endpoint through the public app/API ingress. # HTTP request metrics start accumulating only when this listener is enabled. # METRICS_ADDR=127.0.0.1:9090 JWT_SECRET=change-me-in-production # Derived by Makefile / local scripts from the backend port. # Set explicitly only when the daemon reaches the API through a different URL. # MULTICA_SERVER_URL=ws://localhost:8080/ws # Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_ORIGIN. # Set explicitly only when the app's public URL differs from local frontend. MULTICA_APP_URL=${FRONTEND_ORIGIN} # Public URL the API is reachable at from the open internet (no trailing # slash). Used to mint absolute webhook URLs for autopilot webhook # triggers and to show correct daemon setup commands in the web UI. Leave # unset behind a same-origin reverse proxy or for plain localhost dev — # the frontend will compose the URL from window.origin + webhook_path in # that case. Headers are intentionally not used to derive this value, to # avoid Host / X-Forwarded-Host spoofing when a self-hosted reverse proxy # is not hardened. MULTICA_PUBLIC_URL= # Comma-separated CIDR list of reverse proxies whose X-Forwarded-For / # X-Real-IP headers the per-IP webhook rate limiter is allowed to trust. # Empty (the default) means "trust no headers" — the limiter uses # r.RemoteAddr only, which is the safe shape when the backend is # exposed directly. Set this when running behind nginx/Caddy/Cloudflare: # e.g. "127.0.0.1/32" for a same-host reverse proxy, or the CDN's # announced ranges for cloud deployments. MULTICA_TRUSTED_PROXIES= MULTICA_DAEMON_CONFIG= MULTICA_WORKSPACE_ID= MULTICA_DAEMON_ID= MULTICA_DAEMON_DEVICE_NAME= MULTICA_DAEMON_POLL_INTERVAL=3s MULTICA_DAEMON_HEARTBEAT_INTERVAL=15s MULTICA_CODEX_PATH=codex MULTICA_CODEX_MODEL= MULTICA_CODEX_WORKDIR= MULTICA_CODEX_TIMEOUT=20m # Self-host image channel # Default stable release channel. Pin to an exact release like v0.2.4 if you # want to stay on a specific version. If the selected tag has not been # published to GHCR yet, use make selfhost-build / the build override instead. MULTICA_IMAGE_TAG=latest MULTICA_BACKEND_IMAGE=ghcr.io/multica-ai/multica-backend MULTICA_WEB_IMAGE=ghcr.io/multica-ai/multica-web # Email # Two delivery options - only one needs to be configured: # # Option A: Resend (SaaS, recommended for cloud deployments) # Set RESEND_API_KEY to a key from resend.com and verify your sending domain there. # For local/dev use, leave RESEND_API_KEY empty - codes print to stdout. To # accept a fixed local code, also set MULTICA_DEV_VERIFICATION_CODE above # (ignored when APP_ENV=production). RESEND_API_KEY= RESEND_FROM_EMAIL=noreply@multica.ai # # Option B: SMTP relay (for self-hosted / on-premise deployments) # Takes priority over Resend when SMTP_HOST is set. # Supports unauthenticated relay (leave SMTP_USERNAME empty) and authenticated SMTP. # Set SMTP_TLS_INSECURE=true only for private CA or self-signed certificates. # SMTP_TLS controls the TLS mode: # - unset / "starttls" (default): plaintext connect, upgrade via STARTTLS. # - "implicit" (aliases: "smtps", "ssl"): TLS handshake on connect (SMTPS). # Required by providers that only offer port 465 and do not advertise # STARTTLS (e.g. Aliyun enterprise mail). Auto-enabled when SMTP_PORT=465 # and SMTP_TLS is unset. # SMTP_EHLO_NAME is the EHLO/HELO name announced to the relay. Defaults to the # machine hostname; set a real FQDN when a strict relay (e.g. Google Workspace # smtp-relay.gmail.com) rejects the default and the connection drops as an EOF. SMTP_HOST= SMTP_PORT=25 SMTP_USERNAME= SMTP_PASSWORD= SMTP_TLS_INSECURE=false SMTP_TLS= SMTP_EHLO_NAME= # Google OAuth # The web login page reads GOOGLE_CLIENT_ID from /api/config at runtime, so # changing it only requires restarting the backend / compose stack. No web # rebuild is needed. GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= # Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_ORIGIN. # Set explicitly only when your OAuth callback URL differs from local frontend. GOOGLE_REDIRECT_URI=${FRONTEND_ORIGIN}/auth/callback # S3 / CloudFront # S3_BUCKET — bucket NAME only (e.g. "my-bucket"). Do NOT include the # ".s3..amazonaws.com" suffix; the server builds the public URL # from S3_BUCKET + S3_REGION. S3_REGION must match the bucket's real region. S3_BUCKET= S3_REGION=us-west-2 AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= # AWS_ENDPOINT_URL — optional S3-compatible endpoint (MinIO, RustFS, R2, etc.). # For internal Docker/VPC hosts such as http://rustfs:9000, leave # ATTACHMENT_DOWNLOAD_MODE=auto or set proxy explicitly so browsers/CLI do # not need direct access to the object store. AWS_ENDPOINT_URL= ATTACHMENT_DOWNLOAD_MODE=auto ATTACHMENT_DOWNLOAD_URL_TTL=30m CLOUDFRONT_KEY_PAIR_ID= CLOUDFRONT_PRIVATE_KEY_SECRET=multica/cloudfront-signing-key CLOUDFRONT_PRIVATE_KEY= CLOUDFRONT_DOMAIN= # COOKIE_DOMAIN — optional Domain attribute on session + CloudFront cookies. # Leave empty for single-host deployments (localhost, LAN IP, or a single # hostname) — session cookies become host-only, which is what the browser # wants. Only set it when the frontend and backend sit on different # subdomains of one registered domain (e.g. ".example.com"). Do NOT set it # to an IP address: RFC 6265 forbids IP literals in the cookie Domain # attribute and browsers silently drop such cookies. COOKIE_DOMAIN= # AUTH_TOKEN_TTL — auth token lifetime. Accepts Go duration strings (e.g. # "8760h", "720h30m") or plain integer seconds. # Default: 2592000 (30 days). Self-hosted deployments on trusted networks can # set a longer value to reduce re-authentication frequency. # Note: longer TTL = longer exposure window if a cookie is leaked. # AUTH_TOKEN_TTL=2592000 # Local file storage (fallback when S3_BUCKET is not set) LOCAL_UPLOAD_DIR=./data/uploads # Derived by Makefile / local scripts from the backend port. # Set explicitly only when uploads are served through a different public URL. # LOCAL_UPLOAD_BASE_URL=http://localhost:8080 # Security # Comma-separated list of allowed origins for CORS and WebSocket connections. # Defaults to localhost dev origins when unset. # Example: CORS_ALLOWED_ORIGINS=https://app.multica.ai,https://staging.multica.ai CORS_ALLOWED_ORIGINS= # ==================== Rate limiting (optional Redis) ==================== # Per-IP fixed-window rate limiter on the public auth endpoints # (/auth/send-code, /auth/verify-code, /auth/google). Backed by Redis. # When REDIS_URL is unset the limiter is a no-op (fail-open) and the # backend logs "rate limiting disabled: REDIS_URL not configured" at # startup. The same REDIS_URL is reused by the realtime fan-out hub, # the PAT cache, and the daemon-token cache. # REDIS_URL=redis://localhost:6379/0 # Max requests per IP per minute. Defaults are 5 for send-code/google # and 20 for verify-code. # RATE_LIMIT_AUTH=5 # RATE_LIMIT_AUTH_VERIFY=20 # Comma-separated CIDRs whose X-Forwarded-For the auth limiter is # allowed to trust. Empty (default) = never trust XFF, only RemoteAddr. # REQUIRED behind a reverse proxy — otherwise every real user shares # the proxy IP and the whole deployment lands in one bucket, turning # /auth/send-code into 5 req/min site-wide. Use e.g. "127.0.0.1/32,::1/128" # for same-host Caddy/Nginx, or the CDN's published ranges for ALB/CF. # This is a separate list from MULTICA_TRUSTED_PROXIES above (which # governs the autopilot webhook limiter). # RATE_LIMIT_TRUSTED_PROXIES= # Realtime metrics endpoint (/health/realtime) access control. See MUL-1342. # When unset, the endpoint only serves direct loopback (127.0.0.1 / ::1) # callers with no forwarding headers and returns 404 to everything else — # safe for local dev. Any deployment behind a reverse proxy (Caddy / Nginx # terminating TLS in front of localhost:8080) MUST set this token, since # proxied requests look like loopback at the Go layer; with no token, those # requests are refused with 404. Pass the token as # `Authorization: Bearer `. # REALTIME_METRICS_TOKEN= # GitHub App integration (Settings → GitHub "Connect GitHub") # Both must be set for the Connect button to enable and for webhooks to be # accepted; leave empty to disable the integration. See docs/github-integration. # GITHUB_APP_SLUG is the tail of https://github.com/apps/. GITHUB_APP_SLUG= GITHUB_WEBHOOK_SECRET= # Optional: GitHub App identity for App-authenticated REST calls. When set, # the setup callback enriches the installation row with the real account # login (org / user name) immediately after install. When unset, the row # is created with the "unknown" placeholder and the next `installation` # webhook from GitHub overwrites it — set both to skip that interim flash. # GITHUB_APP_ID is the numeric "App ID" shown on the App's settings page. # GITHUB_APP_PRIVATE_KEY is the full PEM block (including BEGIN/END lines) # generated under "Private keys" on that same page; preserve newlines. GITHUB_APP_ID= GITHUB_APP_PRIVATE_KEY= # Lark / Feishu bot integration (Settings → Integrations "Bind to Lark") # Off until MULTICA_LARK_SECRET_KEY is set — a base64-encoded 32-byte key # that encrypts each Bot's app secret at rest. Leave empty to disable. # Generate one with: openssl rand -base64 32 MULTICA_LARK_SECRET_KEY= # Mainland 飞书 and international Lark are auto-detected per installation # (at QR scan) and served side by side — LEAVE THESE EMPTY for normal use. # They are optional deployment-wide overrides that force EVERY installation # onto one host (a proxy, a mock for tests, or a single-cloud staging # setup); HTTP drives outbound Open Platform API calls, CALLBACK the inbound # long-conn bootstrap. NOTE: if you previously ran international Lark by # setting these to https://open.larksuite.com, the server relabels your # existing installs to region=lark on first boot after upgrade, so you can # clear these afterwards. See docs/lark-bot-integration. MULTICA_LARK_HTTP_BASE_URL= MULTICA_LARK_CALLBACK_BASE_URL= # Frontend # Leave empty — auto-derived from page origin in browser, set by Makefile for local dev. # NEXT_PUBLIC_API_URL also feeds the Next.js SSR proxy when explicitly set. NEXT_PUBLIC_API_URL= NEXT_PUBLIC_WS_URL= # Remote API (optional) — set to proxy local frontend to a remote backend # Leave empty to use local backend (localhost:8080) # REMOTE_API_URL=https://multica-api.copilothub.ai # ==================== Self-hosting: Control Signups (fixes #930) ==================== # Set to "false" to completely disable new user signups (recommended for private instances) ALLOW_SIGNUP=true # The web UI reads ALLOW_SIGNUP from /api/config at runtime, so toggling this # only requires restarting the backend / compose stack — not rebuilding web. # It is not hot-reloaded. # Optional: Only allow emails from these domains (comma-separated) ALLOWED_EMAIL_DOMAINS= # Optional: Only allow these exact email addresses (comma-separated) ALLOWED_EMAILS= # Set to "true" to disable workspace creation for every caller on this # instance (#3433). Operators usually leave this unset, bootstrap the # shared workspace, then flip this to "true" and restart so subsequent # users join only via invitations and the entire deployment is visible to # the platform admin. The web UI reads this from /api/config at runtime, # so toggling requires a backend restart but not a frontend rebuild. DISABLE_WORKSPACE_CREATION= # ==================== Analytics (PostHog) ==================== # Product analytics events feed the acquisition → activation → expansion funnel. # Leave POSTHOG_API_KEY empty for local dev / self-hosted instances; the server # will run a no-op analytics client and ship nothing. See docs/analytics.md. POSTHOG_API_KEY= POSTHOG_HOST=https://us.i.posthog.com # Optional override for the `environment` PostHog event property. # Defaults from APP_ENV and normalizes to production / staging / dev. ANALYTICS_ENVIRONMENT= # Force the no-op client even when POSTHOG_API_KEY is set (CI / opt-out). ANALYTICS_DISABLED=