From 9cfd4163f63aeb652eeff0d716117566e0b90c63 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Wed, 29 Apr 2026 19:38:52 +0200 Subject: [PATCH] feat(nginx): bake default white-label routing config, closes #1 (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add `nginx.conf.template` with full routing: admin/CMS surfaces → `cms:5555`, uploads → `cms:5555` (cached), `/api` → `shop:9999` (no cache), `/` → `shop:9999` (cached) - Update `docker-entrypoint.sh` to render template via `envsubst` at startup; skip if adapter has already mounted its own config - Update `Dockerfile` to `COPY nginx.conf.template` into image - Bump CHANGELOG to v0.1.1 ## Test plan - [ ] Build image locally: `docker build -t libreshop/nginx:test libreshop/nginx/` - [ ] Smoke test white-label default: no env overrides → confirm rendered `/etc/nginx/nginx.conf` contains `cms:5555` and `shop:9999` - [ ] Smoke test env override: `NGINX_UPSTREAM_SHOP=shop:8080` → confirm substitution - [ ] Smoke test adapter override: mount custom `nginx.conf` via compose `configs:` → confirm "adapter-provided nginx.conf detected" log line - [ ] Deploy libreshop/demo with `nginx:v0.1.1` → confirm `/admin` returns 200 Closes #1 Co-authored-by: Michael Czechowski Reviewed-on: https://git.librete.ch/libreshop/nginx/pulls/2 --- CHANGELOG.md | 11 ++++- Dockerfile | 6 ++- docker-entrypoint.sh | 37 ++++++++++++----- nginx.conf.template | 99 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 nginx.conf.template diff --git a/CHANGELOG.md b/CHANGELOG.md index 56bcda7..f881558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,16 @@ All notable changes to libreshop/nginx are documented here. -## Unreleased +## v0.1.1 (Unreleased) +### Added +- Default white-label `nginx.conf` baked into the image. Routes the standard libreshop surface out of the box: `/admin`, `/upload`, `/content-manager`, `/content-type-builder`, `/i18n`, `/email`, `/user-permissions`, `/users-permissions`, `/documentation`, `/plugins`, `/uploads` → `${NGINX_UPSTREAM_CMS:-cms:5555}`; `/api` and `/` → `${NGINX_UPSTREAM_SHOP:-shop:9999}`. Cache zones, X-Forwarded headers, and a `/health` endpoint included. +- `docker-entrypoint.sh` envsubst pass over `nginx.conf.template` at startup with `NGINX_UPSTREAM_SHOP`, `NGINX_UPSTREAM_CMS`, `NGINX_CACHE_LIFETIME` (default `1h`). +- Adapter-override detection: if `/etc/nginx/nginx.conf` was replaced via compose `configs:` (i.e. doesn't carry the stock nginx `worker_processes auto;` line), the entrypoint leaves it untouched. + +### Why +`libreshop/demo` standalone preview returned 404 on `/admin` because the image had no default routing. Adapters like mp work fine because they mount their own `nginx.conf`, but the toolkit must be runnable without an adapter. + +## Unreleased - Extracted from `mp/nginx/` (2026-04-29). The component history before the extraction lives in the `muellerprints` repository. diff --git a/Dockerfile b/Dockerfile index e70e6ad..c413194 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ -# Libre Shop - (Reverse) Proxy -# Version 1.0.0 +# libreshop/nginx — reverse proxy fronting libreshop adapters. FROM nginx:1.26.2 +# White-label default config rendered at startup via envsubst. +# Adapters override the whole nginx.conf via compose `configs:`. +COPY nginx.conf.template /etc/nginx/nginx.conf.template COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 0d9549f..1c3a5f6 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,19 +1,36 @@ #!/usr/bin/env bash set -e -echo "Generated nginx.conf:" -echo "" +# Defaults — overridable via env in compose / docker run. +: "${NGINX_UPSTREAM_SHOP:=shop:9999}" +: "${NGINX_UPSTREAM_CMS:=cms:5555}" +: "${NGINX_CACHE_LIFETIME:=1h}" +export NGINX_UPSTREAM_SHOP NGINX_UPSTREAM_CMS NGINX_CACHE_LIFETIME + +# Render the template only when the nginx.conf in place is the +# upstream nginx-image stock default. Adapters that mount their own +# config via compose `configs:` (e.g. mp) get the file replaced +# before this script runs; we detect that by the absence of the +# stock `worker_processes auto;` line and leave that file alone. +if [ -f /etc/nginx/nginx.conf.template ] \ + && grep -q "worker_processes auto;" /etc/nginx/nginx.conf 2>/dev/null; then + envsubst '${NGINX_UPSTREAM_SHOP} ${NGINX_UPSTREAM_CMS} ${NGINX_CACHE_LIFETIME}' \ + < /etc/nginx/nginx.conf.template \ + > /etc/nginx/nginx.conf + echo "→ libreshop default nginx.conf rendered from template" +else + echo "→ adapter-provided nginx.conf detected; leaving untouched" +fi + +mkdir -p /cache/shop /cache/uploads /cache/api + +echo "Effective nginx.conf:" +echo "---" cat /etc/nginx/nginx.conf +echo "---" -mkdir -p /cache/shop -mkdir -p /cache/uploads -mkdir -p /cache/api - -echo "Starting nginx" -echo "NGINX_DEBUG=${NGINX_DEBUG}" -# Start Nginx if [ "$NGINX_DEBUG" = "true" ]; then - sed -i 's/error\.log warn/error.log debug/' /etc/nginx/nginx.conf + perl -i -pe 's|error_log /dev/stdout error|error_log /dev/stdout debug|' /etc/nginx/nginx.conf exec nginx-debug -g 'daemon off;' else exec nginx -g 'daemon off;' diff --git a/nginx.conf.template b/nginx.conf.template new file mode 100644 index 0000000..ce2ddc5 --- /dev/null +++ b/nginx.conf.template @@ -0,0 +1,99 @@ +# nginx.conf.template — libreshop white-label default. +# +# The image's docker-entrypoint.sh runs envsubst over this template and +# writes the result to /etc/nginx/nginx.conf at startup. Adapters that +# need a wholly different topology can still mount their own config via +# compose `configs:` (see mp's compose.yml for an example) — the +# template is then ignored entirely. +# +# Substituted at startup: +# ${NGINX_UPSTREAM_SHOP} default shop:9999 +# ${NGINX_UPSTREAM_CMS} default cms:5555 +# ${NGINX_CACHE_LIFETIME} default 1h +# +# All other `$variable` tokens are nginx variables, left literal. + +worker_processes 1; +events { worker_connections 1024; } + +http { + log_format info '$time_iso8601 info: $request ($msec) $status $remote_addr - $remote_user $body_bytes_sent "$http_referer" "$http_user_agent"'; + access_log /dev/stdout info; + error_log /dev/stdout error; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Cache zones live under /cache (the image creates these dirs at + # startup). 2g caps each — adapters with heavier traffic can mount + # their own config to enlarge. + proxy_cache_path /cache/shop levels=1:2 keys_zone=shop_cache:5m max_size=2g inactive=30m; + proxy_cache_path /cache/uploads levels=1:2 keys_zone=uploads_cache:5m max_size=2g inactive=30m; + + server { + listen 80 default_server; + server_name _; + + # Trust forwarded headers when fronted by another proxy + # (e.g. caddy in the netcup edge network). + real_ip_header X-Forwarded-For; + set_real_ip_from 0.0.0.0/0; + + location /health { return 200 '{"status":"ok"}'; } + + # CMS admin + management surfaces. + location ~ ^/(upload|content-manager|content-type-builder|admin|i18n|email|user-permissions|users-permissions|documentation|plugins) { + proxy_pass http://${NGINX_UPSTREAM_CMS}; + 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 $http_x_forwarded_proto; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_cache off; + proxy_buffering off; + } + + # CMS uploads (images, files). + location /uploads { + proxy_pass http://${NGINX_UPSTREAM_CMS}; + 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 $http_x_forwarded_proto; + proxy_cache uploads_cache; + proxy_cache_valid 200 ${NGINX_CACHE_LIFETIME}; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + add_header X-Cache-Status $upstream_cache_status; + } + + # Shop API (Nuxt server routes). + location /api { + proxy_pass http://${NGINX_UPSTREAM_SHOP}; + 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 $http_x_forwarded_proto; + proxy_cache off; + proxy_buffering off; + } + + # Shop frontend (catch-all). + location / { + proxy_pass http://${NGINX_UPSTREAM_SHOP}; + 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 $http_x_forwarded_proto; + proxy_cache shop_cache; + proxy_cache_valid 200 10m; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + add_header X-Cache-Status $upstream_cache_status; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + } +}