# ──────────────────────────────────────────────────────────────────
# Ulicraft LAN-party stack — Minecraft 1.21.1 NeoForge + Drasl auth
# ──────────────────────────────────────────────────────────────────
# Single, unified compose. One file holds the whole stack:
#   drasl        auth + skins (Yggdrasil), internal only
#   minecraft    the server (itzg/NEOFORGE), installs mods via packwiz
#   mc-backup    world backups every 6h via RCON
#   caddy        internal ingress — routes auth./pack./apex/launcher/avatar.
#                /distribution. by Host header (conf.d/*.caddy)
#   nmsr         skin/avatar renderer at avatar.${BASE_DOMAIN}
#   uptime-kuma  status monitoring at status.${BASE_DOMAIN}
#
# Bring up:  docker compose up -d   (render configs first: tooling/render-config.sh)
#
# DNS is handled OUTSIDE this repo — point every service name at the host.
# Production TLS terminates at the HOST's nginx in front of caddy
# (nginx/ulicraft-caddy.conf.tmpl + Let's Encrypt); caddy is published on a
# localhost-only port (CADDY_HTTP_BIND/CADDY_HTTP_PORT in .env). Containers reach
# the stack's own names internally via caddy's mcnet aliases (auth./pack.).
# ──────────────────────────────────────────────────────────────────

services:
  drasl:
    image: unmojang/drasl:latest
    container_name: drasl
    restart: unless-stopped
    # Internal-only: no published host port. Reached via caddy at
    # auth.${BASE_DOMAIN} (caddy holds the network alias on mcnet).
    volumes:
      - ./drasl/config:/etc/drasl:ro
      - drasl_state:/var/lib/drasl
    networks:
      - mcnet
    # No env vars — config is the rendered ./drasl/config/config.toml
    # (from config.toml.tmpl via tooling/render-config.sh).

  minecraft:
    image: itzg/minecraft-server:java21
    container_name: minecraft
    restart: unless-stopped
    stdin_open: true
    tty: true
    ports:
      - "25565:25565"
      - "24454:24454/udp"      # Simple Voice Chat, if used
    environment:
      EULA: "TRUE"
      TYPE: "NEOFORGE"
      VERSION: "1.21.1"
      # Verify NEOFORGE_VERSION against projects.neoforged.net before deploy.
      NEOFORGE_VERSION: "21.1.209"   # pin a known-good build; update deliberately

      # ── Memory / performance ────────────────────────────────────
      INIT_MEMORY: "4G"
      MAX_MEMORY: "10G"               # 8 players + 100 mods comfortably
      USE_AIKAR_FLAGS: "TRUE"
      JVM_XX_OPTS: "-XX:+UseG1GC"

      # ── Auth: offline mode + authlib-injector pointing at Drasl ─
      ONLINE_MODE: "FALSE"
      # Resolves over mcnet to caddy (alias auth.${BASE_DOMAIN}) -> drasl.
      JVM_OPTS: "-javaagent:/extras/authlib-injector.jar=http://auth.${BASE_DOMAIN}/authlib-injector"

      # ── Server config ───────────────────────────────────────────
      DIFFICULTY: "normal"
      MODE: "survival"
      MOTD: "Uli LAN party"
      MAX_PLAYERS: "10"
      VIEW_DISTANCE: "10"
      SIMULATION_DISTANCE: "8"
      ENFORCE_SECURE_PROFILE: "FALSE"  # required for authlib-injector on 1.21+
      ALLOW_FLIGHT: "TRUE"             # many tech mods need this

      # ── Mod source: packwiz pack served by caddy (alias pack.) ──
      PACKWIZ_URL: "http://pack.${BASE_DOMAIN}/pack.toml"

      # ── RCON for remote admin ───────────────────────────────────
      ENABLE_RCON: "TRUE"
      RCON_PASSWORD: ${RCON_PASSWORD}
      RCON_PORT: 25575

      TZ: "Europe/Madrid"
    volumes:
      - mc_data:/data
      - ./runtime:/extras:ro       # authlib-injector.jar lives here
    depends_on:
      - drasl
      - caddy                      # pack./auth. must resolve at boot
    # Containers don't inherit the host's /etc/hosts; the LAN resolver returns a
    # dead address for the public names. Pin auth./pack. to the host so HTTPS to
    # them lands on the host's nginx (valid LE cert) -> caddy -> service.
    extra_hosts:
      - "auth.${BASE_DOMAIN}:host-gateway"
      - "pack.${BASE_DOMAIN}:host-gateway"
    networks:
      - mcnet

  mc-backup:
    image: itzg/mc-backup
    container_name: mc-backup
    restart: unless-stopped
    environment:
      BACKUP_INTERVAL: "6h"
      RCON_HOST: "minecraft"
      RCON_PASSWORD: ${RCON_PASSWORD}
      PRUNE_BACKUPS_DAYS: "14"
      TZ: "Europe/Madrid"
    volumes:
      - mc_data:/data:ro
      - ./backups:/backups
    depends_on:
      - minecraft
    networks:
      - mcnet

  # Internal ingress. Routes every vhost by Host header (conf.d/*.caddy):
  #   auth. -> drasl, pack. -> packwiz files, apex -> landing + /launcher,
  #   avatar. -> nmsr, status. -> uptime-kuma, distribution. -> static site.
  # Published on a localhost-only port; the host's nginx terminates TLS in
  # front of it (nginx/ulicraft-caddy.conf.tmpl).
  caddy:
    image: caddy:alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "${CADDY_HTTP_BIND:-127.0.0.1}:${CADDY_HTTP_PORT:-8880}:80"
    environment:
      BASE_DOMAIN: ${BASE_DOMAIN}
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/conf.d/00-core.caddy:/etc/caddy/conf.d/00-core.caddy:ro
      - ./caddy/conf.d/10-static.caddy:/etc/caddy/conf.d/10-static.caddy:ro
      - ./caddy/conf.d/30-distribution.caddy:/etc/caddy/conf.d/30-distribution.caddy:ro
      - ./caddy/conf.d/40-avatar.caddy:/etc/caddy/conf.d/40-avatar.caddy:ro
      - ./caddy/conf.d/50-status.caddy:/etc/caddy/conf.d/50-status.caddy:ro
      - ./www:/srv/www:ro
      - ./launcher:/srv/launcher:ro
      - ./pack:/srv/pack:ro
      # Custom (locally-hosted) mod jars live OUTSIDE ./pack so packwiz refresh
      # doesn't index them as direct files. Served at pack.${BASE_DOMAIN}/custom/.
      # Nested under the :ro /srv/pack mount — the mountpoint pack/custom/ must
      # pre-exist on the host (tracked via pack/custom/.gitkeep) or Docker can't
      # mkdir it in the read-only parent.
      - ./custom:/srv/pack/custom:ro
      # Static site from ANOTHER repo (absolute host path in .env).
      - ${DISTRIBUTION_WEB_ROOT:?DISTRIBUTION_WEB_ROOT unset in .env}:/srv/distribution:ro
    networks:
      mcnet:
        # Containers resolve the stack's own public names to caddy internally.
        aliases:
          - "auth.${BASE_DOMAIN}"
          - "pack.${BASE_DOMAIN}"

  # Avatar/skin renderer — built from source (docker/nmsr/Dockerfile), pulls
  # player profiles from Drasl over mcnet. caddy proxies avatar. -> nmsr:8080.
  nmsr:
    build:
      context: ./docker/nmsr
    image: ulicraft/nmsr:local
    container_name: nmsr
    restart: unless-stopped
    volumes:
      - ./nmsr/config.toml:/nmsr/config.toml:ro
    networks:
      - mcnet

  # Self-hosted SLP→JSON pinger (built from docker/mc-status, mcutil wrapper).
  # Emits mcstatus.io v2 JSON so the landing's ServerCard reads it unchanged.
  # No published port — caddy proxies apex /api/mcstatus/* -> mc-status:8080.
  # Pings the minecraft service internally over mcnet; result cached 30s.
  mc-status:
    build:
      context: ./docker/mc-status
    image: ulicraft/mc-status:local
    container_name: mc-status
    restart: unless-stopped
    environment:
      MC_STATUS_TARGET: "minecraft:25565"
      MC_STATUS_CACHE_TTL: "30s"
    networks:
      - mcnet

  # Status monitoring + public status page. caddy proxies status. -> :3001.
  # Joins mcnet so monitors probe the stack by internal service name.
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    volumes:
      - kuma_data:/app/data
    networks:
      - mcnet

volumes:
  drasl_state:
  mc_data:
  kuma_data:

networks:
  mcnet:
    driver: bridge
