services:
  # Keycloak: OIDC Provider (Recommended)
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: DEV_keycloak
    environment:
      # Admin credentials (change for production!)
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: admin
      # Database settings
      KC_DB: postgres
      KC_DB_URL_HOST: postgres
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: keycloak
      # Hostname configuration for development
      # Using full URL so issuer matches what fail2ban-ui expects
      # When using KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true, hostname must be a full URL
      # Set KEYCLOAK_PUBLIC_URL environment variable to your server's public URL
      # If not set, defaults to http://localhost:3000
      KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL:-http://localhost:3000}
      KC_HOSTNAME_STRICT: "false"
      KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true"
      # Development mode (use "start" for production)
      KC_HEALTH_ENABLED: "true"
      KC_METRICS_ENABLED: "true"
    command:
      - start-dev
      - --http-host=0.0.0.0
      - --http-port=8080
    ports:
      - "0.0.0.0:3000:8080"
      - "0.0.0.0:9000:9000"  # Management port for health checks
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./keycloak-data:/opt/keycloak/data:z
    healthcheck:
      # Keycloak v26+ uses port 9000 for health endpoints (management port)
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '200 OK' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 30
      start_period: 90s
    restart: unless-stopped
    networks:
      - oidc-network

  # PostgreSQL: Database for Keycloak
  postgres:
    image: postgres:15-alpine
    container_name: DEV_keycloak-db
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: keycloak
    volumes:
      - ./keycloak-db:/var/lib/postgresql/data:z
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - oidc-network

  # Pocket-ID: OIDC Provider (Alternative 1) - Not fully tested
  # Uncomment to use Pocket-ID instead of Keycloak
  # pocket-id:
  #   image: ghcr.io/pocket-id/pocket-id:latest
  #   container_name: DEV_pocket-id
  #   environment:
  #     - APP_URL=http://localhost:3005
  #     # Encryption key (choose one method):
  #     # Method 1: Direct key (simple but less secure)
  #     # Generate with: openssl rand -base64 32
  #     - ENCRYPTION_KEY=aF98qjUZuCJ_j_9Bh8hXx5U832ZpYQW
  #     # Method 2: File-based key (recommended)
  #     # Put the base64 key in a file and point to it here.
  #     # ENCRYPTION_KEY_FILE=/path/to/encryption_key
  #     - TRUST_PROXY=false
  #     - MAXMIND_LICENSE_KEY=
  #     - PUID=1000
  #     - PGID=1000
  #     - DB_PROVIDER=sqlite
  #     - DB_CONNECTION_STRING=sqlite:///pocket-id/var/db.sqlite
  #     - PORT=1411
  #   volumes:
  #     - ./pocket-id-data:/pocket-id/var:z
  #   ports:
  #     - "3005:1411"
  #   restart: unless-stopped
  #   networks:
  #     - oidc-network

  # Authentik: OIDC Provider (Alternative 2) - Not fully tested
  # Uncomment to use Authentik instead of Keycloak
  # Note: Authentik requires additional setup (migrations, initial admin user)
  # authentik-server:
  #   image: ghcr.io/goauthentik/server:latest
  #   container_name: DEV_authentik-server
  #   environment:
  #     - AUTHENTIK_SECRET_KEY=dev-secret-key-change-in-production-generate-with-openssl-rand-base64-60
  #     - AUTHENTIK_ERROR_REPORTING__ENABLED=false
  #     - AUTHENTIK_DISABLE_UPDATE_CHECK=true
  #     - AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
  #     - PG_PASS=authentik
  #     - AUTHENTIK_REDIS__HOST=redis
  #     - AUTHENTIK_POSTGRESQL__HOST=postgresql
  #     - AUTHENTIK_POSTGRESQL__USER=authentik
  #     - AUTHENTIK_POSTGRESQL__PASSWORD=authentik
  #     - AUTHENTIK_POSTGRESQL__NAME=authentik
  #   ports:
  #     - "3007:9000"
  #     - "3008:9443"
  #   depends_on:
  #     - authentik-postgresql
  #     - authentik-redis
  #   volumes:
  #     - ./authentik-media:/media:z
  #   restart: unless-stopped
  #   networks:
  #     - oidc-network
  #
  # authentik-worker:
  #   image: ghcr.io/goauthentik/server:latest
  #   container_name: DEV_authentik-worker
  #   environment:
  #     - AUTHENTIK_SECRET_KEY=dev-secret-key-change-in-production-generate-with-openssl-rand-base64-60
  #     - AUTHENTIK_ERROR_REPORTING__ENABLED=false
  #     - AUTHENTIK_DISABLE_UPDATE_CHECK=true
  #     - AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
  #     - PG_PASS=authentik
  #     - AUTHENTIK_REDIS__HOST=redis
  #     - AUTHENTIK_POSTGRESQL__HOST=postgresql
  #     - AUTHENTIK_POSTGRESQL__USER=authentik
  #     - AUTHENTIK_POSTGRESQL__PASSWORD=authentik
  #     - AUTHENTIK_POSTGRESQL__NAME=authentik
  #   command: ["authentik", "worker"]
  #   depends_on:
  #     - authentik-postgresql
  #     - authentik-redis
  #   volumes:
  #     - ./authentik-media:/media:z
  #   restart: unless-stopped
  #   networks:
  #     - oidc-network
  #
  # authentik-postgresql:
  #   image: postgres:15-alpine
  #   container_name: DEV_authentik-db
  #   environment:
  #     POSTGRES_PASSWORD: authentik
  #     POSTGRES_USER: authentik
  #     POSTGRES_DB: authentik
  #   volumes:
  #     - ./authentik-db:/var/lib/postgresql/data:z
  #   restart: unless-stopped
  #   networks:
  #     - oidc-network
  #
  # authentik-redis:
  #   image: redis:7-alpine
  #   container_name: DEV_authentik-redis
  #   restart: unless-stopped
  #   networks:
  #     - oidc-network

  # Keycloak Client Auto-Configuration
  # This container automatically creates the OIDC client in Keycloak
  # Set PUBLIC_FRONTEND_URL environment variable to your server's public URL (e.g., http://192.168.1.100:3080)
  # If not set, defaults to http://localhost:3080
  keycloak-init:
    image: curlimages/curl:latest
    container_name: DEV_keycloak-init
    depends_on:
      keycloak:
        condition: service_healthy
    environment:
      - KEYCLOAK_URL=http://keycloak:8080
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_PASSWORD=admin
      - REALM=master
      - CLIENT_ID=fail2ban-ui
      # PUBLIC_FRONTEND_URL: Set this to your server's public URL (IP or hostname)
      # If not set, defaults to http://localhost:3080
      - PUBLIC_FRONTEND_URL=${PUBLIC_FRONTEND_URL:-http://localhost:3080}
      - SECRET_FILE=/config/keycloak-client-secret
    volumes:
      - ./init-keycloak.sh:/init-keycloak.sh:ro
      - ./config:/config:z
    # Run as root to avoid permission issues
    user: "0:0"
    command: ["/bin/sh", "-c", "apk add --no-cache jq 2>/dev/null && /bin/sh /init-keycloak.sh 2>&1"]
    networks:
      - oidc-network
    restart: "no"  # Only run once

  # Fail2ban-UI: Main application with OIDC authentication
  fail2ban-ui:
    image: localhost/fail2ban-ui:dev
    container_name: DEV_fail2ban-ui-oidc
    privileged: true
    # Use host network to access Keycloak on localhost:3000
    # This allows issuer URL to match Keycloak's discovery document
    network_mode: host
    # ports:
    #   - "3080:8080"  # Not needed with host network
    environment:
      - PORT=3080
      - BIND_ADDRESS=0.0.0.0
      
      # OIDC Configuration for Keycloak (default)
      # PUBLIC_FRONTEND_URL: Server's public URL for redirects (if not set, defaults to http://localhost:3080)
      # KEYCLOAK_URL: Server's public URL for Keycloak (if not set, defaults to http://localhost:3000)
      # Create a .env file with these variables for remote server access
      - PUBLIC_FRONTEND_URL=${PUBLIC_FRONTEND_URL:-http://localhost:3080}
      - KEYCLOAK_URL=${KEYCLOAK_URL:-http://localhost:3000}
      # OIDC settings
      - OIDC_ENABLED=true
      - OIDC_PROVIDER=keycloak
      - OIDC_ISSUER_URL=${KEYCLOAK_URL}/realms/master
      - OIDC_CLIENT_ID=fail2ban-ui
      - OIDC_CLIENT_SECRET=auto-configured
      - OIDC_CLIENT_SECRET_FILE=/config/keycloak-client-secret
      - OIDC_REDIRECT_URL=${PUBLIC_FRONTEND_URL}/auth/callback
      - OIDC_SCOPES=openid,profile,email
      - OIDC_SESSION_MAX_AGE=7200
      - OIDC_USERNAME_CLAIM=preferred_username
      - OIDC_SKIP_VERIFY=true
      # Optional: Skip login page and redirect directly to OIDC provider (default: false)
      # When set to true, users are immediately redirected to the OIDC provider without showing the login page
      #- OIDC_SKIP_LOGINPAGE=true
      # Optional: Logout URL
      #- OIDC_LOGOUT_URL=${KEYCLOAK_URL}/realms/master/protocol/openid-connect/logout
      
      # Alternative OIDC Configuration Examples (uncomment and adjust as needed):
      # For Pocket-ID:
      # - OIDC_PROVIDER=pocketid
      # - OIDC_ISSUER_URL=http://localhost:3005
      # - OIDC_LOGOUT_URL=http://localhost:3005/logout
      #
      # For Authentik:
      # - OIDC_PROVIDER=authentik
      # - OIDC_ISSUER_URL=http://localhost:3007/application/o/fail2ban-ui/

    volumes:
      # Required for fail2ban-ui: Stores SQLite database, application settings, and SSH keys
      - ./config:/config:z
      # Required for fail2ban-ui: Used for testing logpath before enabling jails
      - /var/log:/var/log:ro

      # Required for compose-local fail2ban instance: We mount the same Fail2Ban config as the linuxserver-fail2ban container (under /config/fail2ban to fail2ban-ui can modify configs)
      - ./fail2ban-config-local/fail2ban:/etc/fail2ban:z
      # Required for compose-local fail2ban instance: Mount the same run directory that contains fail2ban.sock for communication between fail2ban-ui and the linuxserver-fail2ban container
      - ./f2b-run-local:/var/run/fail2ban:z

    restart: unless-stopped
    # Wait for Keycloak to be healthy before starting
    # Note: keycloak-init runs as a one-time job, so we only wait for Keycloak
    # The application will retry connecting to Keycloak if needed
    # Note: With host network mode, depends_on still works but network isolation is bypassed
    depends_on:
      - keycloak

  # Fail2ban-Local: Local Fail2ban instance for testing local connector
  fail2ban-local:
    image: lscr.io/linuxserver/fail2ban:latest
    container_name: DEV_fail2ban-local
    cap_add:
      # Required for fail2ban container: Allows to manage network interfaces and iptables from the container
      - NET_ADMIN
      # Required for fail2ban container: Allows to create raw sockets (needed for fail2ban.sock)
      - NET_RAW
      # Required for fail2ban container: Allows to run as root (needed to manage network interfaces and raw sockets)
      - SYS_ADMIN
    #privileged: true
    network_mode: host # needed to add iptables rules to the host network
    environment:
      - TZ=Europe/Zurich
      - VERBOSITY=-vv
    volumes:
      # To make sure linuxserver-fail2ban configs are persistent across container restarts (also needed by fail2ban-ui to modify configs)
      - ./fail2ban-config-local:/config:z
      # Directory that contains fail2ban.sock for communication between fail2ban-ui and fail2ban container
      - ./f2b-run-local:/var/run/fail2ban:z

      # Log sources for fail2ban container
      - /var/log:/var/log:ro
      - /var/log/httpd:/remotelogs/apache2:ro
    restart: unless-stopped

networks:
  oidc-network:
    driver: bridge
