# ADR-0006 · Router serves console shell, Cell provides APIs **Status**: accepted **Date**: 2026-05 ## Context The operator console is a SPA that needs: - A stable, well-known URL (`http://blocao-router.local/`). - Access to APIs from both the router (network config) and the Cell (Frigate, forensic engine). - TLS termination. - Auth. Options: 1. SPA hosted on the Cell, router redirects. 2. SPA hosted on the router, Cell exposes APIs cross-origin. 3. SPA hosted on the router, router reverse-proxies to Cell APIs. ## Decision **Option 3**. The router always serves the SPA shell at `http://blocao-router.local/` (or HTTPS once cert is set up). The Cell provides APIs only — never a UI directly. The router reverse-proxies API calls based on path: - `/api/router/*` → local `ubus` over `/ubus` - `/api/cell/*` → discovered Cell IP/port - `/api/cell//*` → specific Cell when multi-Cell The browser only ever talks to the router. CORS never appears. ## Consequences **Good**: - Single origin, no CORS complications. - Cell can be unreachable temporarily without breaking the router-side parts of the UI. - Router is the natural single point of auth. - Cell discovery is a router responsibility, transparent to the UI. **Bad / trade-offs**: - Router has to do reverse-proxy work; small CPU cost on a low-power device. - If the router is down, no UI access at all. Mitigated by Tailscale fallback to direct Cell SSH (ops only). - The mental model "router = thin, Cell = fat" is partially violated — router does carry the SPA bytes. ## Alternatives considered - **Cell hosts the SPA**: requires Cell to be discoverable from operator's machine, makes URLs unstable when Cell IPs change. - **Hub hosts the SPA, both router and Cell expose APIs**: requires WAN connectivity for the operator UI, breaks air-gapped deployments.