ng.conf.template
Rama main · réplica de lectura del repositorio original.
# Mirrors the "%(asctime)s [%(name)s] %(levelname)s %(message)s" format used
# by the Connect backend's Python loggers, so nginx and backend log lines
# look consistent in `docker logs`.
log_format connect '$time_iso8601 [nginx] INFO $remote_addr - "$request" $status $body_bytes_sent';
# Party guests may arrive through a TLS-terminating proxy in front of this
# one; keep the scheme it reports so connect can mark the guest cookie
# Secure. Without such a proxy, this nginx's own scheme is the answer.
map $http_x_forwarded_proto $party_forwarded_proto {
default $http_x_forwarded_proto;
"" $scheme;
}
server {
listen ${WEB_PORT};
listen [::]:${WEB_PORT};
sendfile on;
default_type application/octet-stream;
# Gated behind DEBUG=true (see start.sh) — access logs are noisy and
# usually not needed outside of troubleshooting.
${NGINX_ACCESS_LOG}
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html =404;
}
# index.html isn't content-hashed, so nginx sends no Cache-Control for it
# by default and the browser falls back to its own heuristic — meaning a
# stale copy can outlive a redeploy for a while, referencing asset hashes
# that no longer exist. A normal reload only re-fetches it if the browser
# bothers to revalidate — force that every time (still cheap: a 304 when
# unchanged).
location = /index.html {
add_header Cache-Control "no-cache";
root /usr/share/nginx/html;
}
location = /settings.js {
alias /etc/nginx/conf.d/settings.js;
add_header Cache-Control "no-store";
}
# Everything the connect backend exposes except the three proxy-shaped
# routes below (see connect/main.py's router list) — /config, /health,
# /play, /discover, /volume, /join, /claim, /pairing/*, /lyrics/*, /events,
# /status, ... all fall through to this one location. The trailing slash
# on both the location and proxy_pass URL is what makes nginx strip the
# "/api/" prefix down to the backend root (see connect/routes/proxy.py's
# own "nginx strips /api/" comment, which assumes exactly this).
location /api/ {
proxy_pass http://127.0.0.1:${PORT}/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Connect-Token "${CONNECT_TOKEN}";
# /events and /visualizer are long-lived SSE streams (see
# routes/stream.py, routes/events.py) — without proxy_http_version 1.1,
# nginx defaults to HTTP/1.0 towards the backend, which has no chunked
# transfer encoding, so for a response with no known Content-Length
# (a live stream) nginx ends up batching it into internal buffers
# instead of forwarding each chunk as it arrives. The backend already
# sends X-Accel-Buffering: no for exactly this, but that alone wasn't
# reliably disabling it in practice — this is the actual fix, matching
# /rest/'s proxy_buffering off below.
proxy_http_version 1.1;
proxy_buffering off;
}
# Subsonic proxy — matches connect/routes/proxy.py's /rest/{path} route
# directly (unprefixed), same as Electron's direct connection would hit.
location /rest/ {
proxy_pass http://127.0.0.1:${PORT}/rest/;
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-Connect-Token "${CONNECT_TOKEN}";
proxy_buffering off;
proxy_read_timeout 300s;
}
location /auth/ {
proxy_pass http://127.0.0.1:${PORT}/auth/;
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-Connect-Token "${CONNECT_TOKEN}";
}
# Party mode's guest page and API (connect/routes/party.py). Its own
# prefix so a reverse proxy can let exactly this past its login, and the
# one location here that never injects X-Connect-Token: everything under
# it authenticates with the guest's own cookie instead.
location = /party {
proxy_pass http://127.0.0.1:${PORT}/party;
proxy_set_header Host $host;
}
location /party/ {
proxy_pass http://127.0.0.1:${PORT}/party/;
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 $party_forwarded_proto;
# The token injected for /api/ must not ride along on a request that
# carries one of its own by accident - cleared rather than left out.
proxy_set_header X-Connect-Token "";
# Same SSE reasoning as /api/ above (/party/api/events).
proxy_http_version 1.1;
proxy_buffering off;
}
location /stream {
proxy_pass http://127.0.0.1:${PORT}/stream;
proxy_set_header X-Connect-Token "${CONNECT_TOKEN}";
# Same reasoning as /api/'s identical two lines above: without
# proxy_http_version 1.1, nginx speaks HTTP/1.0 to the backend, which
# has no chunked transfer encoding, so a response with no known
# Content-Length (this live ffmpeg-fed audio stream) gets buffered
# internally instead of forwarded chunk-by-chunk as it arrives — the
# backend's own X-Accel-Buffering: no header alone isn't reliably
# enough to prevent that. Matters even more here than for /api/: a cast
# device pulling this in real time needs the data flowing continuously,
# not held back by nginx and then released in an unpredictable burst.
proxy_http_version 1.1;
proxy_buffering off;
}
}