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>
279 lines
7.8 KiB
Markdown
279 lines
7.8 KiB
Markdown
# BMS — Data Centre Infrastructure Management
|
|
|
|
A full-stack DCIM platform with live sensor dashboards, alarm management, power/cooling monitoring, and simulated sensor bots.
|
|
|
|
**Stack:** Next.js 16 · FastAPI · PostgreSQL + TimescaleDB · MQTT (Mosquitto) · Docker Compose
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Prerequisites](#prerequisites)
|
|
2. [Quick Start — Docker Compose](#quick-start--docker-compose)
|
|
3. [Environment Variables](#environment-variables)
|
|
4. [Verify Everything Is Running](#verify-everything-is-running)
|
|
5. [Accessing the App](#accessing-the-app)
|
|
6. [Ports Reference](#ports-reference)
|
|
7. [Local Development (without Docker)](#local-development-without-docker)
|
|
8. [Project Structure](#project-structure)
|
|
9. [Stopping & Resetting](#stopping--resetting)
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
| Requirement | Minimum version | Check |
|
|
|---|---|---|
|
|
| Docker | 24+ | `docker --version` |
|
|
| Docker Compose | v2 (bundled with Docker Desktop / Engine) | `docker compose version` |
|
|
| Git | any | `git --version` |
|
|
|
|
That's it for the Docker path — Node and Python are **not** needed on the host.
|
|
|
|
---
|
|
|
|
## Quick Start — Docker Compose
|
|
|
|
### 1. Clone the repo
|
|
|
|
```bash
|
|
git clone https://git.rdx4.com/youruser/bms.git
|
|
cd bms
|
|
```
|
|
|
|
### 2. Create the environment files
|
|
|
|
```bash
|
|
cp backend/.env.example backend/.env
|
|
cp frontend/.env.local.example frontend/.env.local
|
|
```
|
|
|
|
The defaults work as-is — they already match the service names in `docker-compose.yml`. **The app runs in demo mode with authentication disabled**, so there are no keys to obtain before the first run. See [Environment Variables](#environment-variables) below.
|
|
|
|
### 3. Start all services
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
The first run downloads base images and builds the containers — this takes a few minutes. On subsequent starts it is much faster:
|
|
|
|
```bash
|
|
docker compose up
|
|
```
|
|
|
|
### 4. Done
|
|
|
|
Open your browser at **http://your-server:5646**
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
> **Authentication is disabled.** The app runs in demo mode: `frontend/proxy.ts` passes every
|
|
> request through and there is no auth provider in `app/layout.tsx`. Deploy it on a trusted
|
|
> network (LAN, VPN, or behind an authenticating reverse proxy) — every route is open.
|
|
>
|
|
> To turn auth back on: reinstall `@clerk/nextjs`, wrap the tree in `<ClerkProvider>` in
|
|
> `app/layout.tsx`, restore `<UserButton />` in `components/layout/topbar.tsx`, re-add the
|
|
> `sign-in` / `sign-up` route groups, and swap `proxy.ts` for `clerkMiddleware`.
|
|
|
|
### `backend/.env`
|
|
|
|
```env
|
|
# Database — matches the db service in docker-compose.yml, no change needed
|
|
DATABASE_URL=postgresql+asyncpg://dcim:dcim_pass@db:5432/dcim
|
|
|
|
# MQTT broker — matches the mqtt service, no change needed
|
|
MQTT_HOST=mqtt
|
|
MQTT_PORT=1883
|
|
|
|
# CORS — add your frontend origin if you expose the backend directly
|
|
# Leave empty when using the built-in Next.js proxy (recommended)
|
|
CORS_ORIGINS=[]
|
|
|
|
DEBUG=true
|
|
```
|
|
|
|
### `frontend/.env.local`
|
|
|
|
```env
|
|
# API path — leave as-is, Next.js proxies /api/backend/* to the backend internally
|
|
NEXT_PUBLIC_API_URL=/api/backend
|
|
|
|
# Backend internal URL (used by the Next.js server-side proxy, not sent to the browser)
|
|
# In Docker: http://backend:8000 In local dev: http://localhost:8000
|
|
BACKEND_INTERNAL_URL=http://backend:8000
|
|
```
|
|
|
|
---
|
|
|
|
## Verify Everything Is Running
|
|
|
|
```bash
|
|
docker compose ps
|
|
```
|
|
|
|
All five services should show `healthy` or `running`:
|
|
|
|
```
|
|
NAME STATUS
|
|
dcim_mqtt running (healthy)
|
|
dcim_db running (healthy)
|
|
dcim_backend running (healthy)
|
|
dcim_simulators running
|
|
dcim_frontend running
|
|
```
|
|
|
|
Check individual logs if something looks wrong:
|
|
|
|
```bash
|
|
docker compose logs backend # FastAPI logs
|
|
docker compose logs frontend # Next.js logs
|
|
docker compose logs db # PostgreSQL logs
|
|
docker compose logs simulators # Sensor bot logs
|
|
```
|
|
|
|
Test the backend API directly:
|
|
|
|
```bash
|
|
curl http://localhost:8000/api/health
|
|
# Expected: {"status":"ok"}
|
|
```
|
|
|
|
---
|
|
|
|
## Accessing the App
|
|
|
|
| What | URL |
|
|
|---|---|
|
|
| Dashboard | http://your-server:5646 |
|
|
| API docs (Swagger) | http://your-server:5646/api/backend/docs |
|
|
| API docs (ReDoc) | http://your-server:5646/api/backend/redoc |
|
|
|
|
> **Reverse proxy tip:** The frontend is the only service that needs to be publicly reachable. Point Nginx / Caddy / Traefik at port **5646**. The backend, database, and MQTT broker communicate only on the internal Docker network.
|
|
|
|
```
|
|
Browser → Reverse Proxy → :5646 (Next.js)
|
|
↓ server-side rewrite
|
|
:8000 (FastAPI) — internal only
|
|
```
|
|
|
|
---
|
|
|
|
## Ports Reference
|
|
|
|
| Service | Host port | Container port | Notes |
|
|
|---|---|---|---|
|
|
| Frontend (Next.js) | 5646 | 5646 | Public-facing |
|
|
| Backend (FastAPI) | 8000 | 8000 | Internal; exposed for dev/debugging |
|
|
| Database (PostgreSQL) | 5433 | 5432 | Mapped to 5433 to avoid conflicts with any local Postgres |
|
|
| MQTT (Mosquitto) | 1883 | 1883 | Internal |
|
|
|
|
---
|
|
|
|
## Deploying to the Unraid Server
|
|
|
|
The live instance runs on the Unraid Docker host at `192.168.1.249` as the Portainer stack
|
|
**`bms`** — reachable at **http://192.168.1.249:5646/dashboard**.
|
|
|
|
```bash
|
|
python3 ops/deploy.py # build all four images + deploy/update the stack
|
|
python3 ops/deploy.py --no-build # redeploy the stack using the existing images
|
|
```
|
|
|
|
There is no `docker` CLI in WSL and no SSH to the Unraid box, so `ops/deploy.py` ships each
|
|
service's build context to the remote daemon's `/build` endpoint over the Portainer API
|
|
(token at `~/.portainer-token`), then pushes `docker-compose.prod.yml` as a Portainer stack.
|
|
The script is idempotent — rerun it after any code change.
|
|
|
|
`docker-compose.prod.yml` differs from the dev `docker-compose.yml`: images instead of build
|
|
contexts, no source bind mounts, no `--reload`, and only the frontend publishes a host port.
|
|
Postgres data is bind-mounted to `/mnt/user/appdata/bms/db` so it lands on the Unraid array
|
|
instead of growing inside `docker.img`, and it survives redeploys.
|
|
|
|
## Local Development (without Docker)
|
|
|
|
Useful if you want hot-reload on the frontend or backend without rebuilding containers.
|
|
|
|
### Database only via Docker
|
|
|
|
```bash
|
|
docker compose up db mqtt
|
|
```
|
|
|
|
### Backend
|
|
|
|
Requires Python 3.12+.
|
|
|
|
```bash
|
|
cd backend
|
|
python -m venv .venv
|
|
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
pip install -r requirements.txt
|
|
uvicorn main:app --reload --port 8000
|
|
```
|
|
|
|
### Frontend
|
|
|
|
Requires Node 22+ and pnpm.
|
|
|
|
```bash
|
|
# Install pnpm if you don't have it
|
|
npm install -g pnpm
|
|
|
|
cd frontend
|
|
pnpm install
|
|
pnpm dev
|
|
```
|
|
|
|
Frontend dev server runs on http://localhost:3000 by default.
|
|
|
|
### Simulators
|
|
|
|
```bash
|
|
cd simulators
|
|
pip install -r requirements.txt
|
|
python main.py
|
|
```
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
bms/
|
|
├── frontend/ Next.js 16 app (TypeScript, Tailwind, shadcn/ui, Recharts)
|
|
│ ├── app/ Pages (dashboard, cooling, power, alarms, floor-map, …)
|
|
│ ├── components/ Shared UI components
|
|
│ └── lib/ API client, contexts, utilities
|
|
│
|
|
├── backend/ FastAPI app
|
|
│ ├── api/routes/ REST endpoints (alarms, cooling, power, env, …)
|
|
│ ├── core/ DB connection, config
|
|
│ ├── models/ SQLAlchemy models
|
|
│ └── services/ MQTT subscriber, alarm engine, WebSocket manager
|
|
│
|
|
├── simulators/ Python sensor bots — publish fake DCIM telemetry over MQTT
|
|
│ └── bots/ Per-device bots: CRAC, UPS, generator, PDU, env, …
|
|
│
|
|
├── infra/
|
|
│ └── mosquitto/ Mosquitto broker config
|
|
│
|
|
└── docker-compose.yml Orchestrates all services
|
|
```
|
|
|
|
---
|
|
|
|
## Stopping & Resetting
|
|
|
|
```bash
|
|
# Stop all containers (data is preserved)
|
|
docker compose down
|
|
|
|
# Stop and delete the database volume (full reset — all data lost)
|
|
docker compose down -v
|
|
|
|
# Rebuild images after code changes
|
|
docker compose up --build
|
|
```
|