mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 19:06:34 +00:00
fix(deploy): make Docker the service runtime
Remove host daemon deployment for server, proxy, and Node-RED so Node-RED is only reachable through the Compose proxy boundary.
This commit is contained in:
parent
96d7cc45ba
commit
02412169a0
7 changed files with 56 additions and 277 deletions
117
deploy/README.md
117
deploy/README.md
|
|
@ -1,101 +1,80 @@
|
|||
# BetterFrame deployment
|
||||
|
||||
## Native install (Raspberry Pi)
|
||||
## Recommended: Docker services + native kiosk
|
||||
|
||||
### Server
|
||||
Run server, Angie/nginx, and Node-RED in Docker Compose. Only Angie publishes a
|
||||
host port. The BetterFrame backend ports and Node-RED are internal to the Docker
|
||||
network, which forces `/nrdp/`, `/in/kiosk/`, and admin traffic through the
|
||||
proxy auth rules.
|
||||
|
||||
```bash
|
||||
# Install Node.js 23 + Node-RED
|
||||
curl -fsSL https://deb.nodesource.com/setup_23.x | sudo bash -
|
||||
sudo apt install -y nodejs build-essential
|
||||
sudo npm install -g --unsafe-perm node-red
|
||||
|
||||
# Create user + dirs
|
||||
sudo useradd -r -m -d /var/lib/betterframe betterframe
|
||||
sudo mkdir -p /opt/betterframe /var/log/betterframe /etc/betterframe /var/lib/betterframe/nodered
|
||||
sudo chown -R betterframe:betterframe /var/lib/betterframe /var/log/betterframe
|
||||
|
||||
# Deploy code
|
||||
sudo git clone https://github.com/BetterCorp/BetterFrame.git /opt/betterframe
|
||||
cd /opt/betterframe
|
||||
sudo -u betterframe npm install
|
||||
sudo -u betterframe npm run build
|
||||
sudo cp sec-config.yaml /opt/betterframe/server/sec-config.yaml
|
||||
|
||||
# Install systemd units
|
||||
sudo cp deploy/systemd/betterframe-server.service /etc/systemd/system/
|
||||
sudo cp deploy/systemd/betterframe-nodered.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now betterframe-server betterframe-nodered
|
||||
docker compose -f deploy/docker/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
The native config binds BetterFrame service ports and Node-RED to `127.0.0.1`.
|
||||
Do not expose ports `18080`, `18081`, `18082`, or `1880` directly on the LAN.
|
||||
Use Angie/nginx as the public entry point so `/nrdp/`, `/in/kiosk/`, and the
|
||||
admin routes get the auth protections in `deploy/angie/betterframe.conf`.
|
||||
Published:
|
||||
|
||||
### Kiosk
|
||||
- `80` -> Angie/nginx public edge
|
||||
|
||||
Internal only:
|
||||
|
||||
- `18080` -> admin service
|
||||
- `18081` -> kiosk API service
|
||||
- `18082` -> kiosk WebSocket service
|
||||
- `1880` -> Node-RED
|
||||
|
||||
Access first-run setup at:
|
||||
|
||||
```text
|
||||
http://<pi-ip>/setup
|
||||
```
|
||||
|
||||
Node-RED is reachable only through:
|
||||
|
||||
```text
|
||||
http://<pi-ip>/nrdp/
|
||||
```
|
||||
|
||||
Do not publish `18080`, `18081`, `18082`, or `1880` on the host.
|
||||
|
||||
If migrating from an older native install, stop the old host daemons first:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now betterframe-server betterframe-nodered angie nginx 2>/dev/null || true
|
||||
```
|
||||
|
||||
## Kiosk
|
||||
|
||||
The kiosk still runs natively on the Pi because it needs Wayland/HDMI, GTK,
|
||||
GStreamer, display power control, and local hardware access.
|
||||
|
||||
```bash
|
||||
# Install GTK4 + GStreamer + WebKit
|
||||
sudo apt install -y libgtk-4-dev libgstreamer1.0-dev \
|
||||
libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-good \
|
||||
gstreamer1.0-plugins-bad gstreamer1.0-libav \
|
||||
gstreamer1.0-gtk4 libwebkitgtk-6.0-dev libssl-dev
|
||||
|
||||
# Install Rust
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.cargo/env
|
||||
|
||||
# Build
|
||||
cd ~/betterframe/kiosk
|
||||
cd /opt/betterframe/kiosk
|
||||
cargo build --release
|
||||
sudo install -Dm755 target/release/betterframe-kiosk /opt/betterframe/kiosk/betterframe-kiosk
|
||||
|
||||
# Install systemd user unit
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cp deploy/systemd/betterframe-kiosk.service ~/.config/systemd/user/
|
||||
cp /opt/betterframe/deploy/systemd/betterframe-kiosk.service ~/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now betterframe-kiosk
|
||||
```
|
||||
|
||||
### Angie proxy
|
||||
Kiosks should point at the proxy URL, not direct backend ports:
|
||||
|
||||
```bash
|
||||
sudo apt install -y angie # or nginx
|
||||
sudo cp deploy/angie/betterframe.conf /etc/angie/conf.d/
|
||||
sudo systemctl reload angie
|
||||
BETTERFRAME_SERVER=http://<pi-ip> /opt/betterframe/kiosk/betterframe-kiosk
|
||||
```
|
||||
|
||||
The Angie config gates `/nrdp/*` with the admin session/API-key auth-check
|
||||
endpoint and `/in/kiosk/*` with the kiosk Bearer-key auth-check endpoint.
|
||||
## Native server mode
|
||||
|
||||
Access: `http://<pi-ip>/setup` for first-run. Kiosks should use the proxy URL
|
||||
(`http://<pi-ip>` or `http://betterframe.local`), not direct backend ports.
|
||||
|
||||
## Docker
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Kiosk still runs natively on the Pi (needs Wayland/HDMI), not in Docker.
|
||||
The Compose stack uses `deploy/angie/betterframe.docker.conf` and
|
||||
`deploy/docker/sec-config.yaml` because service names, not `127.0.0.1`, are the
|
||||
correct upstreams inside the Docker network.
|
||||
|
||||
Access: `http://<pi-ip>/setup` for first-run.
|
||||
|
||||
## Production secrets
|
||||
|
||||
For production, store the server key via `systemd-creds`:
|
||||
|
||||
```bash
|
||||
sudo systemd-creds encrypt --name=betterframe-secret \
|
||||
/etc/betterframe/secret.key.plain /etc/betterframe/secret.key
|
||||
sudo chmod 0600 /etc/betterframe/secret.key
|
||||
sudo chown root:root /etc/betterframe/secret.key
|
||||
```
|
||||
|
||||
The systemd unit's `LoadCredential=` directive injects this into the
|
||||
service's `$CREDENTIALS_DIRECTORY`.
|
||||
Native server mode is for development only. Run it manually when debugging; do
|
||||
not install host daemons for BetterFrame server, Angie, or Node-RED in
|
||||
production. The Docker stack owns those services.
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
# 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 127.0.0.1; Angie/nginx is the only public HTTP edge.
|
||||
|
||||
# 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;
|
||||
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/ {
|
||||
auth_request /api/kiosk/_check;
|
||||
rewrite ^/in/kiosk/(.*) /kiosk/$1 break;
|
||||
proxy_pass http://betterframe_nodered;
|
||||
}
|
||||
|
||||
# ---- Proxy auth subrequests ----
|
||||
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;
|
||||
}
|
||||
|
||||
# ---- Health/readiness/version (public) ----
|
||||
location ~ ^/(healthz|readyz|version)$ {
|
||||
proxy_pass http://betterframe_admin;
|
||||
}
|
||||
|
||||
# ---- Root redirect ----
|
||||
location = / {
|
||||
proxy_pass http://betterframe_admin;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# BetterFrame Docker nginx config.
|
||||
#
|
||||
# Same routes as betterframe.conf, but upstreams use compose service names
|
||||
# instead of localhost.
|
||||
# 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; }
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# BetterFrame stack — server + Angie proxy + Node-RED.
|
||||
# BetterFrame stack: server + Angie proxy + Node-RED.
|
||||
# Kiosk runs on the Pi natively (not in Docker, needs Wayland/HDMI).
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f deploy/docker/docker-compose.yml up -d
|
||||
# docker compose -f deploy/docker/docker-compose.yml up -d --build
|
||||
#
|
||||
# Volumes:
|
||||
# betterframe-data — sqlite DB + secret.key
|
||||
# nodered-data — Node-RED flows
|
||||
# betterframe-data: sqlite DB + secret.key
|
||||
# nodered-data: Node-RED flows
|
||||
#
|
||||
# Bind 0.0.0.0:80 on the host (Angie). Backend services only reachable
|
||||
# from within the Docker network.
|
||||
# Only 0.0.0.0:80 is published on the host. Backend services and Node-RED
|
||||
# are reachable only from within the Docker network.
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
const settings = {
|
||||
uiHost: "127.0.0.1",
|
||||
uiPort: Number(process.env.PORT || 1880),
|
||||
functionGlobalContext: {},
|
||||
};
|
||||
|
||||
if (process.env.NODE_RED_CREDENTIAL_SECRET) {
|
||||
settings.credentialSecret = process.env.NODE_RED_CREDENTIAL_SECRET;
|
||||
}
|
||||
|
||||
module.exports = settings;
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
[Unit]
|
||||
Description=BetterFrame Node-RED
|
||||
Documentation=https://github.com/BetterCorp/BetterFrame
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=betterframe
|
||||
Group=betterframe
|
||||
WorkingDirectory=/var/lib/betterframe/nodered
|
||||
Environment=NODE_ENV=production
|
||||
Environment=PORT=1880
|
||||
ExecStart=/usr/bin/env node-red --userDir /var/lib/betterframe/nodered --settings /opt/betterframe/deploy/nodered/settings.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=/var/lib/betterframe/nodered
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
[Unit]
|
||||
Description=BetterFrame Server (BSB)
|
||||
Documentation=https://github.com/BetterCorp/BetterFrame
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=betterframe
|
||||
Group=betterframe
|
||||
WorkingDirectory=/opt/betterframe/server
|
||||
Environment=NODE_ENV=production
|
||||
Environment=NODE_OPTIONS=--import tsx
|
||||
ExecStart=/usr/bin/node --import tsx /opt/betterframe/node_modules/@bsb/base/lib/scripts/bsb-plugin-cli.js start
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=/var/lib/betterframe /var/log/betterframe
|
||||
LoadCredential=betterframe-secret:/etc/betterframe/secret.key
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in a new issue