mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
* feat(plugins): expose globally versioned public API Co-authored-by: multica-agent <[email protected]> * fix(plugins): proxy same-origin public API Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]>
219 lines
12 KiB
YAML
219 lines
12 KiB
YAML
# Self-hosting Docker Compose — starts PostgreSQL, backend, and frontend.
|
|
#
|
|
# Services bind to 127.0.0.1 only. For cross-machine or public access, front
|
|
# them with a reverse proxy (Caddy / nginx / Cloudflare Tunnel) that terminates
|
|
# TLS and forwards to 127.0.0.1:8080 (backend) and 127.0.0.1:3000 (frontend).
|
|
# Do NOT change these bindings to 0.0.0.0 — Docker bypasses host firewalls
|
|
# (UFW/iptables) by default, so the raw ports would be exposed to the internet
|
|
# while any deployment credentials remain weak. See:
|
|
# apps/docs/content/docs/self-host-quickstart.mdx
|
|
#
|
|
# Usage:
|
|
# cp .env.example .env
|
|
# # Edit .env — generate JWT_SECRET with `openssl rand -hex 32` (required)
|
|
# docker compose -f docker-compose.selfhost.yml up -d
|
|
#
|
|
# Frontend: http://localhost:${FRONTEND_PORT:-3000}
|
|
# Backend: http://localhost:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}
|
|
#
|
|
# The published values above are HOST ports; the containers always listen on
|
|
# 8080 / 3000 internally, so changing them never needs a rebuild. PORT is the
|
|
# variable to edit; BACKEND_PORT, API_PORT and SERVER_PORT are optional aliases
|
|
# that override it in that order. Keep this alias order identical to Makefile
|
|
# and scripts/local-env.sh. The web dev fallback intentionally omits PORT
|
|
# because Next uses that variable for its own frontend listener.
|
|
#
|
|
# Note that *which source* wins differs per entry point — Compose lets the
|
|
# calling environment outrank this file, while make lets the included env file
|
|
# outrank the environment. Nothing re-derives the published port from the inputs
|
|
# any more: `make selfhost` (via scripts/selfhost-wait.sh) and both installers
|
|
# read it back with `docker compose port`, so the health check and the printed
|
|
# URL always match what was actually published.
|
|
|
|
name: multica
|
|
|
|
services:
|
|
postgres:
|
|
image: pgvector/pgvector:pg17
|
|
environment:
|
|
POSTGRES_DB: ${POSTGRES_DB:-multica}
|
|
POSTGRES_USER: ${POSTGRES_USER:-multica}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-multica}
|
|
volumes:
|
|
- pgdata:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test:
|
|
[
|
|
"CMD-SHELL",
|
|
"pg_isready -U ${POSTGRES_USER:-multica} -d ${POSTGRES_DB:-multica}",
|
|
]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
backend:
|
|
image: ${MULTICA_BACKEND_IMAGE:-ghcr.io/multica-ai/multica-backend}:${MULTICA_IMAGE_TAG:-latest}
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
ports:
|
|
- "127.0.0.1:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}:8080"
|
|
volumes:
|
|
- backend_uploads:/app/data/uploads
|
|
environment:
|
|
DATABASE_URL: postgres://${POSTGRES_USER:-multica}:${POSTGRES_PASSWORD:-multica}@postgres:5432/${POSTGRES_DB:-multica}?sslmode=disable
|
|
PORT: "8080"
|
|
METRICS_ADDR: ${METRICS_ADDR:-}
|
|
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set to a strong random value — generate one with 'openssl rand -hex 32'}
|
|
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:3000}
|
|
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
|
|
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
|
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:[email protected]}
|
|
SMTP_HOST: ${SMTP_HOST:-}
|
|
SMTP_PORT: ${SMTP_PORT:-25}
|
|
SMTP_USERNAME: ${SMTP_USERNAME:-}
|
|
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
|
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
|
|
SMTP_TLS: ${SMTP_TLS:-}
|
|
SMTP_TLS_INSECURE: ${SMTP_TLS_INSECURE:-false}
|
|
SMTP_EHLO_NAME: ${SMTP_EHLO_NAME:-}
|
|
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
|
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
|
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:3000/auth/callback}
|
|
S3_BUCKET: ${S3_BUCKET:-}
|
|
S3_REGION: ${S3_REGION:-us-west-2}
|
|
AWS_ENDPOINT_URL: ${AWS_ENDPOINT_URL:-}
|
|
S3_USE_PATH_STYLE: ${S3_USE_PATH_STYLE:-}
|
|
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
|
|
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
|
|
ATTACHMENT_DOWNLOAD_MODE: ${ATTACHMENT_DOWNLOAD_MODE:-auto}
|
|
ATTACHMENT_DOWNLOAD_URL_TTL: ${ATTACHMENT_DOWNLOAD_URL_TTL:-30m}
|
|
CLOUDFRONT_DOMAIN: ${CLOUDFRONT_DOMAIN:-}
|
|
CLOUDFRONT_KEY_PAIR_ID: ${CLOUDFRONT_KEY_PAIR_ID:-}
|
|
CLOUDFRONT_PRIVATE_KEY: ${CLOUDFRONT_PRIVATE_KEY:-}
|
|
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
|
|
APP_ENV: ${APP_ENV:-production}
|
|
MULTICA_DEV_VERIFICATION_CODE: ${MULTICA_DEV_VERIFICATION_CODE:-}
|
|
MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:3000}
|
|
MULTICA_DATABASE_STARTUP_TIMEOUT: ${MULTICA_DATABASE_STARTUP_TIMEOUT:-3m}
|
|
MULTICA_DATABASE_CONNECT_TIMEOUT: ${MULTICA_DATABASE_CONNECT_TIMEOUT:-5s}
|
|
MULTICA_SHUTDOWN_HOLD_DURATION: ${MULTICA_SHUTDOWN_HOLD_DURATION:-}
|
|
MULTICA_RUNTIME_RECONNECT_GRACE: ${MULTICA_RUNTIME_RECONNECT_GRACE:-}
|
|
MULTICA_TASK_QUEUED_TTL: ${MULTICA_TASK_QUEUED_TTL:-}
|
|
ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true}
|
|
ALLOWED_EMAILS: ${ALLOWED_EMAILS:-}
|
|
ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-}
|
|
DISABLE_WORKSPACE_CREATION: ${DISABLE_WORKSPACE_CREATION:-}
|
|
# Managed entitlement policy is explicitly opt-in. The false/empty
|
|
# defaults keep self-hosted installs on the legacy path with no Cloud
|
|
# request and no quota-table access.
|
|
MULTICA_ENTITLEMENT_POLICY_ENABLED: ${MULTICA_ENTITLEMENT_POLICY_ENABLED:-false}
|
|
MULTICA_ENTITLEMENT_POLICY_URL: ${MULTICA_ENTITLEMENT_POLICY_URL:-}
|
|
MULTICA_ENTITLEMENT_SERVICE_TOKEN: ${MULTICA_ENTITLEMENT_SERVICE_TOKEN:-}
|
|
MULTICA_ENTITLEMENT_POLICY_TIMEOUT: ${MULTICA_ENTITLEMENT_POLICY_TIMEOUT:-3s}
|
|
MULTICA_ENTITLEMENT_STALE_GRACE: ${MULTICA_ENTITLEMENT_STALE_GRACE:-15m}
|
|
MULTICA_ENTITLEMENT_EMERGENCY_DISABLED: ${MULTICA_ENTITLEMENT_EMERGENCY_DISABLED:-false}
|
|
GITHUB_APP_SLUG: ${GITHUB_APP_SLUG:-}
|
|
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-}
|
|
# App API credentials. The pull_request webhook is what mirrors a PR
|
|
# onto a card; these are what let the server authenticate as the App
|
|
# and pull the CI / mergeability snapshot that enriches it, plus
|
|
# Settings -> Repositories -> Choose from GitHub. Left unset,
|
|
# ghsnapshot.NewClientFromEnv returns a nil client and the enrichment
|
|
# degrades off silently - no error and no log line to notice.
|
|
# GITHUB_APP_PRIVATE_KEY is a PEM block: double-quote it in .env so
|
|
# its newlines survive into the container.
|
|
GITHUB_APP_ID: ${GITHUB_APP_ID:-}
|
|
GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
|
|
# Public URL the API is reachable at from the open internet, no
|
|
# trailing slash. Used to mint absolute webhook URLs for autopilot
|
|
# webhook triggers. Leave unset behind a same-origin reverse proxy
|
|
# (e.g. 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 on misconfigured proxies.
|
|
MULTICA_PUBLIC_URL: ${MULTICA_PUBLIC_URL:-}
|
|
# Surface HTML must use a second, cookie-free browser origin routed to
|
|
# this backend. Both settings are optional only in the sense that leaving
|
|
# either empty disables plugin surfaces rather than weakening isolation.
|
|
MULTICA_PLUGIN_SECRET_KEY: ${MULTICA_PLUGIN_SECRET_KEY:-}
|
|
MULTICA_PLUGIN_SURFACE_ORIGIN: ${MULTICA_PLUGIN_SURFACE_ORIGIN:-}
|
|
MULTICA_PLUGIN_API_URL: ${MULTICA_PLUGIN_API_URL:-}
|
|
MULTICA_PLUGIN_DIR: ${MULTICA_PLUGIN_DIR:-}
|
|
# Comma-separated CIDRs whose source IP is allowed to set
|
|
# X-Forwarded-For / X-Real-IP for the webhook per-IP rate limiter.
|
|
# Empty default = headers ignored, RemoteAddr used. Set e.g.
|
|
# "127.0.0.1/32" when running behind a same-host reverse proxy.
|
|
MULTICA_TRUSTED_PROXIES: ${MULTICA_TRUSTED_PROXIES:-}
|
|
# Server-side LLM layer, backing internal helpers such as chat title
|
|
# generation. Both the API key and the base URL empty = layer disabled,
|
|
# and callers fall back silently rather than failing. MULTICA_LLM_MAX_RETRIES
|
|
# is the retry budget: unset = 2, 0 = disabled, 1-5 = an exact ceiling.
|
|
# Anything else fails the boot instead of being silently corrected.
|
|
MULTICA_LLM_API_KEY: ${MULTICA_LLM_API_KEY:-}
|
|
MULTICA_LLM_BASE_URL: ${MULTICA_LLM_BASE_URL:-}
|
|
MULTICA_LLM_DEFAULT_MODEL: ${MULTICA_LLM_DEFAULT_MODEL:-}
|
|
MULTICA_LLM_MAX_RETRIES: ${MULTICA_LLM_MAX_RETRIES:-}
|
|
# Lark / Feishu bot integration. MULTICA_LARK_SECRET_KEY is the
|
|
# opt-in: unset = integration disabled. Mainland 飞书 and international
|
|
# Lark are auto-detected per installation and served side by side, so
|
|
# the two base-URL knobs should normally stay EMPTY. They are optional
|
|
# deployment-wide overrides that force every installation onto one host
|
|
# (proxy / mock / single-cloud staging). Upgrading from a setup that
|
|
# used https://open.larksuite.com here? The server relabels existing
|
|
# installs to region=lark on first boot, then you can clear them.
|
|
# See docs/lark-bot-integration.
|
|
MULTICA_LARK_SECRET_KEY: ${MULTICA_LARK_SECRET_KEY:-}
|
|
MULTICA_LARK_HTTP_BASE_URL: ${MULTICA_LARK_HTTP_BASE_URL:-}
|
|
MULTICA_LARK_CALLBACK_BASE_URL: ${MULTICA_LARK_CALLBACK_BASE_URL:-}
|
|
# Slack bot integration. MULTICA_SLACK_SECRET_KEY is the opt-in: unset =
|
|
# integration disabled. It decrypts the per-installation bot/app tokens,
|
|
# which are brought by each workspace via OAuth/BYO and stored encrypted
|
|
# in the database, so this single deployment-wide key is all the operator
|
|
# needs to set here.
|
|
MULTICA_SLACK_SECRET_KEY: ${MULTICA_SLACK_SECRET_KEY:-}
|
|
# Self-hosted Git provider integration (Forgejo / Gitea / GitLab). This
|
|
# is a self-host-only feature, so the compose file turns it on by default;
|
|
# the managed cloud leaves it unset (off). It still needs a valid
|
|
# MULTICA_VCS_SECRET_KEY below to actually work.
|
|
MULTICA_VCS_INTEGRATION_ENABLED: ${MULTICA_VCS_INTEGRATION_ENABLED:-true}
|
|
# VCS integration at-rest encryption key for token-based providers
|
|
MULTICA_VCS_SECRET_KEY: ${MULTICA_VCS_SECRET_KEY:-}
|
|
# WeCom smart-bot integration. MULTICA_WECOM_SECRET_KEY is the
|
|
# opt-in: unset = integration disabled. It encrypts each installation's
|
|
# smart-bot secret at rest. The server needs it to unseal a bot's
|
|
# subscribe credentials at connection time, and to seal the secret when
|
|
# an installation is created from the WeCom settings tab.
|
|
MULTICA_WECOM_SECRET_KEY: ${MULTICA_WECOM_SECRET_KEY:-}
|
|
# Comma-separated ranges the inbound media fetcher may dial even though
|
|
# they look reserved. Empty (the default) keeps the SSRF guard as strict
|
|
# as it ships; set it only behind a fake-IP proxy whose pool would
|
|
# otherwise get every attachment refused. See .env.example.
|
|
MULTICA_WECOM_MEDIA_ALLOW_CIDRS: ${MULTICA_WECOM_MEDIA_ALLOW_CIDRS:-}
|
|
# 1 = log every inbound and outbound WeCom frame, including the first
|
|
# 120 runes of each message body, so a real-device session can be
|
|
# checked against the server afterwards. Off unless set; turn it on only
|
|
# for a debugging session and unset it when that session ends, because
|
|
# what it records is user message content.
|
|
MULTICA_WECOM_TRACE: ${MULTICA_WECOM_TRACE:-}
|
|
restart: unless-stopped
|
|
|
|
frontend:
|
|
image: ${MULTICA_WEB_IMAGE:-ghcr.io/multica-ai/multica-web}:${MULTICA_IMAGE_TAG:-latest}
|
|
depends_on:
|
|
- backend
|
|
ports:
|
|
- "127.0.0.1:${FRONTEND_PORT:-3000}:3000"
|
|
environment:
|
|
HOSTNAME: "0.0.0.0"
|
|
REMOTE_API_URL: ${REMOTE_API_URL:-http://backend:8080}
|
|
DOCS_URL: ${DOCS_URL:-}
|
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-}
|
|
NEXT_PUBLIC_WS_URL: ${NEXT_PUBLIC_WS_URL:-}
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
pgdata:
|
|
backend_uploads:
|