BetterFrame/deploy/angie/betterframe.docker.conf

150 lines
4.9 KiB
Text
Raw Normal View History

# BetterFrame Docker nginx config.
#
# Production proxy routes for the Docker Compose stack. Upstreams use compose
# service names and only this proxy is published on the host.
upstream betterframe_admin { server server:18080; keepalive 16; }
upstream betterframe_api { server server:18081; keepalive 16; }
upstream betterframe_ws { server server:18082; }
upstream betterframe_nodered { server nodered:1880; keepalive 8; }
limit_req_zone $binary_remote_addr zone=bf_public:10m rate=30r/s;
server {
listen 80;
server_name _;
client_max_body_size 16M;
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; }
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/ {
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;
}
location /api/admin/ {
proxy_pass http://betterframe_admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
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;
proxy_send_timeout 86400s;
}
feat(remote-debug): journal streaming + secure terminal via WebSocket Kiosk side (remote_debug.rs + ws_client.rs refactor): - Journal streaming: server sends journal-start → kiosk spawns journalctl -f, pipes lines back as journal-line messages via WS. journal-stop kills the process. On-demand, not always-on. - Terminal: server sends terminal-request → kiosk checks lockout + firmware_channel == "dev" → generates 8-char code displayed on screen as fullscreen overlay (NOT logged) → server relays admin's code via terminal-auth → kiosk validates with constant-time compare → on success spawns bash, relays I/O as base64 terminal-data. - Lockout: 3 failed codes per boot → lockout_count++. 3 lockouts (9 total failures) → permanent (reflash only). Reboot resets attempt counter, not lockout counter. Successful pairing resets all. - ws_client.rs rewritten with split reader/writer + tokio::select! for multiplexing incoming WS messages with outbound journal/terminal data from sync threads. Server side (coordinator-ws + routes-admin): - New admin debug WS endpoint: /ws/admin/debug/:kioskId. Authenticated via admin API key (query param) or session cookie. Relays messages bidirectionally between admin browser ↔ kiosk. - Admin pages: /admin/kiosks/:id/logs (journal viewer with start/ stop/clear) and /admin/kiosks/:id/terminal (code entry + terminal area). Both open in new tabs from the kiosk detail page. - Angie proxy config updated with /ws/admin/debug/ location block. Security: - Terminal only on dev channel - Code displayed physically on screen, never logged or stored server-side - Lockout: 3/boot, 3 lockouts = permanent, pairing resets - Kiosk responds "locked" without specifying which lockout triggered
2026-05-22 18:13:39 +00:00
# Admin debug WS (journal + terminal) — authenticated via API key in query.
location /ws/admin/debug/ {
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;
proxy_send_timeout 86400s;
}
location /nrdp/ {
auth_request /api/admin/_check;
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";
}
location = /nrdp { return 301 /nrdp/; }
location /dash/ {
auth_request /api/kiosk/_check;
auth_request_set $bf_kiosk_id $upstream_http_x_betterframe_kiosk_id;
proxy_pass http://betterframe_nodered;
proxy_set_header Host $host;
proxy_set_header X-BetterFrame-Kiosk-Id $bf_kiosk_id;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /in/public/ {
limit_req zone=bf_public burst=20 nodelay;
rewrite ^/in/public/(.*) /$1 break;
proxy_pass http://betterframe_nodered;
}
location /in/kiosk/ {
auth_request /api/kiosk/_check;
auth_request_set $bf_kiosk_id $upstream_http_x_betterframe_kiosk_id;
rewrite ^/in/kiosk/(.*) /$1 break;
proxy_pass http://betterframe_nodered;
proxy_set_header X-BetterFrame-Kiosk-Id $bf_kiosk_id;
}
location = /api/admin/_check {
internal;
proxy_pass http://betterframe_admin;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Cookie $http_cookie;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-Real-IP $remote_addr;
}
location = /api/kiosk/_check {
internal;
proxy_pass http://betterframe_api;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Authorization $http_authorization;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ { return 404; }
location /ws/ { return 404; }
location ~ ^/(healthz|readyz|version)$ {
proxy_pass http://betterframe_admin;
}
location = / {
proxy_pass http://betterframe_admin;
}
location / {
proxy_pass http://betterframe_nodered;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}