# Edge reverse proxy — single entry point (replaces Caddy).
# Optional upstreams use Docker DNS resolver so nginx starts even if a module is disabled.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# Reflect browser Origin for cross-origin API calls (web app on :3004, API on :80).
map $http_origin $cors_allow_origin {
    default $http_origin;
    ''      '*';
}

server {
    listen 80;
    server_name ${DOMAIN};

    client_max_body_size 10G;

    # --- ultid /api/v1/* (before OpenWebUI catch-alls; ^~ beats prefix /api/) ---
    location ^~ /api/v1/mail/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/migration/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/admin/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/drive/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/office/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/richtext/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/ultidraw/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/ai/mcp {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Mcp-Session-Id, Origin, X-Requested-With, X-Trace-Id, X-Ulti-Token, X-OpenWebUI-User-Email, X-OpenWebUI-User-Id, X-OpenWebUI-User-Name, X-OpenWebUI-User-Role" always;
        add_header Access-Control-Expose-Headers "Mcp-Session-Id, X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Cookie $http_cookie;
        proxy_set_header Authorization $http_authorization;
        proxy_set_header X-Ulti-Token $http_x_ulti_token;
        proxy_set_header X-OpenWebUI-User-Email $http_x_openwebui_user_email;
        proxy_set_header X-OpenWebUI-User-Id $http_x_openwebui_user_id;
        proxy_set_header X-OpenWebUI-User-Name $http_x_openwebui_user_name;
        proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role;
        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 ${FORWARDED_PROTO};
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
    location ^~ /api/v1/ai/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Mcp-Session-Id, Origin, X-Requested-With, X-Trace-Id, X-Ulti-Token, X-OpenWebUI-User-Email, X-OpenWebUI-User-Id, X-OpenWebUI-User-Name, X-OpenWebUI-User-Role" always;
        add_header Access-Control-Expose-Headers "Mcp-Session-Id, X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Cookie $http_cookie;
        proxy_set_header Authorization $http_authorization;
        proxy_set_header X-Ulti-Token $http_x_ulti_token;
        proxy_set_header X-OpenWebUI-User-Email $http_x_openwebui_user_email;
        proxy_set_header X-OpenWebUI-User-Id $http_x_openwebui_user_id;
        proxy_set_header X-OpenWebUI-User-Name $http_x_openwebui_user_name;
        proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role;
        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 ${FORWARDED_PROTO};
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
    location ^~ /api/v1/users/me {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/calendar/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/contacts/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/meet/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/photos/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /api/v1/search {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }

    # OpenWebUI trusted-header signin — ultid injects X-Ulti-User-* (auth_request + POST body deadlocks)
    location = /api/v1/auths/signin {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;
        proxy_pass http://$ultid_upstream/api/v1/ai/embed-signin;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Cookie $http_cookie;
        proxy_set_header Authorization $http_authorization;
        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 ${FORWARDED_PROTO};
    }

    # --- OpenWebUI API at site root (prod SPA: WEBUI_BASE_URL="" → fetch("/api/…")) ---
    # No auth_request here: POST + auth_request deadlocks (embed-auth timeout → 500).
    # Ultimail gate is /ai/ HTML; OpenWebUI API auth uses its own JWT (signin via embed-signin).
    location ~ ^/api/(config|changelog|version|webhook|usage|models|embeddings|message|chat|tasks)(/|$) {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;
        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Accept-Encoding "";
        sub_filter_once off;
        sub_filter_types application/json text/plain;
        sub_filter 'UltiAI (Open WebUI)' 'UltiAI';
        sub_filter 'Open WebUI' 'UltiAI';
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
    location ~ ^/api/v1/(auths|channels|chats|notes|models|knowledge|prompts|tools|skills|memories|folders|groups|files|functions|evaluations|analytics|utils|terminals|automations|calendars|scim|pipelines|tasks|images|audio|retrieval|configs|users|chat|embeddings|messages)(/|$) {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;
        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # ultid API fallback (must stay after ^~ /api/auth/ — mail OIDC routes)
    location /api/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;

        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;

        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;

        proxy_pass http://$ultid_upstream;
        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 ${FORWARDED_PROTO};
    }

    # OpenWebUI socket.io (ultid uses /ws without /socket.io)
    location ^~ /ws/socket.io {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;
        proxy_pass http://$openwebui_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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 ${FORWARDED_PROTO};
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # OpenWebUI socket.io under /ai prefix (SvelteKit base /ai — avoid embed-auth on /ai/)
    location ^~ /ai/ws/socket.io {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;
        rewrite ^/ai/ws/socket.io(.*)$ /ws/socket.io$1 break;
        proxy_pass http://$openwebui_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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 ${FORWARDED_PROTO};
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    location /ws {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_ws_upstream ultid:8080;

        proxy_hide_header Access-Control-Allow-Origin;

        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Vary Origin always;

        proxy_pass http://$ultid_ws_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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 ${FORWARDED_PROTO};
    }

    # OpenWebUI Ollama/OpenAI proxy mounts (SPA uses /ollama, /openai at root)
    location ^~ /ollama/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;
        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
    }
    location ^~ /openai/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;
        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
    }

    # TipTap / Hocuspocus — proxy WS without redirect (301 breaks upgrade)
    location /collab {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $hocuspocus_upstream host.docker.internal:1234;

        rewrite ^/collab/?(.*)$ /$1 break;
        proxy_pass http://$hocuspocus_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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 ${FORWARDED_PROTO};
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # Ultimail OIDC post-login — before Authentik /auth/ (path collision)
    location ^~ /auth/complete {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location /auth/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $authentik_upstream authentik-server:9000;
        proxy_pass http://$authentik_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 86400;
        proxy_hide_header X-Frame-Options;
        proxy_hide_header Content-Security-Policy;
        # Permet l’embed du portail Authentik dans la suite (même host + dev Next :3004).
        add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:3004 http://127.0.0.1:3004" always;
    }

    location /meet/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $jitsi_upstream jitsi-web;
        rewrite ^/meet/(.*)$ /$1 break;
        proxy_pass http://$jitsi_upstream;
        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 ${FORWARDED_PROTO};
    }

    # Public Nextcloud share links → UltiDrive viewer
    location ~ ^/cloud/index\.php/s/([^/]+)/?(.*)$ {
        return 301 /drive/s/$1$is_args$args;
    }

    location /cloud/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $nc_upstream nextcloud;
        rewrite ^/cloud/(.*)$ /$1 break;
        proxy_pass http://$nc_upstream;
        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 ${FORWARDED_PROTO};
    }

    location = /cloud {
        return 301 /cloud/;
    }

    # OnlyOffice fetches Nextcloud after StorageUrl host rewrite (URLs lack /cloud prefix).
    location ^~ /index.php {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $nc_upstream nextcloud;
        proxy_pass http://$nc_upstream;
        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 ${FORWARDED_PROTO};
        proxy_read_timeout 300s;
    }

    # OpenWebUI — same-origin proxy with trusted-header SSO
    location = /api/v1/ai/embed-auth {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;
        proxy_pass http://$ultid_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Cookie $http_cookie;
        proxy_set_header Authorization $http_authorization;
        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 ${FORWARDED_PROTO};
    }

    location /ai/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;

        auth_request /api/v1/ai/embed-auth;
        auth_request_set $ulti_user_email $upstream_http_x_ulti_user_email;
        auth_request_set $ulti_user_name $upstream_http_x_ulti_user_name;
        auth_request_set $ulti_user_role $upstream_http_x_ulti_user_role;

        proxy_hide_header X-Frame-Options;
        add_header Content-Security-Policy "frame-ancestors 'self'" always;

        rewrite ^/ai/?(.*)$ /$1 break;
        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header X-Forwarded-Prefix /ai;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Ulti-User-Email $ulti_user_email;
        proxy_set_header X-Ulti-User-Name $ulti_user_name;
        proxy_set_header X-Ulti-User-Role $ulti_user_role;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;

        # OpenWebUI serves root-relative /static, /_app, /api — prefix for subpath /ai/
        proxy_set_header Accept-Encoding "";
        sub_filter_once off;
        sub_filter_types text/html text/css application/javascript application/json text/javascript;
        sub_filter 'href="/static/' 'href="/ai/static/';
        sub_filter 'src="/static/' 'src="/ai/static/';
        sub_filter 'href="/_app/' 'href="/ai/_app/';
        sub_filter 'src="/_app/' 'src="/ai/_app/';
        sub_filter '"/_app/' '"/ai/_app/';
        sub_filter "'/_app/" "'/ai/_app/";
        sub_filter '"/static/' '"/ai/static/';
        sub_filter "'/static/" "'/ai/static/";
        sub_filter '"/api/' '"/ai/api/';
        sub_filter "'/api/" "'/ai/api/";
        sub_filter 'href="/manifest.json' 'href="/ai/manifest.json';
        sub_filter '"/manifest.json' '"/ai/manifest.json';
        # SvelteKit base path — without this, /ai/ routes 404 (base "" expects site root)
        sub_filter 'base: ""' 'base: "/ai"';
        sub_filter "base: ''" 'base: "/ai"';
        # Keep t="" so socket.io uses default namespace (/), not /ai — see openwebui-subfilters.conf
        include /etc/nginx/openwebui-subfilters.conf;
        sub_filter 'location.href="/auth"' 'location.href="/ai/auth"';
        sub_filter "location.href='/auth'" "location.href='/ai/auth'";
        sub_filter '"/auth?"' '"/ai/auth?"';
        sub_filter '"/auth"' '"/ai/auth"';
        sub_filter '<script src="/static/loader.js"' '<script src="/ai/static/ulti-session.js"></script><script src="/ai/static/loader.js"';
        # In-app links that escape to site root (e.g. "Nouvelle conversation" → /)
        sub_filter 'href="/"' 'href="/ai/"';
        sub_filter "href='/'" "href='/ai/'";
        sub_filter 'href="/notes' 'href="/ai/notes';
        sub_filter 'href="/workspace' 'href="/ai/workspace';
        sub_filter 'href="/home' 'href="/ai/home';
        sub_filter 'href="/c/' 'href="/ai/c/';
        sub_filter 'href="/automations' 'href="/ai/automations';
        sub_filter 'href="/calendar' 'href="/ai/calendar';
        sub_filter 'href="/channels' 'href="/ai/channels';
        sub_filter 'href="/playground' 'href="/ai/playground';
        # UltiAI branding (WEBUI_NAME env still appends " (Open WebUI)" in backend)
        sub_filter 'UltiAI (Open WebUI)' 'UltiAI';
        sub_filter 'Open WebUI' 'UltiAI';
    }

    location = /ai {
        return 301 /ai/;
    }

    # OpenWebUI trusted-header signin under /ai prefix (iframe loads /ai/api/v1/auths/signin).
    location = /ai/api/v1/auths/signin {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $ultid_upstream ultid:8080;

        auth_request /api/v1/ai/embed-auth;
        auth_request_set $ulti_user_email $upstream_http_x_ulti_user_email;
        auth_request_set $ulti_user_name $upstream_http_x_ulti_user_name;
        auth_request_set $ulti_user_role $upstream_http_x_ulti_user_role;

        proxy_hide_header Access-Control-Allow-Origin;
        proxy_hide_header Access-Control-Allow-Methods;
        proxy_hide_header Access-Control-Allow-Headers;
        proxy_hide_header Access-Control-Expose-Headers;
        proxy_hide_header Access-Control-Max-Age;
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Vary;
        add_header Access-Control-Allow-Origin $cors_allow_origin always;
        add_header Access-Control-Allow-Methods "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Accept, Authorization, Content-Type, Idempotency-Key, Origin, X-Requested-With, X-Trace-Id" always;
        add_header Access-Control-Expose-Headers "X-Trace-Id" always;
        add_header Access-Control-Max-Age 300 always;
        add_header Vary Origin always;

        proxy_pass http://$ultid_upstream/api/v1/ai/embed-signin;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Cookie $http_cookie;
        proxy_set_header Authorization $http_authorization;
        proxy_set_header X-Ulti-User-Email $ulti_user_email;
        proxy_set_header X-Ulti-User-Name $ulti_user_name;
        proxy_set_header X-Ulti-User-Role $ulti_user_role;
        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 ${FORWARDED_PROTO};
    }

    # OpenWebUI API (prefix /ai/api — évite collision avec ultid /api/v1/)
    location /ai/api/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;

        rewrite ^/ai/api/?(.*)$ /api/$1 break;
        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # OpenWebUI constants chunk — t="" keeps socket.io on default namespace; /ai API paths baked in
    location = /ai/_app/immutable/chunks/QGuclOcQ.js {
        auth_request /api/v1/ai/embed-auth;
        default_type application/javascript;
        alias /etc/nginx/patches/QGuclOcQ.js;
    }
    location = /_app/immutable/chunks/QGuclOcQ.js {
        auth_request /api/v1/ai/embed-auth;
        default_type application/javascript;
        alias /etc/nginx/patches/QGuclOcQ.js;
    }

    # OpenWebUI assets (SPA uses root-relative /static, /_app — not /ai/…)
    location ^~ /static/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;

        auth_request /api/v1/ai/embed-auth;
        auth_request_set $ulti_user_email $upstream_http_x_ulti_user_email;
        auth_request_set $ulti_user_name $upstream_http_x_ulti_user_name;
        auth_request_set $ulti_user_role $upstream_http_x_ulti_user_role;

        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header X-Ulti-User-Email $ulti_user_email;
        proxy_set_header X-Ulti-User-Name $ulti_user_name;
        proxy_set_header X-Ulti-User-Role $ulti_user_role;

        proxy_set_header Accept-Encoding "";
        sub_filter_once off;
        sub_filter_types application/javascript text/javascript;
        sub_filter '"/api/' '"/ai/api/';
        sub_filter "'/api/" "'/ai/api/";
        include /etc/nginx/openwebui-subfilters.conf;
        sub_filter 'location.href="/auth"' 'location.href="/ai/auth"';
        sub_filter '"/auth?"' '"/ai/auth?"';
        sub_filter '"/auth"' '"/ai/auth"';
        sub_filter 'href="/"' 'href="/ai/"';
        sub_filter "href='/'" "href='/ai/'";
        sub_filter 'Open WebUI' 'UltiAI';
        sub_filter 'UltiAI (Open WebUI)' 'UltiAI';
    }

    location ^~ /_app/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;

        auth_request /api/v1/ai/embed-auth;
        auth_request_set $ulti_user_email $upstream_http_x_ulti_user_email;
        auth_request_set $ulti_user_name $upstream_http_x_ulti_user_name;
        auth_request_set $ulti_user_role $upstream_http_x_ulti_user_role;

        proxy_pass http://$openwebui_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header X-Ulti-User-Email $ulti_user_email;
        proxy_set_header X-Ulti-User-Name $ulti_user_name;
        proxy_set_header X-Ulti-User-Role $ulti_user_role;

        proxy_set_header Accept-Encoding "";
        sub_filter_once off;
        sub_filter_types application/javascript text/javascript application/json;
        sub_filter '"/api/' '"/ai/api/';
        sub_filter "'/api/" "'/ai/api/";
        include /etc/nginx/openwebui-subfilters.conf;
        sub_filter 'location.href="/auth"' 'location.href="/ai/auth"';
        sub_filter '"/auth?"' '"/ai/auth?"';
        sub_filter '"/auth"' '"/ai/auth"';
        sub_filter 'href="/"' 'href="/ai/"';
        sub_filter "href='/'" "href='/ai/'";
        sub_filter 'Open WebUI' 'UltiAI';
        sub_filter 'UltiAI (Open WebUI)' 'UltiAI';
    }

    location = /manifest.json {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $openwebui_upstream openwebui:8080;

        auth_request /api/v1/ai/embed-auth;
        auth_request_set $ulti_user_email $upstream_http_x_ulti_user_email;
        auth_request_set $ulti_user_name $upstream_http_x_ulti_user_name;
        auth_request_set $ulti_user_role $upstream_http_x_ulti_user_role;

        proxy_pass http://$openwebui_upstream/manifest.json;
        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 ${FORWARDED_PROTO};
        proxy_set_header X-Ulti-User-Email $ulti_user_email;
        proxy_set_header X-Ulti-User-Name $ulti_user_name;
        proxy_set_header X-Ulti-User-Role $ulti_user_role;
    }

    location /office/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $oo_upstream onlyoffice;
        rewrite ^/office/(.*)$ /$1 break;
        proxy_pass http://$oo_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host/office;
        proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO};
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
    }

    location = /office {
        return 301 /office/;
    }

    # UltiDrive — same suite frontend as mail (unified Next.js app)
    location /drive/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location = /drive {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Ulti Suite frontend (mail + drive + contacts + agenda + account + settings) — dev: pnpm dev on host (MAIL_FRONTEND_UPSTREAM=host.docker.internal:3004)
    # Prod: set MAIL_FRONTEND_UPSTREAM=suite-frontend:3000
    # Pages produit marketing (/suite/ultimail, /suite/ultidrive, …) — frontend Next.
    location ^~ /suite {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Démos publiques de la landing (zéro rétention) — frontend Next.
    location ^~ /demo {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Sauvegarde no-op des démos — route API du frontend Next, pas ultid.
    location ^~ /api/demo/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
    }

    location ^~ /api/auth/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
    }

    location ^~ /mail/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Do not 301 /mail → /mail/ (Next 308 /mail/ → /mail causes a redirect loop).
    location = /mail {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location = /mail/ {
        return 302 /mail/inbox;
    }

    location ^~ /login {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location ^~ /chat {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location ^~ /contacts {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location ^~ /agenda {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Réglages mail Ultimail (hors /mail/*)
    location ^~ /settings {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Compte utilisateur Ulti
    location ^~ /account {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # Ancien chemin compte → /account (ne pas matcher /compte-mark.svg etc.)
    location = /compte {
        return 301 /account;
    }

    location ^~ /compte/ {
        rewrite ^/compte/(.*)$ /account/$1 permanent;
    }

    # Console d'administration suite
    location ^~ /admin {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location ^~ /_next/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # next/font (Geist, etc.) — separate from /_next/ static chunks
    location ^~ /__nextjs_font/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
    }

    location ^~ /brand/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO};
    }

    location ^~ /mail-backgrounds/ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO};
    }

    # Public assets at repo root (launcher icons, etc.)
    location ~* ^/[^/]+\.(svg|png|ico|webp)$ {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream$request_uri;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO};
    }

    # Landing page de la suite (servie par le frontend Next).
    location = / {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $mail_upstream ${MAIL_FRONTEND_UPSTREAM};
        proxy_pass http://$mail_upstream;
        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 ${FORWARDED_PROTO};
    }

    # OpenWebUI SPA routes without /ai prefix (client nav + hard reload in UltiAI iframe).
    # Do not include "auth" — Ultimail OIDC uses /auth/application/o/… (Authentik).
    location ~ ^/(notes|workspace|c|home|automations|calendar|channels|playground|watch|s|error)(/|$) {
        return 302 /ai$uri$is_args$args;
    }

    location / {
        default_type text/plain;
        return 404 "Not found\n";
    }
}

# Stalwart webadmin + JMAP (mail.${DOMAIN})
server {
    listen 80;
    server_name mail.${DOMAIN};

    client_max_body_size 100M;

    location / {
        resolver 127.0.0.11 valid=10s ipv6=off;
        set $stalwart_upstream stalwart:8080;

        proxy_pass http://$stalwart_upstream;
        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 ${FORWARDED_PROTO};
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}
