worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # WebDAV cache zone definition proxy_cache_path /meta-core/cache levels=1:2 # 2-level directory structure keys_zone=webdav_cache:10m # 10MB shared memory for keys max_size=10g # Max 10GB on disk inactive=1h # Remove if not accessed for 1 hour use_temp_path=off; # Write directly to cache dir # Upstream: meta-core Go API upstream meta-core-api { server 127.0.0.1:9000; } # Upstream: rclone RC API upstream rclone { server 127.0.0.1:5572; } server { listen 80; server_name _; # ========================================================================= # Dashboard (React SPA) # ========================================================================= root /app/dashboard/dist; index index.html; location / { try_files $uri $uri/ /index.html; } # ========================================================================= # Go API routes # All /api/* routes are proxied to meta-core Go server # ========================================================================= location /api/ { proxy_pass http://meta-core-api/api/; 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; # SSE support for /api/events/subscribe proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; # Snapshot import accepts multi-MB ZIP uploads. client_max_body_size 0; } # ========================================================================= # Health check (direct to Go API) # ========================================================================= location /health { proxy_pass http://meta-core-api/health; proxy_http_version 1.1; proxy_set_header Host $host; } # ========================================================================= # Metadata API (for services to access metadata) # ========================================================================= location /meta/ { proxy_pass http://meta-core-api/meta/; 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; } location /meta { proxy_pass http://meta-core-api/meta; proxy_http_version 1.1; proxy_set_header Host $host; } # ========================================================================= # File/Data API endpoints # ========================================================================= location /file/ { proxy_pass http://meta-core-api/file/; 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; } location /data/ { proxy_pass http://meta-core-api/data/; 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; } # ========================================================================= # Service discovery # ========================================================================= location /services { proxy_pass http://meta-core-api/services; proxy_http_version 1.1; proxy_set_header Host $host; } location /services/ { proxy_pass http://meta-core-api/services/; proxy_http_version 1.1; proxy_set_header Host $host; } # meta-discovery v1 neighbours (supersedes /services, which stays # while consumers migrate). location /neighbors { proxy_pass http://meta-core-api/neighbors; proxy_http_version 1.1; proxy_set_header Host $host; } # ========================================================================= # Leader/role endpoints # ========================================================================= location /leader { proxy_pass http://meta-core-api/leader; proxy_http_version 1.1; proxy_set_header Host $host; } location /role { proxy_pass http://meta-core-api/role; proxy_http_version 1.1; proxy_set_header Host $host; } location /urls { proxy_pass http://meta-core-api/urls; proxy_http_version 1.1; proxy_set_header Host $host; } location /status { proxy_pass http://meta-core-api/status; proxy_http_version 1.1; proxy_set_header Host $host; } # ========================================================================= # rclone Web UI (proxied with basic auth) # ========================================================================= location /rclone/ { proxy_pass http://rclone/; 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 Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Basic auth for rclone auth_basic "rclone"; auth_basic_user_file /etc/nginx/.htpasswd; } # ========================================================================= # Metadata Editor (standalone React app) # Serves static files for the metadata editor # API routes (/api/metadata/*, /api/kv/*) are handled by the Go API # via the /api/ proxy above # ========================================================================= location /editor/ { alias /app/editor/dist/; index index.html; try_files $uri $uri/ /editor/index.html; } # ========================================================================= # WebDAV HTML browser (for iframe embedding in dashboard) # Returns HTML directory listings instead of JSON # ========================================================================= # At the browser root, strip the "../" parent-dir row that autoindex # otherwise emits — clicking it inside the dashboard iframe would # navigate to "/" (the dashboard itself), nesting the page inside itself. location = /webdav-browser/ { alias /files/; autoindex on; charset utf-8; sub_filter '../' ''; sub_filter_once on; } location /webdav-browser/ { alias /files/; autoindex on; # No autoindex_format = defaults to HTML charset utf-8; } # ========================================================================= # WebDAV endpoint for file access (nginx proxy_cache for GET/HEAD) # Supports: GET (read), PUT (write), DELETE, MKCOL (mkdir), PROPFIND # Files are cached by nginx for faster repeated access # ========================================================================= location /webdav/ { # Slice module for large file chunked caching slice 1m; proxy_cache_key $uri$slice_range; proxy_set_header Range $slice_range; # Enable caching for GET/HEAD requests proxy_cache webdav_cache; proxy_cache_valid 200 206 1h; proxy_cache_methods GET HEAD; # Don't cache write operations set $no_cache 0; if ($request_method !~ ^(GET|HEAD)$) { set $no_cache 1; } proxy_no_cache $no_cache; proxy_cache_bypass $no_cache; # Cache status header for debugging add_header X-Cache-Status $upstream_cache_status always; # Proxy settings proxy_pass http://meta-core-api/webdav/; 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; # Enable buffering for caching (required for proxy_cache) proxy_buffering on; proxy_request_buffering off; # Don't buffer uploads # Large file support client_max_body_size 0; proxy_read_timeout 600s; proxy_send_timeout 600s; # CORS headers for cross-container access add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Depth, Destination, Range, If-Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range, Accept-Ranges, X-Cache-Status' always; # Handle OPTIONS preflight if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Depth, Destination, Range, If-Range'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } } location /webdav { proxy_pass http://meta-core-api/webdav; proxy_http_version 1.1; proxy_set_header Host $host; } } }