# nginx vhost template for LLeMbas.
#
# install.sh substitutes __SITE_HOST__ and __APP_PORT__ and writes the result to
# /etc/nginx/conf.d/<host>.conf. Edit this file, not the installed copy.
#
# Assumes a self-signed certificate at /etc/nginx/ssl/<host>.{crt,key}, which
# install.sh generates. To use a real certificate, point ssl_certificate at it;
# nothing else here needs to change.

# The terminal panel is a WebSocket, and a proxy that does not pass an upgrade
# through breaks it with no error either side can report -- the browser sees a
# failed handshake, which carries no status and no reason. This map yields
# "upgrade" only when the client asked for one and the empty string otherwise,
# which is exactly what the streamed-reply case below needs, so one `location`
# serves both. `conf.d/*.conf` is included inside `http {}`, where `map` is
# legal; the name is prefixed because two vhosts from this template would
# otherwise collide.
map $http_upgrade $lembas_connection_upgrade {
    default upgrade;
    ''      '';
}

server {
    listen 80;
    listen [::]:80;
    server_name __SITE_HOST__;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name __SITE_HOST__;

    ssl_certificate     /etc/nginx/ssl/__SITE_HOST__.crt;
    ssl_certificate_key /etc/nginx/ssl/__SITE_HOST__.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

    # File uploads land here once that feature exists; 0 = no limit.
    client_max_body_size 0;

    location / {
        proxy_pass http://127.0.0.1:__APP_PORT__;
        proxy_http_version 1.1;
        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;

        # Streamed replies are server-sent events. Every one of these matters:
        # with buffering on (the default) nginx holds the whole reply and
        # delivers it in one lump at the end, which is indistinguishable from
        # streaming being broken.
        proxy_buffering         off;
        proxy_request_buffering off;
        proxy_cache             off;
        # SSE is plain HTTP/1.1 chunked and needs Connection left empty; the
        # terminal is a real upgrade and needs it set. The map at the top of
        # this file is what lets one location do both -- a hard-coded
        # `Connection ""` here, which is what was here before, works for every
        # streamed reply and silently breaks every terminal.
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $lembas_connection_upgrade;

        # A model can think for minutes before the first token. The default
        # 60s read timeout would cut long generations off mid-sentence.
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    location /static/ {
        proxy_pass http://127.0.0.1:__APP_PORT__;
        proxy_set_header Host $host;
        expires 1h;
        add_header Cache-Control "public";
    }

    # The service worker must never be cached. A stale worker keeps serving a
    # stale cache to every tab, and there is no way to tell it to stop. The
    # application already sends no-store; this stops the proxy overriding it.
    # /manifest.webmanifest needs nothing special and comes through location /.
    location = /sw.js {
        proxy_pass http://127.0.0.1:__APP_PORT__;
        proxy_set_header Host $host;
        add_header Cache-Control "no-store";
    }
}
