Переменные окружения
Полный список переменных окружения для self-hosted server Multica.
Self-hosted server Multica читает конфигурацию из переменных окружения при старте — база, вход, email, storage, signup allowlists. На этой странице переменные сгруппированы по назначению: в каждом разделе — что будет, если не задать, и что обязательно в production. Как настроить auth-related переменные — в Sign-in and signup configuration.
Core server variables
Базовые переменные перед deploy — у части есть defaults для старта, но в production задавайте required явно.
| Variable | Default | Required in production? |
|---|---|---|
DATABASE_URL | postgres://multica:multica@localhost:5432/multica?sslmode=disable | Yes |
PORT | 8080 | No (unless you change the port) |
JWT_SECRET | multica-dev-secret-change-in-production | Yes (the default is unsafe) |
APP_ENV | empty | Yes (must be production) |
FRONTEND_ORIGIN | empty | Yes (self-host must set its own domain) |
MULTICA_DEV_VERIFICATION_CODE | empty | No (must stay empty in production) |
Держите MULTICA_DEV_VERIFICATION_CODE пустым в production. Фиксированный локальный test code по умолчанию выключен, но при MULTICA_DEV_VERIFICATION_CODE=888888 любой, кто может запросить code, войдёт с этим значением, пока APP_ENV не production. Shortcut игнорируется при APP_ENV=production.
Database connection pool
| Variable | Default | Description |
|---|---|---|
DATABASE_MAX_CONNS | 25 | pgxpool max connections. Daemon опрашивает часто (каждые 3s) и использует connections; крупным deployment может понадобиться больше |
DATABASE_MIN_CONNS | 5 | Minimum idle connections |
Если не заданы, используются значения выше — не встроенные pgx defaults 4/NumCPU, из‑за которых раньше был pool exhaustion в production.
Email configuration
Два backend доставки — Resend для cloud или SMTP relay для internal / on-premise. SMTP_HOST имеет приоритет над RESEND_API_KEY, если заданы оба.
Resend
| Variable | Default | Description |
|---|---|---|
RESEND_API_KEY | empty | Resend API key |
RESEND_FROM_EMAIL | noreply@multica.ai | Sender address (must be a domain verified in your Resend account; also reused as the From: header when SMTP is in use) |
SMTP relay
| Variable | Default | Description |
|---|---|---|
SMTP_HOST | empty | SMTP relay hostname. Setting this activates SMTP mode and overrides Resend |
SMTP_PORT | 25 | SMTP port. Use 587 for STARTTLS submission, or 465 for SMTPS (implicit TLS, auto-enabled) |
SMTP_USERNAME | empty | SMTP username. Leave empty for unauthenticated relay |
SMTP_PASSWORD | empty | SMTP password |
SMTP_TLS | starttls | TLS mode. implicit (aliases smtps, ssl) forces an immediate TLS handshake on connect (SMTPS); port 465 auto-enables it. Unset / starttls upgrades via STARTTLS after connect |
SMTP_TLS_INSECURE | false | Set true to skip TLS certificate verification (private CA / self-signed only) |
SMTP_EHLO_NAME | machine hostname | EHLO/HELO name announced to the relay. Set a real FQDN when a strict relay (e.g. Google Workspace smtp-relay.gmail.com) rejects the default greeting from a public IP — otherwise the relay drops the connection and it surfaces as an opaque EOF on a later command |
STARTTLS включается автоматически, когда server его advertises. Dial timeout 10s, вся SMTP session — deadline 30s, чтобы «чёрная дыра» relay не зависала auth handler.
Если не настроено ни одно: server не падает с ошибкой, но каждое письмо (verification codes, invite links) пишется только в stdout server. Удобно локально — code из логов; в production забытый email config — тихая чёрная дыра, пользователи не получают письма и ошибки нет.
Google OAuth configuration
Optional. Не задавайте — только email + verification code; задайте — кнопка «Sign in with Google» на странице входа.
| Variable | Default | Description |
|---|---|---|
GOOGLE_CLIENT_ID | empty | Google Cloud OAuth client ID |
GOOGLE_CLIENT_SECRET | empty | Google Cloud OAuth secret |
GOOGLE_REDIRECT_URI | http://localhost:3000/auth/callback | OAuth callback URL (self-host: replace with your frontend domain) |
Runtime: frontend читает настройки через /api/config — смена не требует rebuild frontend, restart server достаточно.
Полная настройка (включая Google Cloud Console) — Sign-in and signup configuration.
File storage configuration
Multica хранит вложения (images и files в comments). S3 preferred; без S3 — fallback на local disk.
S3 / S3-compatible storage
| Variable | Default | Description |
|---|---|---|
S3_BUCKET | empty | Bucket name only (for example my-bucket). Do not include the .s3.<region>.amazonaws.com suffix — the server constructs the public host from S3_BUCKET + S3_REGION. Setting this enables S3 storage |
S3_REGION | us-west-2 | AWS region. Must match the bucket's actual region — it is used both for SDK signing and for building the public URL |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | empty | Static credentials. When both are unset, the AWS SDK default credential chain is used (IAM role / environment credentials) |
AWS_ENDPOINT_URL | empty | Custom S3-compatible endpoint (for example MinIO). Setting this switches to path-style URLs |
ATTACHMENT_DOWNLOAD_MODE | auto | Attachment download path: auto, cloudfront, presign, or proxy. In auto, CloudFront is preferred when fully configured; internal/private endpoint hosts use the server proxy; public S3-compatible endpoints use presigned GET URLs when supported |
ATTACHMENT_DOWNLOAD_URL_TTL | 30m | TTL for CloudFront signed URLs and S3 presigned download URLs. Accepts Go duration strings |
Когда S3_BUCKET не задан: при старте server логирует "S3_BUCKET not set, cloud upload disabled", uploads идут на local disk.
URL stored objects строятся в таком порядке:
https://<CLOUDFRONT_DOMAIN>/<key>если заданCLOUDFRONT_DOMAIN.<AWS_ENDPOINT_URL>/<S3_BUCKET>/<key>(path-style) если заданAWS_ENDPOINT_URL.https://<S3_BUCKET>.s3.<S3_REGION>.amazonaws.com/<key>(virtual-hosted-style). Если вS3_BUCKETесть dots, fallback наhttps://s3.<S3_REGION>.amazonaws.com/<S3_BUCKET>/<key>(path-style) — wildcard TLS AWS не валидирует dotted bucket hosts.
API download_url — GET /api/attachments/{id}/download, если CloudFront signing не настроен. Endpoint редиректит на CloudFront/S3 presigned URLs когда безопасно, или стримит через server для private/internal endpoints вроде http://rustfs:9000. Для Docker/VPC-only object stores задайте ATTACHMENT_DOWNLOAD_MODE=proxy, если auto detection недостаточно консервативен для вашей сети.
Local disk (when S3 is not configured)
| Variable | Default | Description |
|---|---|---|
LOCAL_UPLOAD_DIR | ./data/uploads | Local storage directory |
LOCAL_UPLOAD_BASE_URL | empty (returns relative paths) | Public base URL — leave unset and the frontend can't resolve a full URL for attachments |
CloudFront (optional)
Если S3 за CloudFront: CLOUDFRONT_DOMAIN, CLOUDFRONT_KEY_PAIR_ID, CLOUDFRONT_PRIVATE_KEY (или CLOUDFRONT_PRIVATE_KEY_SECRET из Secrets Manager). Без CloudFront — пропустите; конфликта с S3 нет.
Cookie domain
| Variable | Default | Description |
|---|---|---|
COOKIE_DOMAIN | empty | Scope of the session cookie |
- Empty: cookie только на exact host (single-host deployments)
.example.com: cookie shared между subdomains (app.example.comиapi.example.com— одна sign-in session)- Warning: не может быть IP (бrowsers игнорируют)
Restricting who can sign up
Три слоя allowlist по приоритету. Если любой слой non-empty, email вне списка отклоняется — даже ALLOW_SIGNUP=true не перебивает.
| Variable | Default | Description |
|---|---|---|
ALLOWED_EMAILS | empty | Explicit email allowlist (comma-separated). When non-empty, only listed emails can sign up |
ALLOWED_EMAIL_DOMAINS | empty | Domain allowlist (comma-separated). When non-empty, only listed domains can sign up |
ALLOW_SIGNUP | true | Signup master switch. Set false to disable signup entirely |
Контринтуитивно: ALLOWED_EMAIL_DOMAINS=company.io + ALLOW_SIGNUP=true не значит «company.io или все» — только company.io. Allowlist layers — AND semantics; дерево решений — Sign-in and signup configuration → Signup allowlists.
Invite flows не проверяют signup allowlist — но invitee должен войти перед accept. Если аккаунт Multica уже есть (другой workspace), accept без allowlist; если never signed up, первый шаг sign-in (verification code) проходит allowlist — email, отклонённый ALLOW_SIGNUP=false или ALLOWED_EMAILS / ALLOWED_EMAIL_DOMAINS, не завершит signup и не примет invite.
Locking down workspace creation
ALLOW_SIGNUP=false блокирует новые аккаунты, но не блокирует signed-in user создать workspace через POST /api/workspaces. На self-hosted, где admin видит каждый issue, repo и agent, задайте DISABLE_WORKSPACE_CREATION=true.
| Variable | Default | Description |
|---|---|---|
DISABLE_WORKSPACE_CREATION | false | When true, every call to POST /api/workspaces returns 403 workspace creation is disabled for this instance. The web UI hides every "Create workspace" affordance via /api/config. There is no role/owner exception — the gate is global per instance |
Рекомендуемая bootstrap sequence:
- Старт с
DISABLE_WORKSPACE_CREATIONunset (default). - Admin создаёт shared workspace.
DISABLE_WORKSPACE_CREATION=true, restart backend. Дальше — только invitation.
Если нужен ALLOW_SIGNUP=true для invited users с первым verification code, сочетайте DISABLE_WORKSPACE_CREATION=true с ALLOWED_EMAIL_DOMAINS / ALLOWED_EMAILS. ALLOW_SIGNUP=false дополнительно блокирует pending invitees без аккаунта — только если все members уже имеют Multica account.
Rate limiting (optional Redis)
Публичные auth endpoints — /auth/send-code, /auth/verify-code, /auth/google — fixed-window rate limit per IP. Limiter на Redis. Без REDIS_URL middleware no-op (fail-open), в логах rate limiting disabled: REDIS_URL not configured.
| Variable | Default | Description |
|---|---|---|
REDIS_URL | empty | Redis connection URL (for example redis://localhost:6379/0). When unset, rate limiting on auth endpoints is disabled. The same Redis is also used by the realtime hub fan-out, the PAT cache, and the daemon-token cache — they all fall back to in-memory / direct-DB mode when unset |
RATE_LIMIT_AUTH | 5 | Max requests per IP per minute against /auth/send-code and /auth/google |
RATE_LIMIT_AUTH_VERIFY | 20 | Max requests per IP per minute against /auth/verify-code |
RATE_LIMIT_TRUSTED_PROXIES | empty | Comma-separated CIDRs whose X-Forwarded-For header the limiter is allowed to trust. Empty (the default) means never trust XFF — the limiter only uses the direct connection's RemoteAddr |
При превышении limit: 429 Too Many Requests, Retry-After: 60, body {"error":"too many requests"}.
За reverse proxy задайте RATE_LIMIT_TRUSTED_PROXIES. Иначе все пользователи делят IP proxy с точки зрения backend, весь deployment в одном bucket, /auth/send-code — 5 req/min на весь site. Типично: 127.0.0.1/32,::1/128 для Caddy / Nginx на том же host; published ranges CDN для Cloudflare / ALB / CloudFront. Только IP, чей RemoteAddr внутри CIDR, могут использовать X-Forwarded-For для client id.
Отдельный RATE_LIMIT_TRUSTED_PROXIES не то же, что MULTICA_TRUSTED_PROXIES (autopilot-webhook limiter /api/webhooks/autopilots/{token}). Каждый limiter парсит свой список — за proxy задайте оба.
Daemon tuning parameters
Daemon на локальной машине пользователя; config тоже из local env. Частые переменные:
| Variable | Default | Description |
|---|---|---|
MULTICA_SERVER_URL | ws://localhost:8080/ws | Server address (self-host: replace with your domain) |
MULTICA_DAEMON_HEARTBEAT_INTERVAL | 15s | Heartbeat interval |
MULTICA_DAEMON_POLL_INTERVAL | 3s | Task polling interval |
MULTICA_DAEMON_MAX_CONCURRENT_TASKS | 20 | Max concurrent tasks |
MULTICA_<PROVIDER>_PATH | matches the CLI name | Path to each AI coding tool's executable (for example MULTICA_CLAUDE_PATH) |
MULTICA_<PROVIDER>_MODEL | empty | Default model for each AI coding tool |
Подробнее — Daemon and runtimes.
Frontend access control
| Variable | Default | Description |
|---|---|---|
FRONTEND_ORIGIN | empty | Frontend address. Invite email links, the CORS allowlist, and the cookie domain are all derived from this. When unset, invite email links fall back to the hosted domain https://app.multica.ai — self-host must set this explicitly |
MULTICA_APP_URL | empty | Frontend URL for CLI login flow. Also used by the web UI to show self-host daemon setup commands with your app domain; for same-origin deployments this is also used as daemon server_url when MULTICA_PUBLIC_URL is unset |
MULTICA_PUBLIC_URL | empty | Public API URL, without trailing slash. Used for autopilot webhook URLs and by the web UI as the daemon server_url |
CORS_ALLOWED_ORIGINS | empty | Additional allowed CORS origins (comma-separated) |
ALLOWED_ORIGINS | empty | WebSocket-specific origin allowlist (comma-separated); when unset, fallback order is CORS_ALLOWED_ORIGINS → FRONTEND_ORIGIN → localhost:3000/5173/5174 |
Пустой FRONTEND_ORIGIN — две тихие поломки: (1) invite links ведут на https://app.multica.ai, клик не возвращает на self-hosted instance; (2) WebSocket Origin checks fallback на localhost:3000 / 5173 / 5174, production WebSocket отклоняются — frontend «теряет realtime updates».
GitHub integration
GitHub PR ↔ issue integration требует две переменные. Обе — Connect GitHub в Settings и приём webhooks. Ещё две optional — имя connected account при install.
| Variable | Default | Description |
|---|---|---|
GITHUB_APP_SLUG | empty | The slug of your GitHub App (the tail of https://github.com/apps/<slug>). Drives the Settings → GitHub install button URL |
GITHUB_WEBHOOK_SECRET | empty | The Webhook secret you set on the GitHub App. Used for HMAC-SHA256 verification of every pull_request / installation delivery, and as the HMAC key for the setup-callback state token |
GITHUB_APP_ID | empty | Optional. Numeric App ID from the App's settings page. Combined with GITHUB_APP_PRIVATE_KEY, lets the setup callback fetch the connected account name from GitHub immediately on install |
GITHUB_APP_PRIVATE_KEY | empty | Optional. Full PEM block of the App's RSA private key (including -----BEGIN/END----- lines, newlines preserved). Used to mint the short-lived JWT GitHub requires for App-authenticated REST calls |
Если одна из required не задана:
- Connect GitHub в Settings → GitHub disabled и hint «not configured» для admins.
/api/webhooks/github→503 github webhooks not configured— Multica не обрабатывает без secret.
Если optional GITHUB_APP_ID / GITHUB_APP_PRIVATE_KEY не заданы:
- Карточка кратко показывает
Connected to unknownпосле install; обновление до real org/user name после webhookinstallation.created(обычно секунды) и realtime update на открытой вкладке Settings → GitHub.
Note: GITHUB_WEBHOOK_SECRET же подписывает state token install flow — один secret для оператора. Это не GitHub App Client secret. См. GitHub integration → Self-host setup.
Usage analytics
По умолчанию server шлёт analytics в официальный PostHog Multica. Opt-out: ANALYTICS_DISABLED=true.
| Variable | Default | Description |
|---|---|---|
ANALYTICS_DISABLED | false | Set true to disable backend analytics entirely |
POSTHOG_API_KEY | built-in default key | Set when pointing at your own PostHog instance |
POSTHOG_HOST | https://us.i.posthog.com | Change to your own host if you self-host PostHog |
Далее
- Sign-in and signup configuration — настройка auth variables и типичные ловушки
- GitHub integration — GitHub App для
GITHUB_APP_SLUG/GITHUB_WEBHOOK_SECRET - Troubleshooting — симптомы и fixes misconfiguration
- Daemon and runtimes — что делают
MULTICA_DAEMON_*