# muninn-gate — WG-internal HTTP front door for the Muninn ingestion queue (#377).
# Deployed on corviduo-dev (10.250.50.152), co-located with the worldtree-personal
# muninn watcher (worldtree-personal-worldtree-muninn-1). infra-ops-managed stack,
# separate from the worldtree CI/CD compose.
#
# Image is built out-of-band (the Gitea read token rides as a BuildKit secret, so
# it never lands in a layer) — see README.md:
#   DOCKER_BUILDKIT=1 docker build --secret id=gitea_pw,src=<token> -t muninn-gate:0.0.14 .
#
# Real config (with bearer-key secrets) lives on the server at
# /opt/docker/conf/muninn-gate/muninn-gate.yaml (gitignored); repo carries the
# redacted conf/muninn-gate.example.yaml.
services:
  muninn-gate:
    image: muninn-gate:0.0.14
    container_name: muninn-gate
    restart: unless-stopped
    # Run as the ingestion-owner uid (vh, 1000:1000) so the gate can WRITE the
    # queue (ingestion dir is 1000:1000 0755). Overrides the image's build user;
    # non-root, least-privilege (no root needed — binds :8090, reads config +
    # staging, writes only ingestion). The mounted config is chowned 1000:1000 0600.
    user: "1000:1000"
    # WG-internal bind straight to the host WG address; the launcher owns the bind
    # (the app's `bind` field is observability-only). Host networking so --host/--port
    # land directly on corviduo-dev.
    network_mode: host
    command:
      - uvicorn
      - muninn_gate.entrypoint:app_factory
      - --factory
      - --host
      - "10.250.50.152"
      - --port
      - "8090"
    environment:
      MUNINN_GATE_CONFIG: /app/config/muninn-gate.yaml
    volumes:
      # ingestion_root = /data/state/ingestion — the SAME volume the personal muninn
      # watcher uses, byte-identical path in both containers (absolute-path agreement).
      # NOTE (hardening candidate): whole state volume per muninn-dev's spec; a subpath
      # mount of just `ingestion` -> /data/state/ingestion would be tighter (gate only
      # needs RW on ingestion). Flag to muninn-dev before adopting.
      - worldtree-personal_worldtree-state:/data/state
      # single-writer mounted config (bearer-key secrets), read-only:
      - /opt/docker/conf/muninn-gate/muninn-gate.yaml:/app/config/muninn-gate.yaml:ro
      # shared staging root — SAME absolute path as the watcher's bind, read-only
      # (gate only resolves + reads staging; never writes there):
      - /mnt/muninn-staging/mimir-inbox:/mnt/muninn-staging/mimir-inbox:ro
    healthcheck:
      # /ping is the one anonymous route; returns {"service":"ok"}. NB /health must
      # NOT be the liveness probe — it is always-200 by design even when the watcher
      # is down, so it would never restart the gate (it would wrongly signal the
      # watcher's container).
      test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if b'ok' in urllib.request.urlopen('http://10.250.50.152:8090/ping', timeout=3).read() else 1)"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    labels:
      - homepage.group=Worldtree
      - homepage.name=Muninn Gate
      - homepage.icon=mdi-gate-arrow-right
      - homepage.description=Muninn ingestion queue front door (#377)
      - homepage.href=http://10.250.50.152:8090/ping

volumes:
  # The worldtree-personal muninn watcher's state volume — external, managed by the
  # worldtree-personal compose. We attach to it read/write for the ingestion queue.
  worldtree-personal_worldtree-state:
    external: true
