diff --git a/birdnet-pipy/DOCS.md b/birdnet-pipy/DOCS.md index 11b409212..59b2e3492 100644 --- a/birdnet-pipy/DOCS.md +++ b/birdnet-pipy/DOCS.md @@ -10,7 +10,7 @@ ## Access - **Ingress:** Use the Home Assistant sidebar entry. -- **Direct:** `http://:8099` +- **Direct:** `http://:8011` (or the port you configure) ## Options diff --git a/birdnet-pipy/Dockerfile b/birdnet-pipy/Dockerfile index 58d379ed7..3f15db650 100644 --- a/birdnet-pipy/Dockerfile +++ b/birdnet-pipy/Dockerfile @@ -84,9 +84,6 @@ RUN python3 -m venv "${VIRTUAL_ENV}" ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" RUN pip install --no-cache-dir --extra-index-url https://www.piwheels.org/simple -r /app/requirements.txt -# Add frontend nginx -ADD "https://raw.githubusercontent.com/Suncuss/BirdNET-PiPy/refs/heads/main/frontend/nginx.conf" "/etc/nginx/servers/nginx.conf" - # Patch service hostnames for single-container usage RUN sed -i \ -e "s/API_HOST = 'api'/API_HOST = '127.0.0.1'/" \ diff --git a/birdnet-pipy/README.md b/birdnet-pipy/README.md index f6cc76775..1a27c0509 100644 --- a/birdnet-pipy/README.md +++ b/birdnet-pipy/README.md @@ -9,7 +9,7 @@ BirdNET-PiPy is a self-hosted system that uses the BirdNET deep-learning model t ## Configuration -Install, then start the add-on a first time. Open the Web UI from Home Assistant (Ingress) or directly at `http://:8099`. +Install, then start the add-on a first time. Open the Web UI from Home Assistant (Ingress) or directly at `http://:8011` (or the port you configure). Configure location, audio source, and other settings in the BirdNET-PiPy UI after the container starts. Options can be configured through three ways: diff --git a/birdnet-pipy/rootfs/etc/nginx/servers/nginx.conf b/birdnet-pipy/rootfs/etc/nginx/servers/nginx.conf new file mode 100644 index 000000000..2fad68ebf --- /dev/null +++ b/birdnet-pipy/rootfs/etc/nginx/servers/nginx.conf @@ -0,0 +1,102 @@ +server { + listen 80 default_server; + + root /usr/share/nginx/html; + index index.html; + + include /etc/nginx/includes/server_params.conf; + include /etc/nginx/includes/proxy_params.conf; + + # Allow large file uploads (for database migration) + client_max_body_size 500M; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + + # API proxy - forward /api/ requests to API server + # IMPORTANT: ^~ modifier prevents regex matches (like .png) from taking precedence + location ^~ /api/ { + proxy_pass http://127.0.0.1:5002; + 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; + + # Longer timeouts for migration imports + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + # Internal auth verification endpoint (for nginx auth_request) + location = /internal/auth { + internal; + proxy_pass http://127.0.0.1:5002/api/auth/verify; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header Cookie $http_cookie; + } + + # Auth error handler - returns JSON for API clients + location @stream_unauthorized { + default_type application/json; + return 401 '{"error": "Authentication required"}'; + } + + # Icecast audio stream proxy - forward /stream/ requests to Icecast server + # Protected by authentication when enabled + location ^~ /stream/ { + auth_request /internal/auth; + error_page 401 = @stream_unauthorized; + + proxy_pass http://127.0.0.1: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; + + # Streaming-specific settings + proxy_buffering off; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } + + # Handle static assets with long cache times + # Note: /api/ routes are handled above, so this only affects local static files + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # Handle Vue.js SPA routing - serve index.html for all routes that don't match static files + location / { + try_files $uri $uri/ /index.html; + } + + # Socket.IO WebSocket proxy - forward /socket.io/ requests to API server + location /socket.io/ { + proxy_pass http://127.0.0.1:5002/socket.io/; + 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_cache_bypass $http_upgrade; + } + + # Error pages + error_page 404 /index.html; + error_page 500 502 503 504 /index.html; +}