diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 2dea475..d543d7d 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -10,13 +10,22 @@ services: - ../nginx/ssl:/etc/nginx/ssl:ro depends_on: - frontend + - backend + - file-storage restart: unless-stopped rabbitmq: image: rabbitmq:3.13-management file-storage: image: seber/maxit-file-storage:latest + ports: + - "8888:8888" # public server (for signed URL access) + - "8080:8080" # internal server (for backend/worker) environment: - APP_PORT=8888 + - INTERNAL_PORT=8080 + - ROOT_DIRECTORY=/data + volumes: + - file-storage-media:/data backend: image: maxit/backend:latest depends_on: @@ -36,7 +45,8 @@ services: - DB_PASSWORD=password - DB_NAME=maxit - FILE_STORAGE_HOST=file-storage - - FILE_STORAGE_PORT=8888 + - FILE_STORAGE_PORT=8080 + - FILE_STORAGE_PUBLIC_URL=https://mini-maxit.pl/files db: image: postgres:17 environment: @@ -68,13 +78,16 @@ services: - JOBS_DATA_VOLUME=maxit_jobs-data # watch out for prefix "maxit_" the same as compose name - RESPONSE_QUEUE_NAME=worker_response_queue - WORKER_QUEUE_NAME=worker_queue + - STORAGE_HOST=file-storage + - STORAGE_PORT=8080 frontend: image: maxit/frontend:latest depends_on: - backend environment: - - BACKEND_URL=http://backend:8000/api/v1 + - PUBLIC_BACKEND_API_URL=https://mini-maxit.pl/api/v1 - ORIGIN=https://mini-maxit.pl + - BODY_SIZE_LIMIT=20M expose: - "3000" volumes: diff --git a/nginx/conf/nginx.conf b/nginx/conf/nginx.conf index fd13923..816ecf5 100644 --- a/nginx/conf/nginx.conf +++ b/nginx/conf/nginx.conf @@ -33,8 +33,9 @@ http { application/atom+xml image/svg+xml; - # Rate limiting for frontend + # Rate limiting limit_req_zone $binary_remote_addr zone=frontend:10m rate=30r/s; + limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; proxy_buffer_size 32k; proxy_buffers 8 32k; @@ -72,6 +73,41 @@ http { add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; + # Backend API + location /api/ { + limit_req zone=api burst=50 nodelay; + + proxy_pass http://backend:8000; + 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; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # File storage - signed URL downloads only, no write access from outside + # Frontend URLs are like: /files/buckets/maxit/task/1/file.pdf?expires=...&signature=... + # We strip the /files prefix so file-storage receives /buckets/... (matching the signed path) + location /files/ { + limit_except GET { + deny all; + } + + rewrite ^/files(/.*)$ $1 break; + proxy_pass http://file-storage:8888; + 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; + + proxy_connect_timeout 30s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + # Frontend serving location / { limit_req zone=frontend burst=50 nodelay; diff --git a/nginx/conf/nginx.local.conf b/nginx/conf/nginx.local.conf new file mode 100644 index 0000000..ef89998 --- /dev/null +++ b/nginx/conf/nginx.local.conf @@ -0,0 +1,120 @@ +events { + worker_connections 1024; +} + +http { + client_max_body_size 25m; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Rate limiting + limit_req_zone $binary_remote_addr zone=frontend:10m rate=30r/s; + limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; + + proxy_buffer_size 32k; + proxy_buffers 8 32k; + proxy_busy_buffers_size 64k; + large_client_header_buffers 4 64k; + + server { + listen 80; + server_name _; + + # Backend API + location /api/ { + limit_req zone=api burst=50 nodelay; + + proxy_pass http://backend:8000; + 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; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # File storage - signed URL downloads only, no write access from outside + # Frontend URLs are like: /files/buckets/maxit/task/1/file.pdf?expires=...&signature=... + # We strip the /files prefix so file-storage receives /buckets/... (matching the signed path) + location /files/ { + limit_except GET { + deny all; + } + + rewrite ^/files(/.*)$ $1 break; + proxy_pass http://file-storage:8888; + 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; + + proxy_connect_timeout 30s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Frontend serving + location / { + limit_req zone=frontend burst=100 nodelay; + + proxy_pass http://frontend:3000; + 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; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Nginx health check endpoint + location /health { + access_log off; + return 200 "nginx healthy\n"; + add_header Content-Type text/plain; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + } +}