# Local Hub overlay (`mesh start --with-hub`).
#
# Images are built from the published @mesh-tech/hub tarball on first use
# (see hub-local.ts) — the interim path until the hub pipeline publishes
# pullable images. Requires one-time registry access (mesh registry login);
# the core profile stays zero-access without this overlay.
#
# The Hub reads the SAME local fabric the apps use: SSM registry via
# ministack (x-local-aws-env in the base file), the seeded `hub` database,
# Temporal, SpiceDB — and the local observability backends (Loki logs,
# Tempo traces, Prometheus metrics via the base file's OTel collector), so
# the Hub's operational views run the exact hosted code paths. Only the
# K8s-API views (pod state/metrics) have no local backend.
#
# No HUB_TENANT here on purpose: a local platform is a STANDALONE hub, so
# hub-api resolves the single-tenant conventions ({tenant}-{env}-{app} for
# K8s + Temporal namespaces) that everything local creates — zero
# local-vs-hosted translation.

services:
  hub-api:
    image: mesh-local-hub-api:${MESH_HUB_VERSION:-latest}
    ports:
      # 4568 host-side: the 3000 range belongs to `mesh dev` app services
      - '4568:3002'
    environment:
      AWS_ENDPOINT_URL: http://ministack:4566
      AWS_REGION: us-east-2
      AWS_ACCESS_KEY_ID: test
      AWS_SECRET_ACCESS_KEY: test
      PORT: 3002
      PULUMI_STATE_BUCKET: mesh-local-artifacts
      DATABASE_URL: postgres://postgres:postgres@database:5432/hub?sslmode=disable
      OPS_DB_HOST: database
      OPS_DB_PORT: 5432
      OPS_DB_NAME: hub
      OPS_DB_USER: postgres
      DATABASE_PASSWORD: postgres
      PGSSLMODE: disable
      TEMPORAL_ADDRESS: temporal:7233
      TEMPORAL_NAMESPACE: local-dev
      TEMPORAL_UI_URL: http://localhost:8233
      LOKI_URL: http://loki:3100
      TEMPO_URL: http://tempo:3200
      PROMETHEUS_URL: http://prometheus:9090
      # The REAL issuer URL (what `mesh login local` tokens carry). Valid
      # inside the container via the Dockerfile's loopback forward to the
      # zitadel service — Zitadel routes by Host header, so the issuer host
      # must be `localhost` everywhere, exactly like the hosted public URL.
      ZITADEL_ISSUER: http://localhost:8080
      # The Hub's ADMIN plane. Without this the Hub can verify a token but cannot
      # write to Zitadel, so creating a user or minting an API key silently has
      # nowhere to go — the console offers no affordance and nothing reaches the
      # IdP. `mesh start` seeds the machine key this points at (seed-zitadel).
      ZITADEL_OPSHUB_SECRET_NAME: mesh/local/dev/zitadel/ops-hub
      SPICEDB_ENDPOINT: spicedb:50051
      SPICEDB_HTTP_ENDPOINT: http://spicedb:8443
      SPICEDB_HTTP_SCHEME: http
      SPICEDB_PRESHARED_KEY: local-dev-key
      CORS_ORIGINS: http://localhost:${MESH_HUB_PORT:-9000}
      MESH_LOCAL: "1"
    depends_on:
      database:
        condition: service_healthy
      ministack:
        condition: service_healthy
    networks:
      - mesh_network

  hub-ui:
    image: mesh-local-hub-ui:${MESH_HUB_VERSION:-latest}
    environment:
      PORT: 9000
      API_URL: http://hub-api:3002
      # Browser-facing API base (SSR link env is in-network; the browser
      # needs the host-published port).
      PUBLIC_API_URL: http://localhost:4568
      AGENT_API_URL: http://hub-api:3002
      MESH_LOCAL: "1"
    depends_on:
      - hub-api
    networks:
      - mesh_network

  # oauth2-proxy in front of the Hub UI — the SAME sidecar mesh.apps.Service
  # deploys with `auth.provider: "mesh"` (image + flags mirrored from
  # Service.ts; local deltas: cookie-secure=false because http, in-network
  # upstream instead of localhost, and the roles scope the hub reads).
  # Serves the Hub at http://localhost:9000: unauthenticated browsers land on
  # the local Zitadel login, then get X-Forwarded-Access-Token / -User /
  # -Email + Authorization headers forwarded — exactly like deployed.
  hub-auth:
    # Built locally from stack/hub/Dockerfile.auth (public images only):
    # oauth2-proxy v7.7.1 + the in-container loopback forward that makes the
    # real issuer URL valid here (Zitadel routes by Host header).
    image: mesh-local-hub-auth:v7.7.1-r1
    restart: unless-stopped
    ports:
      # MESH_HUB_PORT lets this Hub run beside another process that owns
      # 9000 (e.g. a hub-ui dev server from a different checkout). The CLI's
      # status probes + Zitadel seeding read the same variable.
      - '${MESH_HUB_PORT:-9000}:4180'
    command:
      - --http-address=0.0.0.0:4180
      - --upstream=http://hub-ui:9000
      - --provider=oidc
      # The ONE issuer URL tokens carry; valid in-container via the
      # baked-in loopback forward.
      - --oidc-issuer-url=http://localhost:8080
      - --scope=openid email profile urn:zitadel:iam:org:project:roles
      - --email-domain=*
      # RP-initiated logout: hub-ui's /logout redirects through
      # /oauth2/sign_out?rd=<issuer end_session>; rd hosts must be
      # whitelisted (same flag the Service sidecar sets in the cloud).
      - --whitelist-domain=localhost:8080
      - --pass-authorization-header=true
      - --pass-access-token=true
      - --pass-user-headers=true
      - --cookie-secure=false
      - --cookie-samesite=lax
      - --cookie-refresh=4h
      - --cookie-expire=12h
      - --skip-jwt-bearer-tokens=true
      - --skip-provider-button=true
      - --silence-ping-logging=true
      - --auth-logging=true
      - --standard-logging=true
      - --redirect-url=http://localhost:${MESH_HUB_PORT:-9000}/oauth2/callback
    environment:
      OAUTH2_PROXY_CLIENT_ID: ${MESH_HUB_OAUTH2_CLIENT_ID:-unseeded}
      OAUTH2_PROXY_CLIENT_SECRET: ${MESH_HUB_OAUTH2_CLIENT_SECRET:-unseeded}
      # Placeholder (raw 32 bytes) keeps the container from crash-looping
      # pre-seed; the CLI always passes the real persisted secret
      # (hub-auth.json).
      OAUTH2_PROXY_COOKIE_SECRET: ${MESH_HUB_OAUTH2_COOKIE_SECRET:-local-dev-placeholder-cookie-32b}
    depends_on:
      - hub-ui
    networks:
      - mesh_network
