From 4c874e20ed02388bdc3dbd1b33aed3009dce2a18 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Sun, 7 Jun 2026 23:35:07 +0200 Subject: [PATCH] refactor(ingress): extract caddy to its own compose file; add nginx path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caddy is no longer in the base stack. Two ingress paths now: - docker-compose.caddy.yml — LAN/air-gap; build-stack.sh adds it for all up modes + the prep pre-bake. Holds the mcnet auth./pack. aliases. - docker-compose.nginx.yml — production behind the host's nginx: publishes drasl on 127.0.0.1:25585, points minecraft at the host (extra_hosts) over HTTPS for pack + authlib. Base loses the caddy service and minecraft's caddy depends_on (re-added by caddy.yml). Blessing Skin override now requires caddy.yml (it only swaps caddy's core conf). Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.blessingskin.yml | 6 ++-- docker-compose.caddy.yml | 46 +++++++++++++++++++++++++++++ docker-compose.nginx.yml | 30 +++++++++++++++++++ docker-compose.yml | 51 +++++++++------------------------ tooling/build-stack.sh | 8 ++++-- 5 files changed, 100 insertions(+), 41 deletions(-) create mode 100644 docker-compose.caddy.yml create mode 100644 docker-compose.nginx.yml diff --git a/docker-compose.blessingskin.yml b/docker-compose.blessingskin.yml index 72cf07e..b6d1633 100644 --- a/docker-compose.blessingskin.yml +++ b/docker-compose.blessingskin.yml @@ -1,9 +1,11 @@ # ────────────────────────────────────────────────────────────────── # Blessing Skin auth variant — use INSTEAD of Drasl. # ────────────────────────────────────────────────────────────────── -# Layer this over the base file: +# Layer this over the base file AND the caddy ingress (caddy lives in its own +# file now; this override only swaps caddy's core conf, so caddy.yml is required): # -# docker compose -f docker-compose.yml -f docker-compose.blessingskin.yml up -d +# docker compose -f docker-compose.yml -f docker-compose.caddy.yml \ +# -f docker-compose.blessingskin.yml up -d # # What it does vs the base stack: # - drasl → left running but UNUSED + unreachable (Caddy no longer diff --git a/docker-compose.caddy.yml b/docker-compose.caddy.yml new file mode 100644 index 0000000..63e91ac --- /dev/null +++ b/docker-compose.caddy.yml @@ -0,0 +1,46 @@ +# ────────────────────────────────────────────────────────────────── +# Caddy ingress — LAN / air-gap path. NOT started with the base stack. +# ────────────────────────────────────────────────────────────────── +# Caddy was moved out of docker-compose.yml so the base stack can run behind +# the host's nginx (production) without it. build-stack.sh adds this file for +# its online/airgap/core modes; static/mirror overrides mount extra vhosts in. +# +# docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d +# +# Holds the mcnet aliases (auth./pack.${BASE_DOMAIN}) so containers resolve the +# stack's own names internally — this is why minecraft can reach the pack here +# without extra_hosts (the nginx path uses extra_hosts instead). +services: + caddy: + image: caddy:alpine + container_name: caddy + restart: unless-stopped + ports: + - "80: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 + - ./www:/srv/www:ro + - ./mirror/launcher:/srv/launcher:ro + - ./caddy/caddy-root-ca.crt:/srv/ca-pub/ca.crt: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/. + - ./custom:/srv/pack/custom:ro + networks: + mcnet: + # The upstream spoof-host aliases live in docker-compose.mirror.yml — + # adding them here would hijack maven.neoforged.net etc. for ALL mcnet + # containers, breaking the ONLINE NeoForge install during pre-bake. + aliases: + - "auth.${BASE_DOMAIN}" + - "pack.${BASE_DOMAIN}" + + # Restore the ordering the base file used to have (base drops it so it can run + # caddy-less behind nginx). + minecraft: + depends_on: + - caddy diff --git a/docker-compose.nginx.yml b/docker-compose.nginx.yml new file mode 100644 index 0000000..6ad66cb --- /dev/null +++ b/docker-compose.nginx.yml @@ -0,0 +1,30 @@ +# ────────────────────────────────────────────────────────────────── +# Production ingress = the HOST's nginx + Let's Encrypt. No caddy. +# ────────────────────────────────────────────────────────────────── +# docker compose -f docker-compose.yml -f docker-compose.nginx.yml up -d +# +# nginx (on the host, already installed) terminates TLS with the LE certs from +# tooling/issue-letsencrypt.sh and: +# - serves apex + pack.${BASE_DOMAIN} as static files (./www, ./pack, ./custom) +# - reverse-proxies auth.${BASE_DOMAIN} -> 127.0.0.1:25585 (drasl) +# See nginx/ulicraft.conf.tmpl. +# +# This override: +# - publishes drasl on localhost so the host nginx can reach it +# - points the minecraft container at the host (extra_hosts) over HTTPS, so it +# fetches the pack and validates sessions through the same public names the +# LE certs cover (JVM trusts Let's Encrypt out of the box) +services: + drasl: + ports: + - "127.0.0.1:25585:25585" + + minecraft: + # Make the public names resolve to the host running nginx. + extra_hosts: + - "auth.${BASE_DOMAIN}:host-gateway" + - "pack.${BASE_DOMAIN}:host-gateway" + environment: + # Use HTTPS now that nginx terminates TLS with a publicly-trusted cert. + JVM_OPTS: "-javaagent:/extras/authlib-injector.jar=https://auth.${BASE_DOMAIN}/authlib-injector" + PACKWIZ_URL: "https://pack.${BASE_DOMAIN}/pack.toml" diff --git a/docker-compose.yml b/docker-compose.yml index 4e98e4d..eaa0da4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,45 +5,21 @@ # DNS is provided by YOUR party DNS server — see tooling/dns-records.sh for the # records to add (online vs airgap). A turnkey dnsmasq is available as an # optional layer: docker-compose.dnsmasq.yml. -# - caddy is the only HTTP ingress; holds mcnet aliases so the stack -# resolves auth.${BASE_DOMAIN} / packwiz.${BASE_DOMAIN} internally too +# - ingress is layered on, NOT in this base file: caddy (docker-compose.caddy.yml, +# LAN/air-gap, holds mcnet aliases) OR the host's nginx (docker-compose.nginx.yml +# + Let's Encrypt, production) # - drasl handles auth (password login; NO Keycloak/OIDC in this stack) -# - packwiz pack is served by caddy at packwiz.${BASE_DOMAIN} +# - packwiz pack is served by the ingress at pack.${BASE_DOMAIN} # - minecraft installs mods via PACKWIZ_URL, authlib-injector -> auth. # ────────────────────────────────────────────────────────────────── services: - # Core ingress. Static site + air-gap mirror are layered in via override - # files (docker-compose.static.yml / .mirror.yml) — see build-stack.sh --up. - caddy: - image: caddy:alpine - container_name: caddy - restart: unless-stopped - ports: - - "80: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 - - ./www:/srv/www:ro - - ./mirror/launcher:/srv/launcher:ro - - ./caddy/caddy-root-ca.crt:/srv/ca-pub/ca.crt:ro - - ./pack:/srv/pack:ro - # Custom (locally-hosted) mod jars live OUTSIDE ./pack so packwiz refresh - # doesn't index them as direct files — they're external download targets - # referenced by pack/mods/*.pw.toml. Served at pack.${BASE_DOMAIN}/custom/. - - ./custom:/srv/pack/custom:ro - networks: - mcnet: - # Only the stack's own subdomains in the base. The upstream spoof-host - # aliases live in docker-compose.mirror.yml — adding them here would - # hijack maven.neoforged.net / libraries.minecraft.net for ALL mcnet - # containers, breaking the ONLINE NeoForge install during pre-bake. - aliases: - - "auth.${BASE_DOMAIN}" - - "pack.${BASE_DOMAIN}" + # NOTE: the HTTP(S) ingress is NOT in this base file. + # - LAN / air-gap: caddy (docker-compose.caddy.yml) — added by build-stack.sh + # - production: the HOST's nginx + Let's Encrypt (docker-compose.nginx.yml + # publishes drasl to localhost; nginx/ulicraft.conf.tmpl) + # Base alone (drasl + minecraft + mc-backup) is not a complete deployment — + # always layer one ingress file on top. drasl: image: unmojang/drasl:latest container_name: drasl @@ -95,7 +71,9 @@ services: 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 ──────────────── + # ── Mod source: packwiz pack served by the ingress ────────── + # http + caddy alias by default; the nginx override switches this to + # https + host-gateway (extra_hosts) for the production path. PACKWIZ_URL: "http://pack.${BASE_DOMAIN}/pack.toml" # ── RCON for remote admin ─────────────────────────────────── @@ -108,8 +86,7 @@ services: - mc_data:/data - ./runtime:/extras:ro # authlib-injector.jar lives here depends_on: - - drasl - - caddy # packwiz pack is served by caddy + - drasl # ingress (caddy/nginx) is layered separately networks: - mcnet diff --git a/tooling/build-stack.sh b/tooling/build-stack.sh index d5a94be..7d33bb5 100755 --- a/tooling/build-stack.sh +++ b/tooling/build-stack.sh @@ -91,7 +91,8 @@ prep() { log "pre-bake server volume (TYPE=NEOFORGE install, one-shot, online)" # Bring minecraft up once so itzg installs NeoForge + mods into the volume, # then stop it. The party then runs offline against the baked volume. - "${COMPOSE[@]}" up -d minecraft + # caddy.yml so the pack (pack.${BASE_DOMAIN}) resolves + is served during bake. + "${COMPOSE[@]}" -f docker-compose.yml -f docker-compose.caddy.yml up -d minecraft warn "watch logs until 'Done' then re-run with --up; or automate a readiness probe" } @@ -102,7 +103,10 @@ prep() { # core base only (no landing, no mirror) — debugging up() { local mode="${1:-airgap}" - local -a files=(-f docker-compose.yml) + # build-stack is the caddy (LAN/air-gap) deployer; caddy lives in its own file + # now (the base stack can also run behind the host's nginx — see + # docker-compose.nginx.yml). Always include caddy here. + local -a files=(-f docker-compose.yml -f docker-compose.caddy.yml) case "$mode" in core) ;; online) files+=(-f docker-compose.static.yml) ;;