# CoderAI single-port reverse proxy (in-container).
# Fronts the main server and the bundled tool web UIs on one published port.
# nginx runs in the foreground under supervisord (daemon off).

# No `user` directive: when the container runs as root, the master stays root and
# spawns workers as nobody; when run with `--user UID`, nginx runs entirely as that
# UID. All writable state below lives under /tmp so non-root runs work unchanged.
worker_processes  auto;
daemon off;
pid /tmp/nginx.pid;
# crit only: nginx's startup/worker [notice] lines and per-request info would
# otherwise stream to the container console (supervisord pipes nginx stderr to
# /dev/fd/1), drowning out coderai's own output on attach / `docker logs`. Real
# failures (crit/alert/emerg) still surface.
error_log /dev/stderr crit;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    server_tokens off;
    # Off: per-request access lines would stream to the container console and
    # bury coderai's output on attach. coderai logs its own requests already.
    access_log off;

    # Writable temp paths under /tmp so the listed user (root or --user UID) can
    # always create them; the defaults under /var/lib/nginx are root-only.
    client_body_temp_path /tmp/nginx-client-body;
    proxy_temp_path       /tmp/nginx-proxy;
    fastcgi_temp_path     /tmp/nginx-fastcgi;
    uwsgi_temp_path       /tmp/nginx-uwsgi;
    scgi_temp_path        /tmp/nginx-scgi;

    # AI workloads: large uploads (images/audio/video) and long generations.
    client_max_body_size 4096m;
    proxy_read_timeout   3600s;
    proxy_send_timeout   3600s;
    proxy_connect_timeout 75s;

    # --- Chain-aware forwarded headers (works behind an OUTER proxy too) ------
    # When this container sits behind another reverse proxy (a "double proxy"),
    # the public scheme/host/sub-path are whatever the OUTER proxy already
    # advertised. If we blindly set these to our own hop's values ($scheme is
    # http on the outer->inner leg, $host is the LAN IP, prefix is just ours),
    # CoderAI (codai/api/urlutils.py) and the bundled tools build public URLs
    # against the wrong origin and images/links 404. So: prefer an incoming
    # X-Forwarded-* value, fall back to our own only when the outer proxy didn't
    # send one.
    map $http_x_forwarded_proto $fwd_proto {
        default $scheme;
        "~."    $http_x_forwarded_proto;
    }
    map $http_x_forwarded_host $fwd_host {
        default $host;
        "~."    $http_x_forwarded_host;
    }
    # Incoming sub-path prefix from the outer proxy (trailing slash stripped).
    # Empty when there is no outer prefix.
    map $http_x_forwarded_prefix $fwd_prefix {
        "~^(?<p>.*?)/?$" $p;
    }

    # Per-app public prefix. Two valid outer-proxy topologies must both work:
    #   (A) outer wraps the WHOLE container under one prefix and proxies to our
    #       root: outer sends e.g. "/ai" -> the app's public prefix is
    #       "/ai/<app>", so we APPEND.
    #   (B) outer mounts the app at the SAME name and proxies to our "/<app>/"
    #       path: outer sends e.g. "/township" (or "/ai/township") that ALREADY
    #       ends in our app name -> use it AS-IS, never append (appending would
    #       emit "/township/township" and every fetch() would 404).
    # Rule: if the incoming prefix already ends in "/<app>", pass it through;
    # otherwise append. Empty incoming -> just "/<app>" (direct, no outer proxy).
    map $fwd_prefix $xfp_editor {
        "~/editor$"   $fwd_prefix;
        default       $fwd_prefix/editor;
    }
    map $fwd_prefix $xfp_videogen {
        "~/videogen$" $fwd_prefix;
        default       $fwd_prefix/videogen;
    }
    map $fwd_prefix $xfp_township {
        "~/township$" $fwd_prefix;
        default       $fwd_prefix/township;
    }

    # Shared proxy headers. CoderAI builds public URLs from these
    # (codai/api/urlutils.py); the tools honour X-Forwarded-Prefix for sub-paths.
    proxy_http_version 1.1;
    proxy_set_header Host              $fwd_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 $fwd_proto;
    proxy_set_header X-Forwarded-Host  $fwd_host;
    # Pass the outer prefix through unchanged at the root (trailing slash
    # stripped); sub-apps override this with their own value below.
    proxy_set_header X-Forwarded-Prefix $fwd_prefix;

    upstream coderai   { server 127.0.0.1:18776; }
    upstream editor    { server 127.0.0.1:8420;  }
    upstream videogen  { server 127.0.0.1:7790;  }
    upstream township  { server 127.0.0.1:7788;  }

    server {
        listen 8776 default_server;
        listen [::]:8776 default_server;
        server_name _;

        # --- Video editor: https://host:8776/editor/ -------------------------
        location /editor/ {
            proxy_pass http://editor/;            # trailing slash strips the prefix
            proxy_set_header X-Forwarded-Prefix $xfp_editor;    # smart nest (see map above)
            proxy_request_buffering off;          # stream large uploads through
            proxy_buffering off;                  # SSE progress
        }

        # --- Videogen studio: https://host:8776/videogen/ -------------------
        location /videogen/ {
            proxy_pass http://videogen/;
            proxy_set_header X-Forwarded-Prefix $xfp_videogen;  # smart nest (see map above)
            proxy_request_buffering off;
            proxy_buffering off;
        }

        # --- Township fighters: https://host:8776/township/ ----------------
        location /township/ {
            proxy_pass http://township/;
            proxy_set_header X-Forwarded-Prefix $xfp_township;  # smart nest (see map above)
            proxy_request_buffering off;
            proxy_buffering off;
        }

        # --- CoderAI server + OpenAI API at the root ------------------------
        location / {
            proxy_pass http://coderai;
            proxy_buffering off;                  # SSE: chat stream + task progress
        }
    }
}
