load_module modules/ngx_http_js_module.so;

worker_processes auto;

# Expose the instance-access JWT secret to njs (process.env) so the edge
# gateway can verify desktop access tokens locally. Values come from the
# container environment; they are not logged.
env INSTANCE_ACCESS_TOKEN_SECRET;
env JWT_SECRET;

events {
    worker_connections 8192;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    js_import desktop from /etc/nginx/njs/desktop_auth.js;

    # Cluster DNS resolver, templated by start.sh from /etc/resolv.conf so the
    # desktop location can resolve per-instance Service FQDNs at request time.
    resolver __DNS_RESOLVER__ valid=10s ipv6=off;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    gzip on;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_vary on;
    gzip_types
        application/javascript
        application/json
        application/xml
        image/svg+xml
        text/css
        text/javascript
        text/plain
        text/xml;
    keepalive_timeout 75;
    keepalive_requests 10000;
    server_tokens off;

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

    map $desktop_target $desktop_proxy_mode {
        "deny" "deny";
        "http://127.0.0.1:9001" "fallback";
        default "direct";
    }

    log_format desktop_proxy '$remote_addr - $status "$request_method $desktop_clean_uri $server_protocol" '
                             'mode=$desktop_proxy_mode upstream="$upstream_addr" '
                             'rt=$request_time urt=$upstream_response_time bytes=$body_bytes_sent';

    upstream clawreef_backend {
        server 127.0.0.1:9001;
        keepalive 128;
    }

    server {
        listen 8443 ssl;
        http2 on;
        server_name _;

        ssl_certificate /etc/nginx/tls/tls.crt;
        ssl_certificate_key /etc/nginx/tls/tls.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers off;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 1d;

        root /usr/share/nginx/html;
        index index.html;

        # Allow runtime workspace archives through the API.
        # Default nginx client_max_body_size (1 MiB) would reject normal
        # workspace uploads at the edge with 413 Content Too Large before
        # the request reaches the backend. start.sh rewrites this value from
        # CLAWMANAGER_WORKSPACE_ARCHIVE_MAX_MIB when set.
        client_max_body_size 500m;

        location = /healthz {
            access_log off;
            add_header Content-Type text/plain;
            return 200 'ok';
        }

        location ^~ /assets/ {
            try_files $uri =404;
            access_log off;
            expires 1y;
            add_header Cache-Control "public, max-age=31536000, immutable";
        }

        location ~* ^/(?:lobster_logo|lobster_transparent|openclaw|hermes)\.png$ {
            try_files $uri =404;
            access_log off;
            expires 30d;
            add_header Cache-Control "public, max-age=2592000";
        }

        location ~* ^/vendor-icons/.+\.(?:png|jpg|jpeg|gif|webp|ico|svg)$ {
            try_files $uri =404;
            access_log off;
            expires 30d;
            add_header Cache-Control "public, max-age=2592000";
        }

        location ~ ^/s/ {
            proxy_pass http://clawreef_backend;
            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 https;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_connect_timeout 15s;
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            proxy_buffering off;
            proxy_request_buffering off;
        }

        location ~ ^/api/v1/instances/(?<inst_id>[0-9]+)/proxy(?<proxy_sub>/.*)?$ {
            # njs verifies the instance-access JWT locally and chooses the
            # proxy target: the instance Service (direct), the in-process
            # control-plane proxy (gray fallback), or "deny".
            js_set $desktop_target desktop.resolveTarget;
            # Forward the original URI to the upstream but with the JWT "token"
            # query param stripped, so the access token never reaches webtop.
            js_set $desktop_clean_uri desktop.cleanUri;

            access_log /var/log/nginx/access.log desktop_proxy;
            add_header X-ClawManager-Desktop-Proxy $desktop_proxy_mode always;

            error_page 463 = @desktop_denied;
            if ($desktop_target = "deny") {
                return 463;
            }

            proxy_http_version 1.1;
            proxy_set_header Host $http_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 https;
            proxy_set_header X-Forwarded-Prefix /api/v1/instances/$inst_id/proxy;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            # Browsers may include the original iframe URL (with ?token=...)
            # as Referer on subresource requests. Do not forward it to webtop.
            proxy_set_header Referer "";

            proxy_ssl_verify off;
            proxy_ssl_server_name on;

            proxy_connect_timeout 15s;
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            proxy_buffering off;
            proxy_request_buffering off;

            proxy_pass $desktop_target$desktop_clean_uri;
        }

        location /api/ {
            proxy_pass http://clawreef_backend;
            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 https;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_connect_timeout 15s;
            proxy_read_timeout 3600s;
        }

        location / {
            try_files $uri $uri/ /index.html;
        }

        location @desktop_denied {
            add_header Content-Type text/plain always;
            return 401 "Access token expired or invalid";
        }
    }
}
