feat(proxy): plain-HTTP LAN entry for waiter (:80) and manager (:8081), no certs required
Phones can now open the waiter app at http://<LAN IP> with no domain, DNS record or certificate (works around DNS-rebinding failures, KI-001). The manager gets http://<LAN IP>:8081, with http://<LAN IP>/manager redirecting there. waiter.*/manager.* hostnames on :80 still redirect to https, so legacy domain sites behave as before. - Both plain-HTTP servers are LAN-only (allow RFC1918/loopback/ULA/link-local, deny all -> 403), so a router port-forward can't expose an unencrypted POS - nginx-proxy/nginx.conf and the install.sh heredoc are now byte-identical (one canonical config, routing map in its header) - install.sh generates a 10-year self-signed cert when certs/ is empty (nginx won't start its TLS listeners without one), detects HOST_IP via 'ip route get', prompts for it on fresh installs and backfills it into an existing .env, always starts the stack, prints the LAN URLs - docker-compose publishes 8081; .env.example documents HOST_IP - pack README: ports/request path updated, CS-5 byte-identical check Verified: nginx -t; install.sh in Debian (fresh / upgrade without HOST_IP / re-run - no duplicate HOST_IP, cert SAN includes HOST_IP, key 600); full stack from freshly built images: every entry point returns the expected 200/301/302, and removing the gateway's range from the allow list yields 403 on :80 and :8081. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+10
-6
@@ -13,13 +13,15 @@ This folder is its **own git repo**, nested inside the `xenia-pos` parent repo.
|
||||
| Service | Stack | Dev port | Prod exposure | Image |
|
||||
|---|---|---|---|---|
|
||||
| `local_backend` | FastAPI + SQLAlchemy + SQLite (`/app/data/pos.db`) | 8000 | only through proxy / inner nginx | `pos-backend` |
|
||||
| `waiter_pwa` | React + Vite + vite-plugin-pwa, axios, zustand, react-query, Dexie (IndexedDB) | 5173 | `https://<host>` (443) | `pos-waiter` |
|
||||
| `manager_dashboard` | React + Vite | 5174 | `https://<host>:4443` | `pos-manager` |
|
||||
| `proxy` | nginx:alpine, TLS termination | — | 80 → 443 redirect, 443, 4443 | stock |
|
||||
| `waiter_pwa` | React + Vite + vite-plugin-pwa, axios, zustand, react-query, Dexie (IndexedDB) | 5173 | **`http://<LAN IP>`** (80, LAN only) · `https://waiter.<domain>` / `https://<IP>` (443) | `pos-waiter` |
|
||||
| `manager_dashboard` | React + Vite | 5174 | **`http://<LAN IP>:8081`** (LAN only; `http://<IP>/manager` redirects there) · `https://manager.<domain>` (443) · `https://<IP>:4443` | `pos-manager` |
|
||||
| `proxy` | nginx:alpine | — | 80, 443, 4443, 8081. See the header of `nginx-proxy/nginx.conf` for the full routing map | stock |
|
||||
|
||||
### Request path in production
|
||||
Phones normally use **plain HTTP by LAN IP** (`http://<HOST_IP>` → proxy:80 default_server). Only private source IPs are allowed; everything else gets 403.
|
||||
Legacy domain sites use `https://waiter.<domain>` (proxy:443), and `waiter.*`/`manager.*` on port 80 still redirect to https.
|
||||
```
|
||||
phone ──https──▶ proxy:443 ──http──▶ waiter_pwa nginx:80 ──┬─ / → static SPA
|
||||
phone ──http(s)──▶ proxy:80/443 ──http──▶ waiter_pwa nginx:80 ──┬─ / → static SPA
|
||||
├─ /api/ → backend:8000
|
||||
├─ /api/ws/ → backend:8000 (WebSocket upgrade)
|
||||
└─ /static/ → backend:8000/static/
|
||||
@@ -64,8 +66,10 @@ Waiters walk in and out of WiFi range and phones sleep. Any new waiter flow must
|
||||
**CS-4. Money and prices are snapshotted.**
|
||||
Prices, costs and discounts are copied onto order items and logs when the action happens (snapshot pattern, `PriceEventLog`). Reports read the snapshots, never the live catalogue.
|
||||
|
||||
**CS-5. The proxy config lives in two places.**
|
||||
Edit `nginx-proxy/nginx.conf` **and** the heredoc in `install.sh` together (global Rule 9).
|
||||
**CS-5. The proxy config lives in two places and must stay byte-identical.**
|
||||
`install.sh` writes the heredoc on client sites, and `nginx-proxy/nginx.conf` is the repo copy. Edit both together (global Rule 9) and check with:
|
||||
`sed -n "/<< 'EOF'/,/^EOF/p" install.sh | sed '1d;$d' | diff - nginx-proxy/nginx.conf`
|
||||
The plain-HTTP servers (80 default_server, 8081) must keep their LAN-only `allow`/`deny` block.
|
||||
|
||||
**CS-6. Permissions are checked on the backend.**
|
||||
Hiding a button in the UI is not access control. Every new endpoint declares its role or `perm_*` requirement through `routers/deps.py`.
|
||||
|
||||
Reference in New Issue
Block a user