Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,40 @@ BUBLIK_DOCKER_MCP_PORT=8001
EMAIL_HOST=127.0.0.1
EMAIL_PORT=1025

########################################################
# AI Provider API Keys
# In ai.json, use "api_key": "${env:AI_OPENAI_API_KEY}" to read a
# deployment environment variable, or "${settings:AI_OPENAI_API_KEY}"
# to read a Django setting. Add each ${env:AI_...} variable needed by
# your configured providers to the deployment .env file.
# Examples (add YOUR keys, not these placeholders):
# AI_OPENAI_API_KEY=sk-...
# AI_ANTHROPIC_API_KEY=sk-ant-...
# AI_GEMINI_API_KEY=...
# AI_DEEPSEEK_API_KEY=...
# AI_GROQ_API_KEY=...
# AI_MISTRAL_API_KEY=...
########################################################

########################################################
# AI chat file storage (S3-compatible; bundled SeaweedFS by default)
# The bundled SeaweedFS binds to 127.0.0.1 only; change the credentials
# if you expose it. Set S3_PUBLIC_ENDPOINT_URL only when the S3
# endpoint is browser-reachable (e.g. real AWS S3) to switch file
# downloads from proxying to presigned-URL redirects. On real S3 a
# lifecycle rule on the chat/ prefix can replace the best-effort
# object cleanup performed on thread deletion.
########################################################
S3_ENDPOINT_URL=http://127.0.0.1:8333
# S3_PUBLIC_ENDPOINT_URL=
S3_ACCESS_KEY=bublik
S3_SECRET_KEY=bublik-secret-key
S3_BUCKET=bublik-chat-files
S3_REGION=us-east-1
S3_PRESIGN_EXPIRY=300
CHAT_FILE_MAX_SIZE=20971520
BUBLIK_DOCKER_SEAWEEDFS_S3_PORT=8333

########################################################
# Internal configuration, don't touch, unless you know what you are doing
########################################################
Expand Down Expand Up @@ -95,6 +129,9 @@ DB_USER=bublik
ANALYTICS_ENABLED=False
ANALYTICS_DB_PATH=/app/bublik/logs/analytics.sqlite3

# Optional AI chat sidebar (disabled by default)
CHAT_ENABLED=False

EMAIL_ADMINS=admin@bublik.com
EMAIL_FROM=admin@bublik.com
EMAIL_USE_TLS=False
Expand Down
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ RUN apt-get update \
libyaml-dev \
libssl-dev \
libglib2.0-dev \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libgdk-pixbuf-2.0-0 \
fonts-dejavu-core \
fonts-liberation \
git \
&& rm -rf /var/lib/apt/lists/* \
&& cpan -T JSON
Expand Down
52 changes: 51 additions & 1 deletion docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
- DOCS_URL=${DOCS_URL}
command: python manage.py run_mcp_server --host ${BUBLIK_DOCKER_MCP_HOST} --port ${BUBLIK_DOCKER_MCP_PORT}
network_mode: host
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- PER_CONF_DIR=${PER_CONF_DIR}
Expand Down Expand Up @@ -36,6 +37,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand Down Expand Up @@ -90,9 +92,10 @@ services:
args:
- URL_PREFIX=${URL_PREFIX}
- DOCS_URL=${DOCS_URL}
command: python manage.py runserver ${BUBLIK_DOCKER_DJANGO_HOST}:${BUBLIK_DOCKER_DJANGO_PORT}
command: uvicorn bublik.asgi:application --host ${BUBLIK_DOCKER_DJANGO_HOST} --port ${BUBLIK_DOCKER_DJANGO_PORT} --reload
entrypoint: ./entrypoint-django.sh
network_mode: host
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- BUBLIK_DOCKER_PROXY_EXT_PORT=${BUBLIK_DOCKER_PROXY_EXT_PORT}
Expand Down Expand Up @@ -121,6 +124,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand All @@ -145,10 +149,19 @@ services:
- HOST_UID=${HOST_UID:-1000}
- HOST_GID=${HOST_GID:-1000}
- UMASK=${UMASK:-022}
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://127.0.0.1:8333}
- S3_PUBLIC_ENDPOINT_URL=${S3_PUBLIC_ENDPOINT_URL:-}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-bublik}
- S3_SECRET_KEY=${S3_SECRET_KEY:-bublik-secret-key}
- S3_BUCKET=${S3_BUCKET:-bublik-chat-files}
- S3_REGION=${S3_REGION:-us-east-1}
- S3_PRESIGN_EXPIRY=${S3_PRESIGN_EXPIRY:-300}
- CHAT_FILE_MAX_SIZE=${CHAT_FILE_MAX_SIZE:-20971520}
depends_on:
- db
- redis
- rabbitmq
- seaweedfs
volumes:
- static_data:/app/bublik/bublik/representation/static
- ${BUBLIK_DOCKER_DATA_DIR}/django-logs:/app/bublik/logs
Expand All @@ -171,6 +184,38 @@ services:
ports:
- ${REDIS_HOST}:${REDIS_PORT}:6379

seaweedfs:
container_name: ${COMPOSE_PROJECT_NAME}-seaweedfs
image: chrislusf/seaweedfs:4.40
restart: unless-stopped
# Keep all SeaweedFS components on loopback; only its S3 API is used.
network_mode: host
command: >
mini
-dir=/data
-ip=127.0.0.1
-ip.bind=127.0.0.1
-s3.port=${BUBLIK_DOCKER_SEAWEEDFS_S3_PORT:-8333}
-s3.port.iceberg=0
-webdav=false
-admin.ui=false
environment:
- AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY:-bublik}
- AWS_SECRET_ACCESS_KEY=${S3_SECRET_KEY:-bublik-secret-key}
- S3_BUCKET=${S3_BUCKET:-bublik-chat-files}
volumes:
- seaweedfs_data:/data
healthcheck:
test:
[
"CMD-SHELL",
"curl --fail http://127.0.0.1:${BUBLIK_DOCKER_SEAWEEDFS_S3_PORT:-8333}/status || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s

rabbitmq:
container_name: ${COMPOSE_PROJECT_NAME}-rabbitmq
image: rabbitmq:3-management
Expand Down Expand Up @@ -198,6 +243,7 @@ services:
- rabbitmq
volumes:
- ${BUBLIK_DOCKER_DATA_DIR}/django-logs:/app/bublik/logs
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- BUBLIK_DOCKER_PROXY_EXT_PORT=${BUBLIK_DOCKER_PROXY_EXT_PORT}
Expand All @@ -221,6 +267,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand Down Expand Up @@ -263,6 +310,7 @@ services:
- URL_PREFIX=${URL_PREFIX}
- DOCS_URL=${DOCS_URL}
command: celery -A ${CELERY_APP} flower --persistent=${FLOWER_PERSISTENT} --url_prefix=${FLOWER_URL_PREFIX}
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- BUBLIK_DOCKER_PROXY_EXT_PORT=${BUBLIK_DOCKER_PROXY_EXT_PORT}
Expand All @@ -286,6 +334,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand Down Expand Up @@ -441,4 +490,5 @@ volumes:
db_data:
flower_data:
static_data:
seaweedfs_data:
static_te_html:
52 changes: 51 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ services:
image: ${DOCKER_REGISTRY:-ghcr.io}/${DOCKER_ORG:-ts-factory}/${RUNNER_IMAGE_NAME:-bublik-runner}:${IMAGE_TAG:-latest}
command: python manage.py run_mcp_server --host ${BUBLIK_DOCKER_MCP_HOST} --port ${BUBLIK_DOCKER_MCP_PORT}
network_mode: host
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- PER_CONF_DIR=${PER_CONF_DIR}
Expand Down Expand Up @@ -30,6 +31,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand Down Expand Up @@ -70,7 +72,8 @@ services:
extra_hosts:
- host.docker.internal:host-gateway
command: >
gunicorn bublik.wsgi:application
gunicorn bublik.asgi:application
--worker-class uvicorn_worker.UvicornWorker
--bind ${BUBLIK_DOCKER_DJANGO_HOST}:${BUBLIK_DOCKER_DJANGO_PORT}
--limit-request-line 0
--chdir ${BUBLIK_SRC}
Expand All @@ -81,6 +84,7 @@ services:
--access-logfile ${BUBLIK_ACCESS_LOG}
--log-level info
--name "${BUBLIK_WEB_NAME}"
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- BUBLIK_DOCKER_PROXY_EXT_PORT=${BUBLIK_DOCKER_PROXY_EXT_PORT}
Expand Down Expand Up @@ -109,6 +113,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand All @@ -131,11 +136,20 @@ services:
- HOST_UID=${HOST_UID:-1000}
- HOST_GID=${HOST_GID:-1000}
- UMASK=${UMASK:-022}
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://127.0.0.1:8333}
- S3_PUBLIC_ENDPOINT_URL=${S3_PUBLIC_ENDPOINT_URL:-}
- S3_ACCESS_KEY=${S3_ACCESS_KEY:-bublik}
- S3_SECRET_KEY=${S3_SECRET_KEY:-bublik-secret-key}
- S3_BUCKET=${S3_BUCKET:-bublik-chat-files}
- S3_REGION=${S3_REGION:-us-east-1}
- S3_PRESIGN_EXPIRY=${S3_PRESIGN_EXPIRY:-300}
- CHAT_FILE_MAX_SIZE=${CHAT_FILE_MAX_SIZE:-20971520}
expose:
- ${BUBLIK_DOCKER_DJANGO_PORT}
entrypoint: ./entrypoint-django.sh
depends_on:
- redis
- seaweedfs
volumes:
- ${BUBLIK_DOCKER_DATA_DIR}/django-logs:/app/bublik/logs
- static_data:/app/bublik/bublik/representation/static
Expand All @@ -160,6 +174,37 @@ services:
ports:
- ${REDIS_HOST}:${REDIS_PORT}:6379

seaweedfs:
image: chrislusf/seaweedfs:4.40
restart: unless-stopped
# Keep all SeaweedFS components on loopback; only its S3 API is used.
network_mode: host
command: >
mini
-dir=/data
-ip=127.0.0.1
-ip.bind=127.0.0.1
-s3.port=${BUBLIK_DOCKER_SEAWEEDFS_S3_PORT:-8333}
-s3.port.iceberg=0
-webdav=false
-admin.ui=false
environment:
- AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY:-bublik}
- AWS_SECRET_ACCESS_KEY=${S3_SECRET_KEY:-bublik-secret-key}
- S3_BUCKET=${S3_BUCKET:-bublik-chat-files}
volumes:
- seaweedfs_data:/data
healthcheck:
test:
[
"CMD-SHELL",
"curl --fail http://127.0.0.1:${BUBLIK_DOCKER_SEAWEEDFS_S3_PORT:-8333}/status || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s

rabbitmq:
image: rabbitmq:4.2-management
ports:
Expand All @@ -176,6 +221,7 @@ services:
image: ${DOCKER_REGISTRY:-ghcr.io}/${DOCKER_ORG:-ts-factory}/${RUNNER_IMAGE_NAME:-bublik-runner}:${IMAGE_TAG:-latest}
command: celery -A ${CELERY_APP} worker --max-tasks-per-child ${CELERYD_MAX_TASKS_PER_CHILD} --concurrency 1 --prefetch-multiplier 1 -l ${CELERY_LOG_LEVEL}
restart: unless-stopped
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- BUBLIK_DOCKER_PROXY_EXT_PORT=${BUBLIK_DOCKER_PROXY_EXT_PORT}
Expand All @@ -199,6 +245,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand Down Expand Up @@ -240,6 +287,7 @@ services:
network_mode: host
command: celery -A ${CELERY_APP} flower --persistent=${FLOWER_PERSISTENT} --url_prefix=${FLOWER_URL_PREFIX} --port=${FLOWER_PORT}
restart: unless-stopped
env_file: .env
environment:
- BUBLIK_DOCKER_PROXY_PORT=${BUBLIK_DOCKER_PROXY_PORT}
- BUBLIK_DOCKER_PROXY_EXT_PORT=${BUBLIK_DOCKER_PROXY_EXT_PORT}
Expand All @@ -263,6 +311,7 @@ services:
- DB_PORT=${DB_PORT}
- ANALYTICS_ENABLED=${ANALYTICS_ENABLED:-False}
- ANALYTICS_DB_PATH=${ANALYTICS_DB_PATH:-/app/bublik/logs/analytics.sqlite3}
- CHAT_ENABLED=${CHAT_ENABLED:-False}
- CELERY_APP=${CELERY_APP}
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND}
- CELERY_ACCEPT_CONTENT=${CELERY_ACCEPT_CONTENT}
Expand Down Expand Up @@ -369,4 +418,5 @@ volumes:
static_data:
flower_data:
redis_data:
seaweedfs_data:
static_te_html:
22 changes: 22 additions & 0 deletions docker-settings.py.template
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ INSTALLED_APPS = [

ANALYTICS_ENABLED = os.getenv('ANALYTICS_ENABLED', '0').lower() in ('1', 'true', 'yes')
ANALYTICS_DB_PATH = os.getenv('ANALYTICS_DB_PATH', '/app/bublik/logs/analytics.sqlite3')
CHAT_ENABLED = os.getenv('CHAT_ENABLED', '0').lower() in ('1', 'true', 'yes')

if ANALYTICS_ENABLED:
INSTALLED_APPS.append('bublik.analytics')
Expand All @@ -90,6 +91,11 @@ PREFIX = os.getenv('URL_PREFIX', '')
BUBLIK_DOCKER_PROXY_PORT = os.getenv('BUBLIK_DOCKER_PROXY_PORT', '80')
URL_PREFIX = f'{PREFIX}'[1:]

_bublik_fqdn = os.getenv('BUBLIK_FQDN', '')
if _bublik_fqdn and BUBLIK_DOCKER_PROXY_PORT not in ('80', '443'):
_bublik_fqdn = f'{_bublik_fqdn.rstrip("/")}:{BUBLIK_DOCKER_PROXY_PORT}'
BUBLIK_FQDN = _bublik_fqdn

ROOT_URLCONF = 'bublik.urls'

TEMPLATES = [
Expand All @@ -111,6 +117,7 @@ TEMPLATES = [
MANAGEMENT_COMMANDS_LOG = os.getenv('MANAGEMENT_COMMANDS_LOG')

WSGI_APPLICATION = 'bublik.wsgi.application'
ASGI_APPLICATION = 'bublik.asgi.application'
DJANGO_LOG_LEVEL = os.getenv('DJANGO_LOG_LEVEL', 'INFO')
CELERY_LOG_LEVEL = os.getenv('CELERYD_LOG_LEVEL', 'INFO')
LOGGING = {
Expand Down Expand Up @@ -218,6 +225,7 @@ if ANALYTICS_ENABLED:

REDIS_PORT = os.getenv('REDIS_PORT', '6379')
REDIS_HOST = os.getenv('REDIS_HOST', '127.0.0.1')
REDIS_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/0'

CACHES = {
'default': {
Expand Down Expand Up @@ -375,6 +383,20 @@ KEY_VALUE_DELIMITER = '='
PERIOD_DELIMITER = '-'
TIMESTAMP_DELIMITER = 's'

# S3-compatible storage for AI-chat generated files (bundled SeaweedFS by default)
S3_ENDPOINT_URL = os.getenv('S3_ENDPOINT_URL', 'http://127.0.0.1:8333')
# Set only when the S3 endpoint is browser-reachable (e.g. real AWS S3);
# downloads are then redirected to presigned URLs instead of proxied.
S3_PUBLIC_ENDPOINT_URL = os.getenv('S3_PUBLIC_ENDPOINT_URL', '')
S3_ACCESS_KEY = os.getenv('S3_ACCESS_KEY', 'bublik')
S3_SECRET_KEY = os.getenv('S3_SECRET_KEY', 'bublik-secret-key')
S3_BUCKET = os.getenv('S3_BUCKET', 'bublik-chat-files')
S3_REGION = os.getenv('S3_REGION', 'us-east-1')
S3_PRESIGN_EXPIRY = int(os.getenv('S3_PRESIGN_EXPIRY', '300'))
# Upload cap for agent-generated files; also bounds the memory used by the
# chat file download endpoint, which reads whole objects.
CHAT_FILE_MAX_SIZE = int(os.getenv('CHAT_FILE_MAX_SIZE', str(20 * 1024 * 1024)))

# Email settings
EMAIL_HOST = os.getenv('EMAIL_HOST', 'mailpit')
EMAIL_PORT = os.getenv('EMAIL_PORT', '1025')
Expand Down
Loading