# BetterFrame Angie/nginx config — routes admin, kiosk-api, ws, node-red. # # Place in /etc/angie/conf.d/betterframe.conf or /etc/nginx/conf.d/betterframe.conf # Run on the Pi alongside the server. TLS termination here; backend services # bind to 0.0.0.0 but firewall should restrict to localhost in production. # Upstreams (BSB services) upstream betterframe_admin { server 127.0.0.1:18080; keepalive 16; } upstream betterframe_api { server 127.0.0.1:18081; keepalive 16; } upstream betterframe_ws { server 127.0.0.1:18082; } upstream betterframe_nodered { server 127.0.0.1:1880; keepalive 8; } # Rate limiting for public endpoints limit_req_zone $binary_remote_addr zone=bf_public:10m rate=30r/s; server { listen 80; listen [::]:80; server_name betterframe.local _; # In production: redirect to HTTPS # return 301 https://$host$request_uri; # For now: serve plain HTTP client_max_body_size 16M; # ---- Admin UI + admin API (session-authenticated) ---- location /admin/ { proxy_pass http://betterframe_admin; 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_http_version 1.1; proxy_set_header Connection ""; } location = /admin { return 301 /admin/; } location /setup { proxy_pass http://betterframe_admin; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /auth/ { proxy_pass http://betterframe_admin; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { proxy_pass http://betterframe_admin; } # ---- Kiosk REST API (Bearer kiosk-key) ---- location /api/kiosk/ { proxy_pass http://betterframe_api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_http_version 1.1; proxy_set_header Connection ""; } location /api/pair/ { # Rate-limit pairing initiate to deter brute force limit_req zone=bf_public burst=10 nodelay; proxy_pass http://betterframe_api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # ---- Admin API (session-authenticated) ---- location /api/admin/ { proxy_pass http://betterframe_admin; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # ---- Live kiosk WebSocket channel ---- location /ws/kiosk { proxy_pass http://betterframe_ws; 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_read_timeout 86400s; # long-lived proxy_send_timeout 86400s; } # ---- Node-RED dashboard (admin-only) ---- location /nrdp/ { # auth_request /api/admin/_check; # enable when auth-check endpoint ready rewrite ^/nrdp/(.*) /$1 break; proxy_pass http://betterframe_nodered; proxy_set_header Host $host; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } # ---- Node-RED HTTP-in (public, rate-limited) ---- location /in/public/ { limit_req zone=bf_public burst=20 nodelay; rewrite ^/in/public/(.*) /public/$1 break; proxy_pass http://betterframe_nodered; } # ---- Node-RED HTTP-in (kiosk-gated) ---- location /in/kiosk/ { # Bearer kiosk-key validated by Node-RED flow rewrite ^/in/kiosk/(.*) /kiosk/$1 break; proxy_pass http://betterframe_nodered; } # ---- Health/readiness/version (public) ---- location ~ ^/(healthz|readyz|version)$ { proxy_pass http://betterframe_admin; } # ---- Root redirect ---- location = / { proxy_pass http://betterframe_admin; } }