From ab3d2c201bb68478942d83f8ef8fc1fc1b126cfa Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Tue, 9 Jun 2026 00:30:27 +0200 Subject: [PATCH] feat(ingress): public distribution.${BASE_DOMAIN} static vhost Serve a static site at distribution.${BASE_DOMAIN} whose web root lives in another repo (DISTRIBUTION_WEB_ROOT, bind-mounted read-only into caddy): - caddy/conf.d/30-distribution.caddy file_server vhost - docker-compose.distribution.yml mount snippet + external web root - issue-letsencrypt.sh / .env.example add distribution to LE_SUBDOMAINS - ulicraft-caddy.conf.tmpl TLS-front block proxying to caddy Co-Authored-By: Claude Opus 4.8 (1M context) --- .env.example | 11 ++++++++--- caddy/conf.d/30-distribution.caddy | 8 ++++++++ docker-compose.distribution.yml | 15 +++++++++++++++ nginx/ulicraft-caddy.conf.tmpl | 20 +++++++++++++++++++- tooling/issue-letsencrypt.sh | 4 ++-- 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 caddy/conf.d/30-distribution.caddy create mode 100644 docker-compose.distribution.yml diff --git a/.env.example b/.env.example index 9392110..9d9469a 100644 --- a/.env.example +++ b/.env.example @@ -23,8 +23,13 @@ RCON_PASSWORD=change-me-to-something-random # CADDY_HTTP_PORT=8880 # CADDY_HTTP_BIND=127.0.0.1 # Then render nginx/ulicraft-caddy.conf.tmpl with CADDY_HTTP_PORT. -# CADDY_HTTP_PORT=80 -# CADDY_HTTP_BIND=0.0.0.0 +CADDY_HTTP_PORT=80 +CADDY_HTTP_BIND=0.0.0.0 + +# distribution.${BASE_DOMAIN} static vhost (docker-compose.distribution.yml). +# Absolute host path to the built static site — it lives in ANOTHER repo. +# Bind-mounted read-only into caddy and served at distribution.${BASE_DOMAIN}. +DISTRIBUTION_WEB_ROOT=/home/oier/projects/mc-mods/ulicraft-group/ulicraft-distribution/dist # Only needed if using CurseForge-exclusive mods: # CF_API_KEY=your-curseforge-api-key @@ -32,7 +37,7 @@ RCON_PASSWORD=change-me-to-something-random # Only needed to issue real TLS certs. DNS-01 needs no inbound ports. LE_EMAIL=you@example.com # space-separated subdomains; apex ${BASE_DOMAIN} is always included -LE_SUBDOMAINS=auth pack +LE_SUBDOMAINS=auth pack distribution # OVH API creds (DNS zone write). Create at https://eu.api.ovh.com/createToken/ # OVH_CK can be blank on first run — acme.sh prints an auth URL, then paste it. OVH_END_POINT=ovh-eu diff --git a/caddy/conf.d/30-distribution.caddy b/caddy/conf.d/30-distribution.caddy new file mode 100644 index 0000000..a17b359 --- /dev/null +++ b/caddy/conf.d/30-distribution.caddy @@ -0,0 +1,8 @@ +# Distribution toggle — static site served from a web root in ANOTHER repo. +# The host path is set by DISTRIBUTION_WEB_ROOT in .env and bind-mounted to +# /srv/distribution by docker-compose.caddy.yml. Mounted only when that var is +# set (see the distribution override). +http://distribution.{$BASE_DOMAIN} { + root * /srv/distribution + file_server browse +} diff --git a/docker-compose.distribution.yml b/docker-compose.distribution.yml new file mode 100644 index 0000000..b1fb5cc --- /dev/null +++ b/docker-compose.distribution.yml @@ -0,0 +1,15 @@ +# ────────────────────────────────────────────────────────────────── +# Distribution vhost — distribution.${BASE_DOMAIN}, static files from a web +# root that lives in ANOTHER repo. Layered on top of the caddy ingress. +# ────────────────────────────────────────────────────────────────── +# docker compose -f docker-compose.yml -f docker-compose.caddy.yml \ +# -f docker-compose.distribution.yml up -d +# +# DISTRIBUTION_WEB_ROOT (.env) = absolute host path to the built static site in +# the other repo. Bind-mounted read-only; caddy serves it via the +# conf.d/30-distribution.caddy snippet. +services: + caddy: + volumes: + - ./caddy/conf.d/30-distribution.caddy:/etc/caddy/conf.d/30-distribution.caddy:ro + - ${DISTRIBUTION_WEB_ROOT:?DISTRIBUTION_WEB_ROOT unset in .env}:/srv/distribution:ro diff --git a/nginx/ulicraft-caddy.conf.tmpl b/nginx/ulicraft-caddy.conf.tmpl index 06c334a..731db89 100644 --- a/nginx/ulicraft-caddy.conf.tmpl +++ b/nginx/ulicraft-caddy.conf.tmpl @@ -32,7 +32,7 @@ upstream ulicraft_caddy { server { listen 80; listen [::]:80; - server_name ${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN}; + server_name ${BASE_DOMAIN} auth.${BASE_DOMAIN} pack.${BASE_DOMAIN} distribution.${BASE_DOMAIN}; return 301 https://$host$request_uri; } @@ -96,3 +96,21 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } } + +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name distribution.${BASE_DOMAIN}; + + ssl_certificate ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/cert.pem; + ssl_certificate_key ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/key.pem; + + location / { + proxy_pass http://ulicraft_caddy; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/tooling/issue-letsencrypt.sh b/tooling/issue-letsencrypt.sh index 80a002d..95d745d 100755 --- a/tooling/issue-letsencrypt.sh +++ b/tooling/issue-letsencrypt.sh @@ -17,7 +17,7 @@ # OVH_CK OVH consumer key (obtained on first run — see below) # OVH_END_POINT OVH API endpoint (e.g. ovh-eu) # optional: -# LE_SUBDOMAINS space-separated (default: "auth pack") +# LE_SUBDOMAINS space-separated (default: "auth pack distribution") # LE_STAGING=1 use the LE staging CA (untrusted, for dry runs) # # First-time OVH consumer key: set OVH_AK/OVH_AS/OVH_END_POINT, leave OVH_CK @@ -53,7 +53,7 @@ ACME="${ACME_SH:-$HOME/.acme.sh/acme.sh}" then re-run (or set ACME_SH=/path/to/acme.sh)." CERT_DIR="${CERT_DIR:-certs}" -read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack}" +read -r -a SUBDOMAINS <<< "${LE_SUBDOMAINS:-auth pack distribution}" if [ "${LE_STAGING:-0}" = "1" ]; then SERVER="letsencrypt_test"; warn "STAGING mode — certs will NOT be trusted by browsers"