# docker-dev/ — Mac-friendly multi-cluster fleet for v2 development + manual UI exercise.
#
# Stack (3 separate Akka clusters — all share the single `OtOpcUa` ConfigDb):
#   sql        SQL Server 2022 — hosts the one ConfigDb that all three clusters use
#   ldap       OpenLDAP with the dev users from C:\publish\glauth\auth.md mirrored in
#
#   Main cluster (existing — split-role admin / driver pair on a single Akka mesh):
#     admin-a   OtOpcUa.Host with OTOPCUA_ROLES=admin (seed)
#     admin-b   OtOpcUa.Host with OTOPCUA_ROLES=admin (joins admin-a)
#     driver-a  OtOpcUa.Host with OTOPCUA_ROLES=driver (joins via admin-a)
#     driver-b  OtOpcUa.Host with OTOPCUA_ROLES=driver (joins via admin-a)
#
#   Site A cluster (2-node fused admin+driver):
#     site-a-1, site-a-2     OTOPCUA_ROLES=admin,driver, seed = site-a-1
#
#   Site B cluster (2-node fused admin+driver):
#     site-b-1, site-b-2     OTOPCUA_ROLES=admin,driver, seed = site-b-1
#
#   traefik    PathPrefix → main cluster admin-a/admin-b; Host(`site-a.localhost`) →
#              site-a-*; Host(`site-b.localhost`) → site-b-*. Add the two site hosts to
#              your /etc/hosts (or rely on macOS `.localhost` auto-resolution).
#
# Multi-tenancy: ConfigDb is one schema with a `ServerCluster` table; each Akka cluster
# corresponds to a row in it (ClusterId = "MAIN" / "SITE-A" / "SITE-B"), and each node's
# `ClusterNode.NodeId` points back at the row that owns it. After first boot, sign in to
# any cluster's Admin UI and create the matching ServerCluster + ClusterNode rows via
# /clusters and /hosts so the runtime knows what configuration scope applies.
#
# Akka mesh isolation: same system name "otopcua" + same remoting port 4053 inside each
# container's own network namespace, but with disjoint seed-node lists — gossip never
# crosses between the three meshes.
#
# Usage:
#   docker compose -f docker-dev/docker-compose.yml up -d --build
#   open http://localhost                  # main cluster Blazor admin UI
#   open http://site-a.localhost           # site A admin UI
#   open http://site-b.localhost           # site B admin UI
#   open http://localhost:8089             # Traefik dashboard (8080 is the sister scadalink stack)
#
# Tear-down:  docker compose -f docker-dev/docker-compose.yml down -v

name: otopcua-dev

services:

  sql:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      ACCEPT_EULA: "Y"
      SA_PASSWORD: "OtOpcUa!Dev123"
      MSSQL_PID: Developer
    ports:
      - "14330:1433"
    healthcheck:
      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'OtOpcUa!Dev123' -No -Q 'SELECT 1' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 20

  # ── Cluster seed (one-shot) ────────────────────────────────────────────────
  # Waits for SQL + the host containers' EF auto-migration, then INSERTs the
  # three ServerCluster rows and the six ClusterNode rows that scope each Akka
  # mesh inside the shared OtOpcUa ConfigDb. Idempotent — re-runs are no-ops.
  cluster-seed:
    image: mcr.microsoft.com/mssql-tools:latest
    depends_on:
      sql:
        condition: service_healthy
    volumes:
      - ./seed:/seed:ro
    entrypoint: ["/bin/bash", "/seed/entrypoint.sh"]
    restart: "no"

  # OpenLDAP was previously here but the bitnami/openldap:2.6 image was retired
  # (manifest gone) and bitnamilegacy/openldap:2.6 crashes during LDIF setup with
  # exit 68. For the dev compose every host container now runs with
  # Authentication__Ldap__DevStubMode=true, so any non-empty username/password
  # signs in as `FleetAdmin`. Restore a real LDAP service when there's a need
  # for end-to-end LDAP coverage (the host code path is unchanged).

  admin-a: &otopcua-host
    build:
      context: ..
      dockerfile: docker-dev/Dockerfile
    image: otopcua-host:dev
    depends_on:
      sql: { condition: service_healthy }
    environment:
      OTOPCUA_ROLES: "admin"
      ASPNETCORE_URLS: "http://+:9000"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "admin-a"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
      Cluster__Roles__0: "admin"
      Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
      Security__Jwt__Issuer: "otopcua-dev"
      Security__Jwt__Audience: "otopcua-dev"
      Authentication__Ldap__DevStubMode: "true"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"

  admin-b:
    <<: *otopcua-host
    environment:
      OTOPCUA_ROLES: "admin"
      ASPNETCORE_URLS: "http://+:9000"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "admin-b"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
      Cluster__Roles__0: "admin"
      Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
      Security__Jwt__Issuer: "otopcua-dev"
      Security__Jwt__Audience: "otopcua-dev"
      Authentication__Ldap__DevStubMode: "true"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"

  driver-a:
    <<: *otopcua-host
    environment:
      OTOPCUA_ROLES: "driver"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "driver-a"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
      Cluster__Roles__0: "driver"
      # Resolved at runtime by GalaxyDriver.ResolveApiKey when a DriverInstance's
      # Gateway.ApiKeySecretRef = "env:GALAXY_MXGW_API_KEY".
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"
    ports:
      - "4840:4840"

  driver-b:
    <<: *otopcua-host
    environment:
      OTOPCUA_ROLES: "driver"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "driver-b"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@admin-a:4053"
      Cluster__Roles__0: "driver"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"
    ports:
      - "4841:4840"

  # ── Site A cluster (2-node fused admin+driver) ──────────────────────────────
  # Shares the OtOpcUa ConfigDb with the main + site-b clusters; multi-tenancy is
  # enforced by ServerCluster.ClusterId rows (configure via /clusters after boot).
  # Akka isolation comes from the disjoint seed list (seed = site-a-1).

  site-a-1:
    <<: *otopcua-host
    environment:
      OTOPCUA_ROLES: "admin,driver"
      ASPNETCORE_URLS: "http://+:9000"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "site-a-1"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@site-a-1:4053"
      Cluster__Roles__0: "admin"
      Cluster__Roles__1: "driver"
      Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
      Security__Jwt__Issuer: "otopcua-dev"
      Security__Jwt__Audience: "otopcua-dev"
      Authentication__Ldap__DevStubMode: "true"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"
    ports:
      - "4842:4840"

  site-a-2:
    <<: *otopcua-host
    depends_on:
      sql: { condition: service_healthy }
      site-a-1: { condition: service_started }
    environment:
      OTOPCUA_ROLES: "admin,driver"
      ASPNETCORE_URLS: "http://+:9000"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "site-a-2"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@site-a-1:4053"
      Cluster__Roles__0: "admin"
      Cluster__Roles__1: "driver"
      Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
      Security__Jwt__Issuer: "otopcua-dev"
      Security__Jwt__Audience: "otopcua-dev"
      Authentication__Ldap__DevStubMode: "true"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"
    ports:
      - "4843:4840"

  # ── Site B cluster (2-node fused admin+driver) ──────────────────────────────

  site-b-1:
    <<: *otopcua-host
    environment:
      OTOPCUA_ROLES: "admin,driver"
      ASPNETCORE_URLS: "http://+:9000"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "site-b-1"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@site-b-1:4053"
      Cluster__Roles__0: "admin"
      Cluster__Roles__1: "driver"
      Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
      Security__Jwt__Issuer: "otopcua-dev"
      Security__Jwt__Audience: "otopcua-dev"
      Authentication__Ldap__DevStubMode: "true"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"
    ports:
      - "4844:4840"

  site-b-2:
    <<: *otopcua-host
    depends_on:
      sql: { condition: service_healthy }
      site-b-1: { condition: service_started }
    environment:
      OTOPCUA_ROLES: "admin,driver"
      ASPNETCORE_URLS: "http://+:9000"
      ConnectionStrings__ConfigDb: "Server=sql,1433;Database=OtOpcUa;User Id=sa;Password=OtOpcUa!Dev123;TrustServerCertificate=True;"
      Cluster__Hostname: "0.0.0.0"
      Cluster__Port: "4053"
      Cluster__PublicHostname: "site-b-2"
      Cluster__SeedNodes__0: "akka.tcp://otopcua@site-b-1:4053"
      Cluster__Roles__0: "admin"
      Cluster__Roles__1: "driver"
      Security__Jwt__SigningKey: "docker-dev-signing-key-with-at-least-32-bytes-of-utf8-content-12345"
      Security__Jwt__Issuer: "otopcua-dev"
      Security__Jwt__Audience: "otopcua-dev"
      Authentication__Ldap__DevStubMode: "true"
      GALAXY_MXGW_API_KEY: "${GALAXY_MXGW_API_KEY:-mxgw_otopcua2_GI7-tNozYE6cXGUSgEzL3AHDV7bYcYIHdMwKYgyHdX4}"
    ports:
      - "4845:4840"

  traefik:
    image: traefik:v3.1
    command:
      - --entrypoints.web.address=:80
      - --providers.file.filename=/etc/traefik/dynamic.yml
      - --providers.file.watch=true
      - --api.insecure=true
    ports:
      - "9200:80"  # host port 9200 → traefik :80 entrypoint (80 conflicts with scadabridge-traefik)
      - "8089:8080"  # 8080 conflicts with the sister scadalink dev stack
    volumes:
      - ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro
    depends_on:
      - admin-a
      - admin-b
      - site-a-1
      - site-a-2
      - site-b-1
      - site-b-2
