diff --git a/birdnet-pipy/CHANGELOG.md b/birdnet-pipy/CHANGELOG.md index bd997146e..0a97b3ff6 100644 --- a/birdnet-pipy/CHANGELOG.md +++ b/birdnet-pipy/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.3.2-2 (31-01-2026) - Minor bugs fixed +## 0.3.2-3 (2026-01-30) +- Build frontend with /birdnet/ base path and serve under /birdnet/ for ingress compatibility. ## 0.3.2 (2026-01-30) - Update to latest version from Suncuss/BirdNET-PiPy (changelog : https://github.com/Suncuss/BirdNET-PiPy/releases) ## 0.6.6 (30-01-2026) diff --git a/birdnet-pipy/Dockerfile b/birdnet-pipy/Dockerfile index 6c7e3651b..7633a80a0 100644 --- a/birdnet-pipy/Dockerfile +++ b/birdnet-pipy/Dockerfile @@ -27,7 +27,7 @@ RUN mkdir -p /src \ WORKDIR /src/frontend RUN npm ci --prefer-offline -RUN npm run build +RUN npm run build -- --base=/birdnet/ FROM ${BUILD_FROM} @@ -57,7 +57,7 @@ RUN chmod 744 /ha_lsio.sh && if grep -qr "lsio" /etc; then /ha_lsio.sh "$CONFIGL # Copy local files COPY rootfs/ / RUN find /etc -type f \( -name "*.sh" -o -path "*/services.d/*/run" \) -exec chmod +x {} \; -COPY --from=frontend-builder /src/frontend/nginx.conf /etc/nginx/servers/nginx.conf +COPY rootfs/etc/nginx/servers/nginx.conf /etc/nginx/servers/nginx.conf # Uses /bin for compatibility purposes # hadolint ignore=DL4005 @@ -94,7 +94,7 @@ RUN sed -i \ COPY --from=frontend-builder /src/deployment/audio/scripts/start-icecast.sh /usr/local/bin/start-icecast.sh RUN chown icecast2 /usr/local/bin/start-icecast.sh && chmod 755 /usr/local/bin/start-icecast.sh -COPY --from=frontend-builder /src/frontend/dist /usr/share/nginx/html +COPY --from=frontend-builder /src/frontend/dist /usr/share/nginx/html/birdnet ################ # 4 Entrypoint # diff --git a/birdnet-pipy/config.yaml b/birdnet-pipy/config.yaml index c84821813..437b89bfa 100644 --- a/birdnet-pipy/config.yaml +++ b/birdnet-pipy/config.yaml @@ -101,4 +101,4 @@ schema: ssl: bool? slug: birdnet-pipy url: https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-pipy -version: "0.3.2-2" +version: "0.3.2-3" diff --git a/birdnet-pipy/rootfs/etc/nginx/includes/ingress_params.conf b/birdnet-pipy/rootfs/etc/nginx/includes/ingress_params.conf index 00a00e5c0..9338d5e3f 100644 --- a/birdnet-pipy/rootfs/etc/nginx/includes/ingress_params.conf +++ b/birdnet-pipy/rootfs/etc/nginx/includes/ingress_params.conf @@ -5,3 +5,5 @@ sub_filter_types *; sub_filter '' ''; sub_filter 'href="/' 'href="%%ingress_entry%%/'; sub_filter 'src="/' 'src="%%ingress_entry%%/'; +sub_filter '"/birdnet/' '"%%ingress_entry%%/birdnet/'; +sub_filter 'url(/birdnet/' 'url(%%ingress_entry%%/birdnet/'; 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..27d1ded03 --- /dev/null +++ b/birdnet-pipy/rootfs/etc/nginx/servers/nginx.conf @@ -0,0 +1,107 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # 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; + + # Allow large file uploads (for database migration) + client_max_body_size 500M; + + # API proxy - forward /api/ requests to API server + # IMPORTANT: ^~ modifier prevents regex matches (like .png) from taking precedence + location ^~ /api/ { + proxy_pass http://api: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://api: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://icecast: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; + } + + location = / { + return 302 /birdnet/; + } + + location = /birdnet { + return 301 /birdnet/; + } + + # Handle Vue.js SPA routing - serve index.html for all routes that don't match static files + location /birdnet/ { + try_files $uri $uri/ /birdnet/index.html; + } + + # Socket.IO WebSocket proxy - forward /socket.io/ requests to API server + location /socket.io/ { + proxy_pass http://api: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 /birdnet/index.html; + error_page 500 502 503 504 /birdnet/index.html; +}