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

5.5 KiB

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