services:
  traefik-certs:
    image: alpine:latest
    restart: "no"
    entrypoint: ["sh", "-c"]
    command:
      - |
        if [ -f /certs/cert.pem ]; then
          echo "Certs already exist, skipping"
          exit 0
        fi

        # Option 1: User-supplied certificate
        if [ -n "$$CERT_FILE" ] && [ -n "$$KEY_FILE" ]; then
          apk add --no-cache openssl >/dev/null 2>&1
          cp "$$CERT_FILE" /certs/cert.pem
          cp "$$KEY_FILE" /certs/key.pem
          if [ -n "$$CA_FILE" ]; then
            cp "$$CA_FILE" /certs/ca.pem
          fi
          # Validate: key matches cert
          CERT_MOD=$$(openssl x509 -noout -modulus -in /certs/cert.pem 2>/dev/null | md5sum)
          KEY_MOD=$$(openssl rsa -noout -modulus -in /certs/key.pem 2>/dev/null | md5sum)
          if [ "$$CERT_MOD" != "$$KEY_MOD" ]; then
            echo "ERROR: Certificate and key do not match!"
            rm -f /certs/cert.pem /certs/key.pem /certs/ca.pem
            exit 1
          fi
          SELF_SIGNED=false
          echo "Installed user-supplied certificate"
        else
          # Option 2: Generate self-signed
          apk add --no-cache openssl >/dev/null 2>&1
          openssl req -x509 -newkey rsa:4096 \
            -keyout /certs/key.pem -out /certs/cert.pem \
            -days 365 -nodes \
            -subj "/CN=$$PUBLIC_HOST" \
            -addext "subjectAltName=DNS:$$PUBLIC_HOST,DNS:*.$$PUBLIC_HOST"
          SELF_SIGNED=true
          echo "Generated self-signed cert for $$PUBLIC_HOST"
        fi

        # Write metadata for SaaS app to seed DB
        SUBJECT=$$(openssl x509 -noout -subject -in /certs/cert.pem 2>/dev/null | sed 's/subject=//')
        FINGERPRINT=$$(openssl x509 -noout -fingerprint -sha256 -in /certs/cert.pem 2>/dev/null | sed 's/.*=//')
        NOT_BEFORE=$$(openssl x509 -noout -startdate -in /certs/cert.pem 2>/dev/null | sed 's/notBefore=//')
        NOT_AFTER=$$(openssl x509 -noout -enddate -in /certs/cert.pem 2>/dev/null | sed 's/notAfter=//')
        HAS_CA=false
        [ -f /certs/ca.pem ] && HAS_CA=true
        cat > /certs/meta.json <<METAEOF
        {"subject":"$$SUBJECT","fingerprint":"$$FINGERPRINT","selfSigned":$$SELF_SIGNED,"hasCa":$$HAS_CA,"notBefore":"$$NOT_BEFORE","notAfter":"$$NOT_AFTER"}
        METAEOF
        mkdir -p /certs/staged /certs/prev
    environment:
      PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
      CERT_FILE: ${CERT_FILE:-}
      KEY_FILE: ${KEY_FILE:-}
      CA_FILE: ${CA_FILE:-}
    volumes:
      - certs:/certs

  traefik:
    image: traefik:v3
    restart: unless-stopped
    depends_on:
      traefik-certs:
        condition: service_completed_successfully
    ports:
      - "80:80"
      - "443:443"
      - "3002:3002"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./docker/traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro
      - certs:/etc/traefik/certs:ro
    networks:
      - cameleer
      - cameleer-traefik

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-cameleer_saas}
      POSTGRES_USER: ${POSTGRES_USER:-cameleer}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cameleer_dev}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./docker/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cameleer} -d ${POSTGRES_DB:-cameleer_saas}"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - cameleer

  logto:
    image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest}
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    entrypoint: ["sh", "-c", "npm run cli db seed -- --swe && npm start"]
    environment:
      DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD:-cameleer_dev}@postgres:5432/logto
      ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
      ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:3002
      TRUST_PROXY_HEADER: 1
      NODE_TLS_REJECT_UNAUTHORIZED: "0"  # dev only — accept self-signed cert for internal OIDC discovery
    healthcheck:
      test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\""]
      interval: 5s
      timeout: 5s
      retries: 30
      start_period: 15s
    labels:
      - traefik.enable=true
      - traefik.http.routers.logto.rule=PathPrefix(`/`)
      - traefik.http.routers.logto.priority=1
      - traefik.http.routers.logto.entrypoints=websecure
      - traefik.http.routers.logto.tls=true
      - traefik.http.routers.logto.service=logto
      - traefik.http.routers.logto.middlewares=logto-cors
      - traefik.http.middlewares.logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:3002
      - traefik.http.middlewares.logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS
      - traefik.http.middlewares.logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type
      - traefik.http.middlewares.logto-cors.headers.accessControlAllowCredentials=true
      - traefik.http.services.logto.loadbalancer.server.port=3001
      - traefik.http.routers.logto-console.rule=PathPrefix(`/`)
      - traefik.http.routers.logto-console.entrypoints=admin-console
      - traefik.http.routers.logto-console.tls=true
      - traefik.http.routers.logto-console.service=logto-console
      - traefik.http.services.logto-console.loadbalancer.server.port=3002
    networks:
      - cameleer

  logto-bootstrap:
    image: postgres:16-alpine
    depends_on:
      logto:
        condition: service_healthy
    restart: "no"
    entrypoint: ["sh", "/scripts/logto-bootstrap.sh"]
    environment:
      LOGTO_ENDPOINT: http://logto:3001
      LOGTO_ADMIN_ENDPOINT: http://logto:3002
      LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
      PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
      PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https}
      PG_HOST: postgres
      PG_USER: ${POSTGRES_USER:-cameleer}
      PG_PASSWORD: ${POSTGRES_PASSWORD:-cameleer_dev}
      PG_DB_SAAS: ${POSTGRES_DB:-cameleer_saas}
      SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin}
      SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:-admin}
    volumes:
      - ./docker/logto-bootstrap.sh:/scripts/logto-bootstrap.sh:ro
      - bootstrapdata:/data
    networks:
      - cameleer

  cameleer-saas:
    image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest}
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      logto-bootstrap:
        condition: service_completed_successfully
    volumes:
      - bootstrapdata:/data/bootstrap:ro
      - certs:/certs
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-cameleer_saas}
      SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer}
      SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-cameleer_dev}
      LOGTO_ENDPOINT: ${LOGTO_ENDPOINT:-http://logto:3001}
      LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
      LOGTO_ISSUER_URI: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}/oidc
      LOGTO_JWK_SET_URI: ${LOGTO_ENDPOINT:-http://logto:3001}/oidc/jwks
      LOGTO_M2M_CLIENT_ID: ${LOGTO_M2M_CLIENT_ID:-}
      LOGTO_M2M_CLIENT_SECRET: ${LOGTO_M2M_CLIENT_SECRET:-}
    labels:
      - traefik.enable=true
      - traefik.http.routers.saas.rule=PathPrefix(`/platform`)
      - traefik.http.routers.saas.entrypoints=websecure
      - traefik.http.routers.saas.tls=true
      - traefik.http.services.saas.loadbalancer.server.port=8080
    networks:
      - cameleer

  clickhouse:
    image: clickhouse/clickhouse-server:latest
    restart: unless-stopped
    volumes:
      - chdata:/var/lib/clickhouse
      - ./docker/clickhouse-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
      - ./docker/clickhouse-users.xml:/etc/clickhouse-server/users.d/default-user.xml:ro
    healthcheck:
      test: ["CMD-SHELL", "clickhouse-client --query 'SELECT 1'"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - cameleer

networks:
  cameleer:
    driver: bridge
  cameleer-traefik:
    name: cameleer-traefik
    driver: bridge

volumes:
  pgdata:
  chdata:
  certs:
  bootstrapdata:
