BMS/memory.md
megaproxy de9b8c51bd Add Unraid deployment via the Portainer API
ops/deploy.py ships each service's build context to the remote Docker
daemon's /build endpoint and pushes docker-compose.prod.yml as a Portainer
stack — there is no docker CLI in WSL and Unraid's SSH is closed.

The prod compose file drops the dev bind mounts and uvicorn --reload,
publishes only the frontend port (8000 is taken by Portainer's Edge
tunnel), and pins bms_net to 172.31.42.0/24 because the host's default
address pools are fully subnetted. Mosquitto's config is baked into an
image since the repo is not checked out on the host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 17:43:00 +01:00

85 lines
5.5 KiB
Markdown

# BMS — memory
DCIM demo platform: Next.js 16 frontend, FastAPI backend, TimescaleDB, Mosquitto MQTT,
and Python simulator bots that publish fake data-centre telemetry.
Live at **http://192.168.1.249:5646/dashboard** (Portainer stack `bms` on Unraid).
## Decisions & rationale
- **Auth is disabled (demo mode).** The repo shipped half-converted: `proxy.ts` already said
"auth is disabled for demo mode", but `app/layout.tsx` still wrapped the tree in
`<ClerkProvider>` and `topbar.tsx` used `<UserButton>`. `@clerk/nextjs` v7 fails `next build`
without a real publishable key, so the frontend image could not build at all. Finished the
conversion instead of buying Clerk keys: removed `ClerkProvider`/`UserButton`, deleted the
`sign-in`/`sign-up` route groups, and dropped `@clerk/nextjs` from `package.json`. The service
is LAN-only and reachable remotely over Tailscale, so the tailnet is the auth boundary.
- **Deploy via the Portainer API, not compose-on-host.** No `docker` CLI in WSL and Unraid's
SSH (port 22) is closed, so `ops/deploy.py` tars each service's build context, POSTs it to the
remote daemon's `/build` endpoint, then pushes `docker-compose.prod.yml` as a Portainer stack.
Same pattern as `toknmtr/ops/deploy.py`.
- **Separate `docker-compose.prod.yml`.** The dev compose file bind-mounts source into the
containers and runs `uvicorn --reload` — wrong for a long-running deploy, and impossible
anyway since the repo is not checked out on the Unraid host. The prod file references
prebuilt image tags, sets env inline (no `.env` files on the host), and publishes only the
frontend's port.
- **Mosquitto config is baked into an image** (`infra/mosquitto/Dockerfile`) rather than
bind-mounted, for the same reason: no repo on the host.
- **`bms_net` pins subnet 172.31.42.0/24.** Docker's default address pools on that host are
fully subnetted — 24 existing stacks have claimed every `172.17-172.31/16` and
`192.168.x/20` slot, so auto-allocation fails with "all predefined address pools have been
fully subnetted". An explicit subnet bypasses the allocator without disturbing other stacks.
Only other 172.31 tenant is `wg0` on 172.31.200.0/24.
- **Postgres data bind-mounts to `/mnt/user/appdata/bms/db`**, not a named volume, so a
growing timeseries DB lands on the array instead of filling Unraid's fixed-size `docker.img`.
- **`pnpm-workspace.yaml` must be copied in the frontend Dockerfile's deps stage.** It carries
the build-script allowlist; without it pnpm aborts with `ERR_PNPM_IGNORED_BUILDS`. The file
now declares both `allowBuilds` (pnpm 11 spelling) and `ignoredBuiltDependencies` (pnpm 10).
`sharp`, `unrs-resolver` and `msw` are all declined — the app has no `next/image`, so sharp
is dead weight. `packageManager: pnpm@11.20.0` is pinned so a future pnpm release cannot
silently change this again.
## Open questions / TODOs
- [ ] Postgres uses the repo's default credentials (`dcim`/`dcim_pass`). Fine while the DB has
no published host port and the box is LAN-only, but change both the compose env and
`DATABASE_URL` together if the port is ever exposed.
- [ ] The simulator floods logs with `mqtt WARNING There are N pending publish calls.` — it
publishes faster than aiomqtt drains. Cosmetic, but it makes `docker logs` near-useless
and grows the log file. Worth batching or throttling the bots.
- [ ] `backend/api/routes/ws.py` exists but nothing in the frontend opens a WebSocket (the UI
polls every 15 s). Either wire it up or drop it. Note: Next.js rewrites do not proxy WS
upgrades, so a WS client would need to reach the backend directly.
- [ ] No host port is published for Postgres or MQTT. Add a `ports:` entry to
`docker-compose.prod.yml` if direct DB access or external MQTT publishing is ever needed.
- [ ] No reverse-proxy entry yet. Add an Nginx Proxy Manager host pointing at `:5646` if this
should be reachable by name rather than IP:port.
## Session log
### 2026-08-04
- Cloned the Forgejo repo into `~/claude/projects/BMS` and deployed it to the Unraid host.
- Finished the demo-mode auth conversion (see Decisions) so the frontend image could build;
regenerated `pnpm-lock.yaml` without `@clerk/nextjs`. Verified `next build` locally — all
18 routes prerender.
- Hit and fixed three deploy blockers in order: (1) the frontend Dockerfile never copied
`pnpm-workspace.yaml`, so pnpm's ignored-builds allowlist was invisible inside the build;
(2) pnpm 11 renamed that setting to `allowBuilds` and ignores `ignoredBuiltDependencies`;
(3) the Unraid daemon's Docker address pools were exhausted, so the stack network could not
be created.
- Backend's host port 8000 deliberately not published — Portainer's Edge tunnel already owns
8000 on that host. Ports 1883/5433/5646 were free; only 5646 is published.
- Added `ops/deploy.py`, `docker-compose.prod.yml`, `infra/mosquitto/Dockerfile`; updated the
README (demo-mode note, Unraid deploy section) and both `.env` examples.
- Verified live: all 5 containers healthy, 30 min of seeded history present, telemetry
flowing, alarm engine firing (55 active), dashboard screenshotted and rendering correctly.
## External references
- Repo: https://git.rdx4.com/megaproxy/BMS
- Live dashboard: http://192.168.1.249:5646/dashboard
- API docs (Swagger): http://192.168.1.249:5646/api/backend/docs
- Portainer (stack `bms`, endpoint id 3): http://192.168.1.249:9000 — token at `~/.portainer-token`
- Postgres data on the host: `/mnt/user/appdata/bms/db`
- Deploy pattern copied from: `~/claude/projects/toknmtr/ops/deploy.py`