networks:
  default:
    name: opentdf_platform

configs:
  caddy_config:
    content: |
      {
        log {
            level INFO
            output stdout
        }
      }
      https://keycloak.opentdf.local:9443 {
        tls internal
        reverse_proxy keycloak:8888
      }
      https://platform.opentdf.local:8443 {
        tls internal
        reverse_proxy {
            to h2c://platform:8080
            transport http {
                versions h2c 2 1.1  # Enable gRPC proxying
            }
        }
      }

services:
  caddy:
    image: caddy:2.8.4-alpine
    command: ['caddy','run', '--config', '/etc/caddy/Caddyfile']
    configs:
      - source: caddy_config
        target: /etc/caddy/Caddyfile
    ports:
      - "8443:8443"
      - "9443:9443"
      - "2019:2019"
    volumes:
      - caddy_data:/data
    depends_on:
      ensure-permissions:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:2019/metrics"]
      interval: 5s
      timeout: 5s
      retries: 3
    restart: unless-stopped

  check-certs:
    image: alpine:3.23
    volumes:
      - type: volume
        source: caddy_data
        target: /etc/ssl/certs
        volume:
          subpath: caddy/certificates/local/keycloak.opentdf.local/
    command:
      - sh
      - -c
      - |
        echo "Checking certificates"
        ls -alh /etc/ssl/certs
        cat /etc/ssl/certs/keycloak.opentdf.local.crt
    depends_on:
      caddy:
        condition: service_healthy
      ensure-permissions:
        condition: service_completed_successfully
    restart: "no"

  ensure-permissions:
    image: alpine:3.23
    command:
    - 'sh'
    - '-c'
    - |
      chmod -R 755 /data
    volumes:
      - caddy_data:/data
    restart: "no"

  keycloak:
    volumes:
    - keys:/keys:ro
    image: keycloak/keycloak:25.0
    restart: always
    depends_on:
      fix-keys-permissions:
        condition: service_completed_successfully
    command:
    - "start-dev"
    - "--verbose"
    - "-Djavax.net.ssl.trustStorePassword=password"
    - "-Djavax.net.ssl.HostnameVerifier=AllowAll"
    - "-Djavax.net.ssl.trustStore=/keys/ca.jks"
    - "--spi-truststore-file-hostname-verification-policy=ANY"
    environment:
      KC_PROXY: edge
      KC_HTTP_RELATIVE_PATH: /auth
      KC_HOSTNAME_STRICT: "false"
      KC_HOSTNAME_STRICT_BACKCHANNEL: "false"
      KC_HOSTNAME_STRICT_HTTPS: "false"
      KC_HTTP_ENABLED: "true"
      KC_HTTP_PORT: "8888"
      KC_HTTPS_PORT: "8443"
      KC_HTTP_MANAGEMENT_PORT: "9001"
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: changeme
      #KC_HOSTNAME_URL: http://localhost:8888/auth
      KC_FEATURES: "preview,token-exchange"
      KC_HEALTH_ENABLED: "true"
      KC_HTTPS_KEY_STORE_PASSWORD: "password"
      KC_HTTPS_KEY_STORE_FILE: "/keys/ca.jks"
      KC_HTTPS_CERTIFICATE_FILE: "/keys/localhost.crt"
      KC_HTTPS_CERTIFICATE_KEY_FILE: "/keys/localhost.key"
      KC_HTTPS_CLIENT_AUTH: "request"
      ###
      # The following environment variable resolves SIGILL with Code 134 when running Java processes on Apple M4 chips
      # 
      # On Apple Silicon (M4 chip):
      #   export JAVA_OPTS_APPEND="-XX:UseSVE=0"
      #   docker-compose up
      # 
      # On other architectures:
      #   export JAVA_OPTS_APPEND=""
      #   docker-compose up
      # 
      # Or set directly: JAVA_OPTS_APPEND="-XX:UseSVE=0" docker-compose up
      JAVA_OPTS_APPEND: "${JAVA_OPTS_APPEND:-}"
      ###
    # ports:
    #   - "${KC_EXPOSE_PORT:-8443}:8443"
    #   - "${KC_EXPOSE_PORT_HTTP:-8888}:8888"
    #   - "${KC_EXPOSE_PORT_MGMT:-9001}:9001"
    healthcheck:
      test:
        - CMD-SHELL
        - |
          [ -f /tmp/HealthCheck.java ] || echo "public class HealthCheck { 
            public static void main(String[] args) throws java.lang.Throwable { 
              javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); 
              javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance(\"SSL\"); 
              sc.init(null, new javax.net.ssl.TrustManager[]{ 
                new javax.net.ssl.X509TrustManager() { 
                  public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } 
                  public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} 
                  public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} 
                } 
              }, new java.security.SecureRandom()); 
              javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 
              java.net.HttpURLConnection conn = (java.net.HttpURLConnection)new java.net.URL(args[0]).openConnection(); 
              System.exit(java.net.HttpURLConnection.HTTP_OK == conn.getResponseCode() ? 0 : 1); 
            } 
          }" > /tmp/HealthCheck.java && java ${JAVA_OPTS_APPEND} /tmp/HealthCheck.java http://localhost:8888/auth 2>/dev/null
      interval: 10s
      timeout: 10s
      retries: 10
      start_period: 3m
  opentdfdb:
    image: postgres:15-alpine
    restart: always
    user: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: opentdf
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 5s
      timeout: 5s
      retries: 10

  jaeger:
    image: jaegertracing/all-in-one:latest
    environment:
      COLLECTOR_OTLP_ENABLED: "true"
    ports:
      - "16686:16686"   # Web UI
      - "4317:4317"     # OTLP gRPC
      - "4318:4318"     # OTLP HTTP
      - "14250:14250"   # Model/collector gRPC
    profiles:
      - tracing
    restart: always

  # Provision Keycloak with initial configuration
  platform-provision-keycloak:
    image: registry.opentdf.io/platform:nightly
    command: ["provision", "keycloak", "-e", "https://keycloak.opentdf.local:9443/auth", "-f", "/configs/keycloak_data.yaml"]
    depends_on:
      keycloak:
        condition: service_healthy
      opentdfdb:
        condition: service_healthy
      patch-platform-config:
        condition: service_completed_successfully
      download-keycloak-data:
        condition: service_completed_successfully
      generate-keys:
        condition: service_completed_successfully
      generate-pqc-keys:
        condition: service_completed_successfully
    volumes:
      - configs:/configs:ro
      - keys:/keys:ro
    environment:
      - OPENTDF_CONFIG_FILE=/configs/opentdf.yaml
    restart: "no"
    extra_hosts:
      - "keycloak.opentdf.local:host-gateway"
  # Prepare fixtures directory structure - create symlink to expected location
  prepare-fixtures:
    image: alpine:3.23
    volumes:
      - configs:/configs
    depends_on:
      download-fixtures:
        condition: service_completed_successfully
    command:
      - sh
      - -c
      - |
        mkdir -p /configs/service/internal/fixtures
        cd /configs
        ln -sf /configs/service/internal/fixtures ./service
    restart: "no"

  # Add sample attributes and metadata  
  platform-provision-fixtures:
    image: registry.opentdf.io/platform:nightly
    command: ["provision", "fixtures", "--config-file", "/configs/opentdf.yaml"]
    working_dir: /configs
    depends_on:
      platform-provision-keycloak:
        condition: service_completed_successfully
      opentdfdb:
        condition: service_healthy
      prepare-fixtures:
        condition: service_completed_successfully
      generate-keys:
        condition: service_completed_successfully
      generate-pqc-keys:
        condition: service_completed_successfully
    volumes:
      - configs:/configs:ro
      - keys:/keys:ro
    restart: "no"

  # Prepare CA certificates bundle with Caddy cert
  prepare-ca-certs:
    image: alpine:3.23
    volumes:
      - type: volume
        source: caddy_data
        target: /caddy-certs
        read_only: true
        volume:
          subpath: caddy/certificates/local/keycloak.opentdf.local
      - platform_certs:/etc/ssl/certs
    depends_on:
      caddy:
        condition: service_healthy
    command:
      - sh
      - -c
      - |
        # Install ca-certificates package
        apk add --no-cache ca-certificates
        # Copy Caddy certificate to CA bundle
        cp /caddy-certs/keycloak.opentdf.local.crt /usr/local/share/ca-certificates/
        update-ca-certificates
        # Copy the updated CA bundle to shared volume
        cp -r /etc/ssl/certs/* /etc/ssl/certs/
        echo "CA certificates prepared successfully"
    restart: "no"

  # Main OpenTDF Platform server
  platform:
    image: registry.opentdf.io/platform:nightly
    command: ["start", "--config-file", "/configs/opentdf.yaml"]
    depends_on:
      platform-provision-fixtures:
        condition: service_completed_successfully
      keycloak:
        condition: service_healthy
      opentdfdb:
        condition: service_healthy
      generate-keys:
        condition: service_completed_successfully
      generate-pqc-keys:
        condition: service_completed_successfully
      prepare-ca-certs:
        condition: service_completed_successfully
    ports:
      - "8080:8080"
    volumes:
      - configs:/configs:ro
      - keys:/keys:ro
      - platform_certs:/etc/ssl/certs:ro
    extra_hosts:
      - "keycloak.opentdf.local:host-gateway"
    restart: unless-stopped

  # Initialize volume permissions
  init-volumes:
    image: alpine:3.23
    volumes:
      - configs:/configs
      - keys:/keys
    command:
      - sh
      - -c
      - |
        chmod 777 /configs /keys
        mkdir -p /configs/service/internal/fixtures
        chmod -R 777 /configs
    restart: "no"

  # Fix keys permissions after generation
  fix-keys-permissions:
    image: alpine:3.23
    volumes:
      - keys:/keys
    depends_on:
      generate-keys:
        condition: service_completed_successfully
      generate-pqc-keys:
        condition: service_completed_successfully
    command:
      - sh
      - -c
      - |
        chmod -R 755 /keys
        chmod 644 /keys/*
    restart: "no"

  # Download platform configuration file
  download-platform-config:
    image: alpine:3.23
    volumes:
      - configs:/configs
    depends_on:
      init-volumes:
        condition: service_completed_successfully
    command: ['wget', '-O', '/configs/opentdf.yaml', 'https://raw.githubusercontent.com/opentdf/platform/main/opentdf-example.yaml']
    restart: "no"

  # Patch platform configuration to use keycloak.opentdf.local:9443
  patch-platform-config:
    image: alpine:3.23
    volumes:
      - configs:/configs
    depends_on:
      download-platform-config:
        condition: service_completed_successfully
    command:
      - sh
      - -c
      - |
        apk add --no-cache sed
        sed -i 's|http://keycloak:8888|https://keycloak.opentdf.local:9443|g' /configs/opentdf.yaml
        echo "Patched opentdf.yaml to use keycloak.opentdf.local:9443"
    restart: "no"

  # Download Keycloak provisioning data
  download-keycloak-data:
    image: alpine:3.23
    volumes:
      - configs:/configs
    depends_on:
      init-volumes:
        condition: service_completed_successfully
    entrypoint: /bin/sh
    command:
      - -c
      - |
        URL='https://raw.githubusercontent.com/opentdf/platform/main/service/cmd/keycloak_data.yaml'
        OUTPUT='/configs/keycloak_data.yaml'
        MAX_ATTEMPTS=3
        
        for i in $$(seq 1 $$MAX_ATTEMPTS); do
          echo "Attempt $$i of $$MAX_ATTEMPTS: Downloading keycloak_data.yaml..."
          
          if wget -O "$$OUTPUT" "$$URL"; then
            echo "Download successful"
            
            # Validate the downloaded file
            if [ -f "$$OUTPUT" ] && [ -s "$$OUTPUT" ]; then
              if head -1 "$$OUTPUT" | grep -q -E '^(---|\w+:)'; then
                echo "Validation passed: File exists, non-empty, and appears to be valid YAML"
                exit 0
              else
                echo "Validation failed: File does not appear to be valid YAML"
                rm -f "$$OUTPUT"
              fi
            else
              echo "Validation failed: File is missing or empty"
            fi
          else
            echo "Download failed (attempt $$i)"
          fi
          
          if [ $$i -lt $$MAX_ATTEMPTS ]; then
            echo "Retrying in 2 seconds..."
            sleep 2
          fi
        done
        
        echo "ERROR: Failed to download and validate keycloak_data.yaml after $$MAX_ATTEMPTS attempts"
        exit 1
    restart: "no"

  # Download fixtures data
  download-fixtures:
    image: alpine:3.23
    volumes:
      - configs:/configs
    depends_on:
      init-volumes:
        condition: service_completed_successfully
    command: ['wget', '-O', '/configs/service/internal/fixtures/policy_fixtures.yaml', 'https://raw.githubusercontent.com/opentdf/platform/main/service/internal/fixtures/policy_fixtures.yaml']
    restart: "no"

  # Download init-temp-keys script
  download-init-script:
    image: alpine:3.23
    volumes:
      - configs:/configs
    depends_on:
      init-volumes:
        condition: service_completed_successfully
    entrypoint: /bin/sh
    command:
      - -c
      - |
        URL='https://raw.githubusercontent.com/opentdf/platform/main/.github/scripts/init-temp-keys.sh'
        OUTPUT='/configs/init-temp-keys.sh'
        MAX_ATTEMPTS=3
        
        for i in $$(seq 1 $$MAX_ATTEMPTS); do
          echo "Attempt $$i of $$MAX_ATTEMPTS: Downloading init-temp-keys.sh..."
          
          if wget -O "$$OUTPUT" "$$URL"; then
            echo "Download successful"
            
            # Validate the downloaded file
            if [ -f "$$OUTPUT" ] && [ -s "$$OUTPUT" ]; then
              if head -1 "$$OUTPUT" | grep -q '^#!/'; then
                echo "Validation passed: File exists, non-empty, and appears to be a shell script"
                exit 0
              else
                echo "Validation failed: File does not appear to be a valid shell script"
                rm -f "$$OUTPUT"
              fi
            else
              echo "Validation failed: File is missing or empty"
            fi
          else
            echo "Download failed (attempt $$i)"
          fi
          
          if [ $$i -lt $$MAX_ATTEMPTS ]; then
            echo "Retrying in 2 seconds..."
            sleep 2
          fi
        done
        
        echo "ERROR: Failed to download and validate init-temp-keys.sh after $$MAX_ATTEMPTS attempts"
        exit 1
    restart: "no"

  # Generate keys without Docker dependency
  generate-keys:
    image: alpine:3.23
    volumes:
      - configs:/configs
      - keys:/keys
    depends_on:
      download-init-script:
        condition: service_completed_successfully
      init-volumes:
        condition: service_completed_successfully
    entrypoint: /bin/sh
    command:
      - -c
      - |
        apk add --no-cache openssl openjdk11-jre bash
        cd /keys
        
        # Generate KAS RSA private key
        openssl genpkey -algorithm RSA -out /keys/kas-private.pem -pkeyopt rsa_keygen_bits:2048
        openssl rsa -in /keys/kas-private.pem -pubout -out /keys/kas-cert.pem
        
        # Generate ECC Key
        openssl ecparam -name prime256v1 > /tmp/ecparams.tmp
        openssl req -x509 -nodes -newkey ec:/tmp/ecparams.tmp -subj "/CN=kas" -keyout /keys/kas-ec-private.pem -out /keys/kas-ec-cert.pem -days 365
        
        # Generate CA
        openssl req -x509 -nodes -newkey RSA:2048 -subj "/CN=ca" -keyout /keys/keycloak-ca-private.pem -out /keys/keycloak-ca.pem -days 365
        
        # Generate localhost certificate
        printf "subjectAltName=DNS:localhost,IP:127.0.0.1" > /tmp/sanX509.conf
        printf "[req]\ndistinguished_name=req_distinguished_name\n[req_distinguished_name]\n[alt_names]\nDNS.1=localhost\nIP.1=127.0.0.1" > /tmp/req.conf
        openssl req -new -nodes -newkey rsa:2048 -keyout /keys/localhost.key -out /tmp/localhost.req -batch -subj "/CN=localhost" -config /tmp/req.conf
        openssl x509 -req -in /tmp/localhost.req -CA /keys/keycloak-ca.pem -CAkey /keys/keycloak-ca-private.pem -CAcreateserial -out /keys/localhost.crt -days 3650 -sha256 -extfile /tmp/sanX509.conf
        
        # Generate sample user certificate
        openssl req -new -nodes -newkey rsa:2048 -keyout /keys/sampleuser.key -out /tmp/sampleuser.req -batch -subj "/CN=sampleuser"
        openssl x509 -req -in /tmp/sampleuser.req -CA /keys/keycloak-ca.pem -CAkey /keys/keycloak-ca-private.pem -CAcreateserial -out /keys/sampleuser.crt -days 3650
        
        # Convert to PKCS12
        openssl pkcs12 -export -in /keys/keycloak-ca.pem -inkey /keys/keycloak-ca-private.pem -out /keys/ca.p12 -nodes -passout pass:password
        
        # Convert PKCS12 to JKS using keytool (no Docker needed)
        keytool -importkeystore \
          -srckeystore /keys/ca.p12 \
          -srcstoretype PKCS12 \
          -destkeystore /keys/ca.jks \
          -deststoretype JKS \
          -srcstorepass "password" \
          -deststorepass "password" \
          -noprompt
        
        echo "Keys generated successfully"
    environment:
      JAVA_OPTS_APPEND: "${JAVA_OPTS_APPEND:-}"
    restart: "no"

  # Generate hybrid post-quantum KAS keys (X-Wing, P256+ML-KEM-768, P384+ML-KEM-1024).
  # Uses a Go image to build and run the keygen from the platform source.
  generate-pqc-keys:
    image: golang:1.25-alpine
    volumes:
      - keys:/keys
    depends_on:
      generate-keys:
        condition: service_completed_successfully
    entrypoint: /bin/sh
    command:
      - -c
      - |
        set -e
        apk add --no-cache git
        WORKDIR=$$(mktemp -d)
        cd "$$WORKDIR"
        git init -q
        git remote add origin https://github.com/opentdf/platform.git
        git config core.sparseCheckout true
        echo "lib/" >> .git/info/sparse-checkout
        echo "service/cmd/keygen/" >> .git/info/sparse-checkout
        echo "service/go.mod" >> .git/info/sparse-checkout
        echo "service/go.sum" >> .git/info/sparse-checkout
        echo "protocol/" >> .git/info/sparse-checkout
        echo "sdk/" >> .git/info/sparse-checkout
        git pull --depth 1 -q origin main
        cd service
        GOWORK=off go run ./cmd/keygen -output /keys
        echo "PQC keys generated successfully"
        rm -rf "$$WORKDIR"
    restart: "no"

volumes:
  keys:
    name: opentdf_keys
  configs:
    name: opentdf_configs
  caddy_data:
  platform_certs:
    name: opentdf_platform_certs

