# App services load `.env` (SECRET_KEY, ALLOWED_HOSTS, email SMTP, AWS/S3, DB credentials, etc.).
# `environment:` overrides DB/Redis/Celery URLs so containers use compose service names, even when
# `.env` still has DB_HOST=localhost for running Django on the host.

services:
  web:
    build:
      context: .
      dockerfile: docker/Dockerfile
    # gunicorn tuned for the 12-core / 47 GB host:
    #   workers=9 procs * threads=4 => 36 concurrent requests in flight, with
    #   memory shared across forks via --preload. gthread class is the right
    #   choice for I/O-bound Django (DB / network) — sync workers would
    #   serialize every request behind the slowest one.
    #   9 workers ≈ ((2*cores)+1) / (threads-1) — the gthread analogue of the
    #   sync (2*cores)+1 rule. Memory cost ~2.3 GB (trivial on 47 GB).
    #   Each gthread thread can hold a DB connection: 9*4 = 36 + ~10 celery
    #   ≈ 46 conns, well under postgres default max_connections=100. Bump
    #   max_connections before going past 12 workers.
    #   --max-requests + jitter recycles workers periodically to keep memory
    #   flat. --worker-tmp-dir=/dev/shm avoids slow heartbeat I/O on some
    #   kernels. --access-logfile/--error-logfile to stdout so docker logs
    #   carries request timing.
    command: >-
      gunicorn config.wsgi:application
      --bind 0.0.0.0:8000
      --workers 9
      --threads 4
      --worker-class gthread
      --worker-tmp-dir /dev/shm
      --max-requests 1000
      --max-requests-jitter 100
      --preload
      --access-logfile -
      --error-logfile -
    env_file:
      - .env
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      REDIS_URL: redis://redis:6379/0
      CELERY_BROKER_URL: redis://redis:6379/1
      CELERY_RESULT_BACKEND: django-db
      DJANGO_SETTINGS_MODULE: config.settings.production
    volumes:
      - uploads_data:/app/uploads
      - static_data:/app/staticfiles
    # Reach Moodle at its wwwroot host (MOODLE_PUBLIC_HOSTNAME) via the host's
    # published Moodle port, so the landing catalogue + MEL WS calls hit the same
    # host Moodle believes it is and aren't 303-redirected. Harmless if unset.
    extra_hosts:
      - "${MOODLE_PUBLIC_HOSTNAME:-moodle.invalid}:host-gateway"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  celery_worker:
    build:
      context: .
      dockerfile: docker/Dockerfile.celery
    command: celery -A config worker -l info
    env_file:
      - .env
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      REDIS_URL: redis://redis:6379/0
      CELERY_BROKER_URL: redis://redis:6379/1
      CELERY_RESULT_BACKEND: django-db
      DJANGO_SETTINGS_MODULE: config.settings.production
    volumes:
      - uploads_data:/app/uploads
    # Reach Moodle at its wwwroot host (see web) so the MEL pull isn't redirected.
    extra_hosts:
      - "${MOODLE_PUBLIC_HOSTNAME:-moodle.invalid}:host-gateway"
    # Confirms the worker is up and has registered our tasks. (The REP video
    # transcode task + its ffmpeg/ffprobe checks were removed with the REP LMS.)
    healthcheck:
      test:
        - CMD-SHELL
        - >-
          celery -A config inspect ping -t 5 2>/dev/null | grep -q pong &&
          celery -A config inspect registered -t 5 2>/dev/null | grep -q apps.mel.tracking.scan_delayed_activities
      interval: 30s
      timeout: 15s
      retries: 5
      start_period: 60s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  celery_beat:
    build:
      context: .
      dockerfile: docker/Dockerfile.celery
    command: celery -A config beat -l info
    env_file:
      - .env
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      REDIS_URL: redis://redis:6379/0
      CELERY_BROKER_URL: redis://redis:6379/1
      CELERY_RESULT_BACKEND: django-db
      DJANGO_SETTINGS_MODULE: config.settings.production
    volumes:
      - uploads_data:/app/uploads
    # Reach Moodle at its wwwroot host (see web) — beat schedules the MEL pull.
    extra_hosts:
      - "${MOODLE_PUBLIC_HOSTNAME:-moodle.invalid}:host-gateway"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  # Demo environment — anonymised seed data, safe for testers.
  # Reachable at http://<server>:8001  (nginx proxies this server block).
  # To reset: docker compose -f docker-compose.prod.yml exec web_demo python manage.py seed_all --volume demo
  web_demo:
    build:
      context: .
      dockerfile: docker/Dockerfile
    command: >-
      gunicorn config.wsgi:application
      --bind 0.0.0.0:8000
      --workers 4
      --threads 2
      --worker-class gthread
      --worker-tmp-dir /dev/shm
      --max-requests 500
      --max-requests-jitter 50
      --preload
      --access-logfile -
      --error-logfile -
    env_file:
      - .env
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_NAME: iilmp_demo
      REDIS_URL: redis://redis:6379/2
      CELERY_BROKER_URL: redis://redis:6379/3
      CELERY_RESULT_BACKEND: django-db
      DJANGO_SETTINGS_MODULE: config.settings.production
      # Route demo-environment mail to the mailhog sink instead of the real
      # SendGrid account .env configures for prod `web` — testers triggering
      # notifications/resets against seeded fake accounts should never send
      # real email. Mirrors the same override pattern dev's docker-compose.yml
      # already uses. mailhog isn't declared in this file but is reachable on
      # the shared compose network (see docker-compose.yml's `mailhog` service).
      EMAIL_HOST: mailhog
      EMAIL_PORT: "1025"
      EMAIL_USE_TLS: "False"
      EMAIL_HOST_USER: ""
      EMAIL_HOST_PASSWORD: ""
    volumes:
      - demo_uploads:/app/uploads
      - static_data:/app/staticfiles
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  # Dedicated Celery worker/beat for the demo environment. web_demo queues
  # tasks on CELERY_BROKER_URL redis DB 3, which the *production*
  # celery_worker/celery_beat above never poll (they listen on DB 1) — without
  # these, every async task queued from the demo site (including any
  # email/notification send) just sits unprocessed forever. Critically, these
  # run against DB_NAME=iilmp_demo — pointing demo tasks at the prod worker
  # instead would execute them against the *production* database, since that
  # worker has no DB_NAME override.
  celery_worker_demo:
    build:
      context: .
      dockerfile: docker/Dockerfile.celery
    command: celery -A config worker -l info
    env_file:
      - .env
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_NAME: iilmp_demo
      REDIS_URL: redis://redis:6379/2
      CELERY_BROKER_URL: redis://redis:6379/3
      CELERY_RESULT_BACKEND: django-db
      DJANGO_SETTINGS_MODULE: config.settings.production
      EMAIL_HOST: mailhog
      EMAIL_PORT: "1025"
      EMAIL_USE_TLS: "False"
      EMAIL_HOST_USER: ""
      EMAIL_HOST_PASSWORD: ""
    volumes:
      - demo_uploads:/app/uploads
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  celery_beat_demo:
    build:
      context: .
      dockerfile: docker/Dockerfile.celery
    command: celery -A config beat -l info
    env_file:
      - .env
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_NAME: iilmp_demo
      REDIS_URL: redis://redis:6379/2
      CELERY_BROKER_URL: redis://redis:6379/3
      CELERY_RESULT_BACKEND: django-db
      DJANGO_SETTINGS_MODULE: config.settings.production
      EMAIL_HOST: mailhog
      EMAIL_PORT: "1025"
      EMAIL_USE_TLS: "False"
      EMAIL_HOST_USER: ""
      EMAIL_HOST_PASSWORD: ""
    volumes:
      - demo_uploads:/app/uploads
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "8001:8001"
    volumes:
      - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
      - static_data:/app/staticfiles:ro
      - uploads_data:/app/uploads:ro
      # Demo (8001) media lives in a separate volume than prod (80). Mount it so
      # the 8001 server block can serve /uploads/ from the demo's own files
      # instead of falling through to the prod volume (→ 404s on the demo site).
      - demo_uploads:/app/demo_uploads:ro
    depends_on:
      - web
      - web_demo
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 10

  postgres:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_DB: ${DB_NAME:-iilmp}
      POSTGRES_USER: ${DB_USER:-iilmp}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-iilmp}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-iilmp} -d ${DB_NAME:-iilmp}"]
      interval: 5s
      timeout: 5s
      retries: 10

  # ── Moodle LMS ────────────────────────────────────────────────────────────
  # Front this with nginx (a moodle. vhost or /moodle path) — do NOT publish a
  # host port directly in prod. PIN IMAGE DIGESTS and use real secrets. Moodle
  # needs its own MariaDB; never point it at the pgvector `postgres`.
  moodle-mariadb:
    image: bitnamilegacy/mariadb:11.8.3@sha256:ada2a782b0bdd7885bda684af7fb1fdaa57929c35e639f7cbbf3f724a1735a31
    environment:
      MARIADB_ROOT_PASSWORD: ${MOODLE_DB_ROOT_PASSWORD:?set in .env.production}
      MARIADB_DATABASE: ${MOODLE_DB_NAME:-bitnami_moodle}
      MARIADB_USER: ${MOODLE_DB_USER:-bn_moodle}
      MARIADB_PASSWORD: ${MOODLE_DB_PASSWORD:?set in .env.production}
      MARIADB_CHARACTER_SET: utf8mb4
      MARIADB_COLLATE: utf8mb4_unicode_ci
    volumes:
      - moodle_mariadb_data:/bitnami/mariadb
    healthcheck:
      test: ["CMD", "/opt/bitnami/scripts/mariadb/healthcheck.sh"]
      interval: 15s
      timeout: 10s
      retries: 10
    restart: unless-stopped

  moodle:
    image: bitnamilegacy/moodle:5.0.2@sha256:df3492f185922381361e8194bffb6d2425accd575992427d571ef12b5c30514c
    environment:
      MOODLE_DATABASE_HOST: moodle-mariadb
      MOODLE_DATABASE_PORT_NUMBER: "3306"
      MOODLE_DATABASE_NAME: ${MOODLE_DB_NAME:-bitnami_moodle}
      MOODLE_DATABASE_USER: ${MOODLE_DB_USER:-bn_moodle}
      MOODLE_DATABASE_PASSWORD: ${MOODLE_DB_PASSWORD:?set in .env.production}
      MOODLE_USERNAME: ${MOODLE_ADMIN_USER:-admin}
      MOODLE_PASSWORD: ${MOODLE_ADMIN_PASSWORD:?set in .env.production}
      MOODLE_EMAIL: ${MOODLE_ADMIN_EMAIL:?set in .env.production}
      MOODLE_SITE_NAME: "RUFORUM IILMP Learning"
      MOODLE_HOST: ${MOODLE_HOST:?public Moodle host, e.g. moodle.example.org}
      # Behind a real TLS-terminating proxy (nginx vhost + a domain) set both to
      # "yes". For the AWS-domain HTTP phase Moodle is published directly (docker
      # NAT — not an HTTP proxy), so both default off.
      MOODLE_REVERSEPROXY: ${MOODLE_REVERSEPROXY:-no}
      MOODLE_SSLPROXY: ${MOODLE_SSLPROXY:-no}
    # Published so a browser can reach Moodle on the single AWS host without a
    # subdomain vhost. Open TCP ${MOODLE_HOST_PORT:-8081} in the EC2 security
    # group. Once rep.ruforum.org is live, drop this and route via the nginx vhost.
    ports:
      - "${MOODLE_HOST_PORT:-8081}:8080"
    volumes:
      - moodle_data:/bitnami/moodle
      - moodle_moodledata:/bitnami/moodledata
    depends_on:
      moodle-mariadb:
        condition: service_healthy
    healthcheck:
      # The bitnamilegacy image ships no curl/wget binary — only php — so probe
      # the login page via php's curl extension (always present for Moodle).
      test: ["CMD", "php", "-r", "$$c=curl_init('http://localhost:8080/login/index.php');curl_setopt($$c,CURLOPT_NOBODY,true);curl_setopt($$c,CURLOPT_TIMEOUT,8);curl_exec($$c);$$h=curl_getinfo($$c,CURLINFO_HTTP_CODE);exit(($$h>=200&&$$h<400)?0:1);"]
      interval: 30s
      timeout: 10s
      retries: 10
      start_period: 180s
    restart: unless-stopped

volumes:
  postgres_data:
  uploads_data:
  static_data:
  demo_uploads:
  moodle_mariadb_data:
  moodle_data:
  moodle_moodledata:
