#!/bin/sh
# Generates the nginx server config at start-up, then runs nginx in the
# foreground.
#
# WHY THIS EXISTS. The upstream used to be written as a literal hostname:
#
#     location /api { proxy_pass http://backend:8080; }
#
# nginx resolves a literal host name ONCE, at start-up, and caches the address
# for the life of the process. The backend container is recreated every time
# the stack restarts or the dev loop rebuilds, and it comes back with a NEW IP.
# From that moment every /api and /uploads request went to the address the
# backend used to have and returned:
#
#     502 Bad Gateway
#
# while the backend itself was perfectly healthy — reachable on its published
# port, answering /health 200, and even reachable from inside this container by
# hostname from a shell. Only nginx was holding the stale address. The symptom
# read as "the API is down", and the fix was a manual `nginx -s reload`, which
# is not something anyone should have to know.
#
# This hit erp-km and pos-km at the same time and self-resolved whenever either
# container happened to be restarted, which is exactly why it looked
# intermittent and unfixable.
#
# THE FIX. proxy_pass re-resolves per request only when the host part is a
# VARIABLE, and that requires a `resolver` directive. The resolver address
# cannot be baked into the image: /etc/resolv.conf does not exist at build time,
# and podman derives the nameserver from the network's subnet, so the value
# changes if the network is ever recreated. So it is read here, at start-up,
# from the very resolv.conf the container was handed.
set -eu

CONFIG=/etc/nginx/conf.d/default.conf

if [ ! -f /etc/resolv.conf ]; then
  echo "entrypoint: /etc/resolv.conf is missing; cannot determine the DNS server" >&2
  exit 1
fi

# First nameserver only. Podman puts aardvark-dns there; a multi-nameserver
# resolv.conf is handled by taking the first, which is the one guaranteed to be
# on this network.
DNS=$(awk '$1 == "nameserver" { print $2; exit }' /etc/resolv.conf)
if [ -z "${DNS}" ]; then
  echo "entrypoint: no nameserver line in /etc/resolv.conf" >&2
  exit 1
fi

echo "entrypoint: proxying /api and /uploads to backend:8080 via resolver ${DNS}"

# Unquoted heredoc so ${DNS} expands. Every nginx variable is escaped: $api,
# $request_uri, $uri, $host, $remote_addr and $proxy_add_x_forwarded_for belong
# to nginx, not to this shell.
cat > "${CONFIG}" <<EOF
server {
  listen 8080;
  port_in_redirect off;
  absolute_redirect off;
  root /usr/share/nginx/html;

  # Re-resolve the backend name for this request. Valid=10s is deliberately
  # shorter than any TTL involved: a recreated backend is picked up in about ten
  # seconds instead of needing a reload.
  resolver ${DNS} valid=10s ipv6=off;
  # A literal "backend:8080" here would be resolved once at start-up and cached
  # forever, which is the bug this file exists to remove.
  set \$api backend:8080;

  # Never cache the SPA shell: a stale index.html keeps old asset hashes and the
  # Cloudflare Web Analytics beacon integrity that caused
  # 'sha512 ... beacon.min.js ... integrity mismatch' + CORS failure.
  # Versioned /assets/* are immutable and can be cached long.
  location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate" always; add_header X-Content-Type-Options "nosniff" always; try_files \$uri =404; }
  location /assets/ { expires 1y; add_header Cache-Control "public, immutable" always; add_header X-Content-Type-Options "nosniff" always; }
  location / { try_files \$uri \$uri/ /index.html; add_header Cache-Control "no-cache" always; }

  # \$request_uri is the original request line, query string included. Passing
  # it explicitly is required once proxy_pass uses a variable: without a URI
  # part nginx can no longer infer what to append, and /api/v1/... would
  # arrive at the backend as /.
  location /api { proxy_pass http://\$api\$request_uri; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; }
  location /uploads { proxy_pass http://\$api\$request_uri; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; }
}
EOF

# Fail loudly here rather than serving a config nginx will reject: a broken
# start-up otherwise surfaces as an empty 502 from every route, which is the
# same symptom the bug above produced and would hide a second failure.
nginx -t

exec nginx -g 'daemon off;'
