dfc75a8619
Astrolabe has been extracted to its own repository at github.com/cbcoutinho/astrolabe for independent releases. Changes: - Replace third_party/astrolabe/ directory with git submodule - Remove astrolabe-ci.yml and appstore-build-publish.yml workflows - Remove scripts/bump-astrolabe.sh - Remove Astrolabe sections from bump-version.yml workflow - Remove Astrolabe build steps from test.yml CI workflow - Remove astrolabe volume mount from docker-compose.yml - Simplify astrolabe install hook to always use app store - Update CONTRIBUTING.md to reflect two-component monorepo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
324 lines
13 KiB
YAML
324 lines
13 KiB
YAML
services:
|
|
# Note: MariaDB is external service. You can find more information about the configuration here:
|
|
# https://hub.docker.com/_/mariadb
|
|
db:
|
|
# Note: Check the recommend version here: https://docs.nextcloud.com/server/latest/admin_manual/installation/system_requirements.html#server
|
|
image: docker.io/library/mariadb:lts@sha256:345fa26d595e8c7fe298e0c4098ed400356f502458769c8902229b3437d6da2b
|
|
restart: always
|
|
command: --transaction-isolation=READ-COMMITTED
|
|
volumes:
|
|
- db:/var/lib/mysql
|
|
ports:
|
|
- 127.0.0.1:3306:3306
|
|
environment:
|
|
- MYSQL_ROOT_PASSWORD=password
|
|
- MYSQL_PASSWORD=password
|
|
- MYSQL_DATABASE=nextcloud
|
|
- MYSQL_USER=nextcloud
|
|
|
|
# Note: Redis is an external service. You can find more information about the configuration here:
|
|
# https://hub.docker.com/_/redis
|
|
redis:
|
|
image: docker.io/library/redis:alpine@sha256:0804c395e634e624243387d3c3a9c45fcaca876d313c2c8b52c3fdf9a912dded
|
|
restart: always
|
|
|
|
app:
|
|
image: docker.io/library/nextcloud:32.0.5@sha256:4b66e9bd8cb2c8af5457c1e2606c9937af2fcccbe4f6338956bc5990caec8968
|
|
restart: always
|
|
ports:
|
|
- 127.0.0.1:8080:80
|
|
depends_on:
|
|
- redis
|
|
- db
|
|
- keycloak
|
|
volumes:
|
|
- nextcloud:/var/www/html
|
|
- ./app-hooks:/docker-entrypoint-hooks.d:ro
|
|
# Mount OIDC development directory outside /var/www/html to avoid rsync conflicts
|
|
# The post-installation hook will register /opt/apps as an additional app directory
|
|
#- ./third_party:/opt/apps:ro
|
|
environment:
|
|
- NEXTCLOUD_TRUSTED_DOMAINS=app
|
|
- NEXTCLOUD_ADMIN_USER=admin
|
|
- NEXTCLOUD_ADMIN_PASSWORD=admin
|
|
- MYSQL_PASSWORD=password
|
|
- MYSQL_DATABASE=nextcloud
|
|
- MYSQL_USER=nextcloud
|
|
- MYSQL_HOST=db
|
|
- REDIS_HOST=redis
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -Ss http://localhost/status.php | grep '\"installed\":true' || exit 1"]
|
|
interval: 10s
|
|
timeout: 30s
|
|
retries: 30
|
|
|
|
recipes:
|
|
image: docker.io/library/nginx:alpine@sha256:5878d06ae4c83d73285438255f705bb3f9a736f41cd24876ed25bb33faf76c7d
|
|
restart: always
|
|
volumes:
|
|
- ./tests/fixtures/test_recipe.html:/usr/share/nginx/html/test_recipe.html:ro
|
|
- ./tests/fixtures/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
|
|
unstructured:
|
|
image: downloads.unstructured.io/unstructured-io/unstructured-api:latest@sha256:dcc82dcdd57241a7db2d78fd73c595d399ef4c1265fc8028fbe624a5eb51ecc3
|
|
restart: always
|
|
ports:
|
|
- 127.0.0.1:8002:8000
|
|
# Unstructured API runs on port 8000 internally
|
|
# We expose it on 8002 externally to avoid conflict
|
|
profiles:
|
|
- unstructured
|
|
|
|
mcp:
|
|
build: .
|
|
restart: always
|
|
command: ["--transport", "streamable-http"]
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8000:8000
|
|
- 127.0.0.1:9090:9090
|
|
volumes:
|
|
- mcp-data:/app/data
|
|
environment:
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_USERNAME=admin
|
|
- NEXTCLOUD_PASSWORD=admin
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
|
|
|
# Semantic search configuration (ADR-007, ADR-021)
|
|
#- ENABLE_SEMANTIC_SEARCH=true
|
|
- VECTOR_SYNC_SCAN_INTERVAL=60
|
|
- VECTOR_SYNC_PROCESSOR_WORKERS=1
|
|
|
|
#- LOG_FORMAT=json
|
|
|
|
# Qdrant configuration (three modes):
|
|
# 1. Network mode: Set QDRANT_URL=http://qdrant:6333 (requires qdrant service)
|
|
# 2. In-memory mode: Set QDRANT_LOCATION=:memory: (default if nothing set)
|
|
# 3. Persistent local: Set QDRANT_LOCATION=/app/data/qdrant (stored in mcp-data volume)
|
|
#- QDRANT_LOCATION=/app/data/qdrant # In-memory mode used if not set
|
|
#- QDRANT_URL=http://qdrant:6333 # Uncomment for network mode
|
|
#- QDRANT_API_KEY=${QDRANT_API_KEY:-my_secret_api_key} # Only for network mode
|
|
|
|
# Observability
|
|
#- OTEL_SERVICE_NAME=nextcloud-mcp-docker-compose
|
|
#- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
|
|
|
|
# Collection naming: Auto-generated as {deployment-id}-{model-name}
|
|
# - Deployment ID: OTEL_SERVICE_NAME (if set) or hostname (fallback)
|
|
# - Model name: OLLAMA_EMBEDDING_MODEL
|
|
# - Example: "nextcloud-mcp-server-nomic-embed-text"
|
|
# - Changing models creates new collection (requires re-embedding)
|
|
# - Set QDRANT_COLLECTION to override auto-generation:
|
|
#- QDRANT_COLLECTION=nextcloud_content
|
|
|
|
# Ollama configuration (optional - uses SimpleEmbeddingProvider if not set)
|
|
# - OLLAMA_BASE_URL=http://ollama:11434
|
|
# - OLLAMA_EMBEDDING_MODEL=nomic-embed-text # Changing this creates new collection
|
|
# - OLLAMA_VERIFY_SSL=false
|
|
|
|
# Document chunking configuration (for vector embeddings)
|
|
# Tune these based on your embedding model and content type
|
|
# - DOCUMENT_CHUNK_SIZE=512 # Words per chunk (default: 512)
|
|
# - DOCUMENT_CHUNK_OVERLAP=50 # Overlapping words (default: 50, recommended: 10-20% of chunk size)
|
|
|
|
mcp-multi-user-basic:
|
|
build: .
|
|
restart: always
|
|
command: ["--transport", "streamable-http"]
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8003:8000
|
|
environment:
|
|
# Multi-user BasicAuth pass-through mode (ADR-020)
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8003
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
|
- ENABLE_MULTI_USER_BASIC_AUTH=true
|
|
- ENABLE_BACKGROUND_OPERATIONS=true
|
|
|
|
# Token storage (required for middleware initialization)
|
|
- TOKEN_ENCRYPTION_KEY=ESF1BvEQdGYsCluwMx9Cxvw3uh5pFowPH7Rg_nIliyo=
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
- ENABLE_SEMANTIC_SEARCH=true
|
|
- VECTOR_SYNC_SCAN_INTERVAL=60
|
|
- VECTOR_SYNC_PROCESSOR_WORKERS=1
|
|
|
|
# OAuth credentials for background sync (optional - uses DCR if not provided)
|
|
# Uncomment to avoid DCR:
|
|
# - NEXTCLOUD_OIDC_CLIENT_ID=your_client_id
|
|
# - NEXTCLOUD_OIDC_CLIENT_SECRET=your_client_secret
|
|
|
|
# NO admin credentials - credentials come from client Authorization header
|
|
volumes:
|
|
- multi-user-basic-data:/app/data
|
|
|
|
mcp-oauth:
|
|
build: .
|
|
command: ["--transport", "streamable-http", "--oauth", "--port", "8001", "--oauth-token-type", "jwt"]
|
|
restart: always
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8001:8001
|
|
environment:
|
|
# Generic OIDC configuration (integrated mode - Nextcloud OIDC app)
|
|
# OIDC_DISCOVERY_URL not set - defaults to NEXTCLOUD_HOST/.well-known/openid-configuration
|
|
# OIDC_CLIENT_ID not set - uses Dynamic Client Registration (DCR)
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8001
|
|
- NEXTCLOUD_RESOURCE_URI=http://localhost:8080 # ADR-005: Nextcloud resource identifier for audience validation
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
|
|
- NEXTCLOUD_OIDC_SCOPES=openid profile email notes:read notes:write calendar:read calendar:write contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write tables:read tables:write files:read files:write sharing:read sharing:write todo:read todo:write
|
|
|
|
# Refresh token storage (ADR-002 Tier 1)
|
|
- ENABLE_BACKGROUND_OPERATIONS=true
|
|
- TOKEN_ENCRYPTION_KEY=ESF1BvEQdGYsCluwMx9Cxvw3uh5pFowPH7Rg_nIliyo=
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
# ADR-005: Multi-audience mode (default - ENABLE_TOKEN_EXCHANGE=false)
|
|
# Tokens must contain BOTH MCP and Nextcloud audiences
|
|
# No token exchange needed - tokens work for both MCP auth and Nextcloud APIs
|
|
|
|
# Semantic search configuration (ADR-007, ADR-021)
|
|
- ENABLE_SEMANTIC_SEARCH=true
|
|
- VECTOR_SYNC_SCAN_INTERVAL=60
|
|
- VECTOR_SYNC_PROCESSOR_WORKERS=1
|
|
|
|
# Qdrant configuration - persistent local storage
|
|
- QDRANT_LOCATION=/app/data/qdrant
|
|
|
|
# Embedding provider for vector sync (use Simple provider as fallback)
|
|
# Ollama not available in CI/test environments
|
|
# - OLLAMA_BASE_URL=http://ollama:11434
|
|
# - OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
|
|
|
# NO admin credentials - using OAuth with Dynamic Client Registration (DCR)
|
|
# Client credentials registered via RFC 7591 and stored in volume
|
|
# JWT token type is used for testing (faster validation, scopes embedded in token)
|
|
volumes:
|
|
- oauth-client-storage:/app/.oauth
|
|
- oauth-tokens:/app/data
|
|
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:26.5.1@sha256:b80a48090594367bd8cf6fe2019466ac4ea49de4d0830fb2a43256eda37b18f5
|
|
command:
|
|
- "start-dev"
|
|
- "--import-realm"
|
|
- "--hostname=http://localhost:8888"
|
|
- "--hostname-strict=false"
|
|
- "--hostname-backchannel-dynamic=true"
|
|
- "--features=preview" # Enable Legacy V1 token exchange (supports both Standard V2 and Legacy V1)
|
|
ports:
|
|
- 127.0.0.1:8888:8080
|
|
environment:
|
|
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
|
|
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
|
|
volumes:
|
|
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm.json:ro
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /realms/nextcloud-mcp HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q 'HTTP/1.1 200'"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 30
|
|
|
|
mcp-keycloak:
|
|
build: .
|
|
command: ["--transport", "streamable-http", "--oauth", "--port", "8002"]
|
|
restart: always
|
|
depends_on:
|
|
keycloak:
|
|
condition: service_healthy
|
|
app:
|
|
condition: service_started
|
|
ports:
|
|
- 127.0.0.1:8002:8002
|
|
environment:
|
|
# Generic OIDC configuration (external IdP mode - Keycloak)
|
|
# Provider auto-detected from OIDC_DISCOVERY_URL issuer
|
|
# Using internal Docker hostname for discovery to get consistent issuer
|
|
- OIDC_DISCOVERY_URL=http://keycloak:8080/realms/nextcloud-mcp/.well-known/openid-configuration
|
|
- NEXTCLOUD_OIDC_CLIENT_ID=nextcloud-mcp-server
|
|
- NEXTCLOUD_OIDC_CLIENT_SECRET=mcp-secret-change-in-production
|
|
- OIDC_JWKS_URI=http://keycloak:8080/realms/nextcloud-mcp/protocol/openid-connect/certs
|
|
|
|
# Nextcloud API endpoint (for accessing APIs with validated token)
|
|
- NEXTCLOUD_HOST=http://app:80
|
|
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8002
|
|
- NEXTCLOUD_RESOURCE_URI=nextcloud # ADR-005: Keycloak uses client IDs as audiences, not URLs
|
|
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8888/realms/nextcloud-mcp
|
|
|
|
# Refresh token storage (ADR-002 Tier 1 & 2)
|
|
- ENABLE_BACKGROUND_OPERATIONS=true
|
|
- TOKEN_ENCRYPTION_KEY=ESF1BvEQdGYsCluwMx9Cxvw3uh5pFowPH7Rg_nIliyo=
|
|
- TOKEN_STORAGE_DB=/app/data/tokens.db
|
|
|
|
# ADR-005: Token exchange mode (RFC 8693)
|
|
# Exchange MCP tokens (aud: nextcloud-mcp-server) for Nextcloud tokens (aud: http://localhost:8080)
|
|
# Provides strict audience separation between MCP session and Nextcloud API access
|
|
- ENABLE_TOKEN_EXCHANGE=true
|
|
- TOKEN_EXCHANGE_CACHE_TTL=300 # Cache exchanged tokens for 5 minutes (default)
|
|
|
|
# OAuth scopes (optional - uses defaults if not specified)
|
|
- NEXTCLOUD_OIDC_SCOPES=openid profile email offline_access notes:read notes:write calendar:read calendar:write contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write tables:read tables:write files:read files:write sharing:read sharing:write todo:read todo:write
|
|
|
|
# NO admin credentials - using external IdP OAuth only!
|
|
volumes:
|
|
- keycloak-tokens:/app/data
|
|
- keycloak-oauth-storage:/app/.oauth
|
|
|
|
# Smithery stateless deployment mode (ADR-016)
|
|
# Test with: docker compose --profile smithery up smithery
|
|
# Then: curl http://localhost:8081/.well-known/mcp-config
|
|
smithery:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.smithery
|
|
restart: always
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
ports:
|
|
- 127.0.0.1:8081:8081
|
|
environment:
|
|
- SMITHERY_DEPLOYMENT=true
|
|
- ENABLE_SEMANTIC_SEARCH=false
|
|
- PORT=8081
|
|
profiles:
|
|
- smithery
|
|
|
|
qdrant:
|
|
image: docker.io/qdrant/qdrant:v1.16.3@sha256:0425e3e03e7fd9b3dc95c4214546afe19de2eb2e28ca621441a56663ac6e1f46
|
|
restart: always
|
|
ports:
|
|
- 127.0.0.1:6333:6333 # REST API
|
|
- 127.0.0.1:6334:6334 # gRPC (optional)
|
|
volumes:
|
|
- qdrant-data:/qdrant/storage
|
|
environment:
|
|
- QDRANT__SERVICE__API_KEY=${QDRANT_API_KEY:-my_secret_api_key}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "test -f /qdrant/.qdrant-initialized"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
profiles:
|
|
- qdrant
|
|
|
|
volumes:
|
|
nextcloud:
|
|
db:
|
|
oauth-client-storage:
|
|
oauth-tokens:
|
|
keycloak-tokens:
|
|
keycloak-oauth-storage:
|
|
qdrant-data:
|
|
mcp-data:
|
|
multi-user-basic-data:
|