Files
memby/admin-ui/nginx.conf
T

76 lines
3.5 KiB
Nginx Configuration File
Raw Normal View History

2026-08-14 09:40:03 +12:00
# The console's own server. It sees only requests the gateway has already authenticated
# and forwarded, so there is no auth here — and there must not be, or there would be two
# places deciding who may read the console.
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
# The gateway proxies /admin/* through untouched, so the paths arriving here carry that
# prefix. Vite is built with base=/admin/, so the asset URLs in index.html match.
location /admin/assets/ {
alias /usr/share/nginx/html/assets/;
# Hashed filenames: a changed file is a changed name, so this can be cached hard
# and a deployment is picked up the moment the shell is re-fetched.
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Canonicalise the console root even though the gateway normally performs this
# redirect before proxying. It keeps the static container correct if it is ever
# reached directly during diagnosis or through a future internal proxy change.
location = /admin {
return 308 /admin/;
}
# Every other /admin path is a console route — /admin/logins, /admin/devices/tv-1, a
# deep link, a refresh, the Back button — and none of them exist as files. They all
# answer with the shell, which is what makes client-side routing work on a reload.
location /admin {
try_files $uri @console_shell;
# The shell must never be cached: it carries the asset hashes, so a stale copy
# points at JavaScript a deployment has already replaced. The gateway sets this too
# on the way past; it is stated in both places because either alone would be enough
# to get wrong.
add_header Cache-Control "no-store" always;
}
# `try_files ... /index.html` would internally re-match the fallback against the
# catch-all `location /` below, which deliberately returns 404. A named location
# retains this server's static root and serves the shell without exposing /index.html
# as a second public route.
location @console_shell {
try_files /index.html =404;
add_header Cache-Control "no-store" always;
}
# A liveness probe for docker-compose. Deliberately outside /admin so it cannot be
# confused with a console route, and it answers without touching the filesystem.
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
# Nothing else is served. The console is the only thing in this image.
location / {
return 404;
}
# The console fetches nothing from anywhere: no CDN, no fonts, no analytics. Saying so
# in a policy means a dependency that tried to would fail loudly during development
# rather than quietly phone home from an operator's browser.
#
# 'unsafe-inline' for styles is Vite's injected critical CSS; connect-src 'self' covers
# the API and the notification stream, both of which are same-origin by construction.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
}