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:11.8.6-noble@sha256:31c1aa20ba2d78c4cbc45b2675e40ffd6a3cd59897a9767ce88fc284309f55ab
    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:8.6.2-alpine@sha256:81b6f81d6a6c5b9019231a2e8eb10085e3a139a34f833dcc965a8a959b040b72
    restart: always

  app:
    image: ${NEXTCLOUD_IMAGE:-docker.io/library/nextcloud:32.0.8@sha256:30aa31f1ef08974254114bef99845de79c3f5f00306d1d09b6211707e7d4c3e3}
    restart: always
    ports:
      - 127.0.0.1:8080:80
    depends_on:
      - redis
      - db
    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
      - ./third_party/astrolabe:/opt/apps/astrolabe:ro
      #- ./third_party/oidc:/opt/apps/oidc: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
      # Set MCP_SERVER_URL to enable Astrolabe OAuth client auto-creation
      # (before-starting hook creates OIDC client + stores credentials in config.php)
      # Example: MCP_SERVER_URL=http://mcp-login-flow:8004 docker compose --profile login-flow up -d
      - MCP_SERVER_URL=${MCP_SERVER_URL:-}
    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:1.29.7-alpine@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1
    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:0.1.1@sha256:f81233ad3049c0b81e010b9be123ed64698787d3218f8356f3e8756f4c14de2f
    restart: always
    ports:
      - 127.0.0.1:8005:8000
    # Unstructured API runs on port 8000 internally
    # We expose it on 8005 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=5
      - VECTOR_SYNC_PROCESSOR_WORKERS=2

      #- 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=":memory:"
      #- 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)
    profiles:
      - single-user

  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)
      # DEVELOPMENT ONLY - generate a fresh key for production:
      # python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
      - TOKEN_ENCRYPTION_KEY=fqqI4G51yBCOcu9cvv6wCUJB7sf_CK2za5ClC6b86yY=
      - 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
    profiles:
      - multi-user-basic

  keycloak:
    image: quay.io/keycloak/keycloak:26.5.4@sha256:ae8efb0d218d8921334b03a2dbee7069a0b868240691c50a3ffc9f42fabba8b4
    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
    profiles:
      - keycloak

  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)

      # Login Flow v2 (ADR-022) with external IdP
      - ENABLE_LOGIN_FLOW=true
      - ENABLE_DCR=true

      # 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
    profiles:
      - keycloak

  # Login Flow v2 mode (ADR-022)
  # Test with: docker compose --profile login-flow up --build -d
  mcp-login-flow:
    build: .
    restart: always
    # --oauth enables the OAuth/OIDC identity layer that Login Flow v2 builds on
    # (user identity via OAuth session, Nextcloud access via app passwords)
    command: ["--transport", "streamable-http", "--oauth", "--port", "8004"]
    depends_on:
      app:
        condition: service_healthy
    ports:
      - 127.0.0.1:8004:8004
    environment:
      - NEXTCLOUD_HOST=http://app:80
      - NEXTCLOUD_MCP_SERVER_URL=http://localhost:8004
      #- NEXTCLOUD_MCP_SERVER_URL=https://nextcloud-mcp-dev.tail148d5.ts.net
      - NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080

      # Login Flow v2 (ADR-022)
      - ENABLE_LOGIN_FLOW=true

      # Token storage (required for app password + session persistence)
      # DEVELOPMENT ONLY - generate a fresh key for production:
      # python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
      - TOKEN_ENCRYPTION_KEY=rxJvkBf7ZBjZZDL4a1sSqjhmjawhmbRMSOGfK8HDyKU=
      - TOKEN_STORAGE_DB=/app/data/tokens.db

      # Semantic search
      - ENABLE_SEMANTIC_SEARCH=true
      - VECTOR_SYNC_SCAN_INTERVAL=60
      - VECTOR_SYNC_PROCESSOR_WORKERS=1
    volumes:
      - login-flow-data:/app/data
      - login-flow-oauth-storage:/app/.oauth
    profiles:
      - login-flow

  # Tailscale Funnel for Claude AI connector testing
  # Usage: docker compose --profile login-flow --profile claude-funnel up --build -d
  # Requires: TS_AUTHKEY in .env file (see env.sample.claude-funnel)
  tailscale-mcp:
    image: docker.io/tailscale/tailscale:v1.94.2@sha256:95e528798bebe75f39b10e74e7051cf51188ee615934f232ba7ad06a3390ffa1
    hostname: nextcloud-mcp-dev
    restart: always
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY}
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_SERVE_CONFIG=/config/serve-config.json
      - TS_HOSTNAME=nextcloud-mcp-dev
      - TS_EXTRA_ARGS=--advertise-tags=tag:container
    volumes:
      - tailscale-state:/var/lib/tailscale
      - ./claude-funnel/serve-config.json:/config/serve-config.json:ro
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    profiles:
      - claude-funnel

  nginx-claude-filter:
    image: docker.io/library/nginx:1.29.7-alpine@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1
    restart: always
    depends_on:
      - mcp-login-flow
    volumes:
      - ./claude-funnel/nginx.conf:/etc/nginx/nginx.conf:ro
    profiles:
      - claude-funnel

  qdrant:
    image: docker.io/qdrant/qdrant:v1.17.1@sha256:94728574965d17c6485dd361aa3c0818b325b9016dac5ea6afec7b4b2700865f
    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:
  keycloak-tokens:
  keycloak-oauth-storage:
  login-flow-data:
  login-flow-oauth-storage:
  qdrant-data:
  mcp-data:
  multi-user-basic-data:
  tailscale-state:
