//nbkelley /homelab

Homelab Dashboard#

What Was Established#

A Node.js + Express dashboard is deployed at https://status.nbkelley.com, serving homelab monitoring data. All API calls are server-side — no internal IPs, credentials, or raw API responses reach the browser.

Deployment#

Detail Value
Public URL https://status.nbkelley.com
Host proxy VM (192.168.1.222)
Port 3002
Runtime Node.js + Express, Docker container
compose.yaml location /home/iluvatar/compose.yaml on proxy VM
App directory /opt/homelab-dashboard/ on proxy VM
Routing Cloudflare Tunnel → 127.0.0.1:3002

File Structure#

/opt/homelab-dashboard/
  server.js         ← Express app, all API logic
  package.json
  package-lock.json
  dockerfile
  start.sh
  node_modules/
  public/
    index.html
    styles.css
    app.js          ← frontend render engine

compose.yaml Entry#

homelab-dashboard:
  build: /opt/homelab-dashboard
  container_name: homelab-dashboard
  restart: unless-stopped
  network_mode: host
  environment:
    - PORT=3002

network_mode: host means the app binds directly to host port 3002. The ports: mapping is ignored when using host networking.

Cloudflare Tunnel Routing#

Added as a Public Hostname in Cloudflare Zero Trust → Networks → Tunnels:

  • Subdomain: status
  • Domain: nbkelley.com
  • Type: HTTP
  • URL: 127.0.0.1:3002

NPM SSL (Let’s Encrypt) was attempted but failed. Cloudflare tunnel handles TLS termination instead — no NPM proxy host needed for this service.

API Endpoints#

Endpoint Description
GET /api/health {"ok":true} — liveness check
GET /api/summary Latest row from homelab_analysis Postgres table. 5-minute cache TTL.
GET /api/live Live data fetched in parallel from Prometheus, Uptime Kuma, UniFi, and Synology. Returns sections, hosts, monitors, network, nas. 2-minute cache TTL.

All data sources are fetched server-side and served as clean JSON. Both /api/summary and /api/live use an in-memory cache. On startup, caches are populated immediately and setInterval keeps them warm (lines 317–319).

Server-Side Caching#

Cache TTL Source
summary 5 minutes Postgres homelab_analysis table
live 2 minutes Prometheus, Uptime Kuma, UniFi, Synology (produces sections, hosts, monitors, network, nas)

On startup, both caches are populated immediately. setInterval keeps them warm in the background.

SECTIONS System#

server.js has a SECTIONS config (lines 34–61) that organizes all hosts and monitors into a hierarchical structure. Each section represents a top-level infrastructure box on the dashboard, with optional sub-groups that cluster related hosts and monitors.

All pattern matching is case-insensitive substring.

Current structure#

Section Section hosts Group Host patterns Monitor patterns
Proxmox proxmox Media servarr, docker jellyfin, hinterflix, sonarr, radarr, bazarr, prowlarr, lidarr, readarr
Infra proxy, cloudflared, prometheus, uptime, n8n, postgresql npm, cloudflare, prometheus, postgres, n8n, uptime kuma
Websites hugoboss nbkelley, hugoboss, mbta, mbtadash, homelab dashboard, homelab-dashboard
AI celebrimbor, elrond, wiki-llm, wiki ollama, open webui, openwebui, webui, wiki-llm

The applySections() function (lines 63–101) processes raw hosts and monitors through this config. Each section’s own hosts (matched by sectionHostPatterns) are shown as section-level metrics. Each group’s matched hosts and monitors are claimed and excluded from the ungrouped host/service lists. Groups with no matches are skipped.

Status rollup#

  • A group’s status is up if all items are up, down if all are down, otherwise degraded.
  • A section’s status is up if all its groups are up, down if any group is down, otherwise degraded.

Frontend (public/app.js)#

The frontend fetches /api/summary and /api/live in parallel on load and every 2 minutes. It renders:

  • Overall summary card — AI-generated text from homelab_analysis, with time-ago timestamp
  • Monitor strip — vertical stack of infra-box cards, one per section:
    • Section header shows section name, aggregate status (up/degraded/down), and CPU/RAM/disk bars for the section’s own host
    • Each section’s sub-groups render as a 2-column grid of subgroup cards, each with a header (name + status badge) and a sorted list of items:
      • Host items — status dot, name, instance, and CPU/RAM/disk progress bars (color-coded: green/yellow/red)
      • Monitor items — status dot, name, 24h uptime %, and ping ms
    • Down items are visually dimmed with a red left-edge indicator
  • Section cards — Hosts, Services, Network, NAS; each shows an AI sub-summary and a status badge (ok / warn / err)

Thresholds used for color coding:

Metric warn err
CPU 70% 85%
RAM 80% 90%
Disk 35% 60%
NAS disk temp >45°C
NAS volume >70%

Styling#

Minimal monospace design. Font: IBM Plex Mono (300/400/500 weights, loaded from Google Fonts). Color palette uses CSS variables (--bg, --surface, --border, --green, --yellow, --red, etc.). No sharp border-radius — all cards and badges are square. Responsive: single-column layout below 640px.

Proxy VM Context#

Service Port Notes
NPM admin 81 http://192.168.1.222:81
NPM HTTP 8000
NPM HTTPS 9443
MBTA dashboard 3000 node process (not Docker), PID varies
Homelab dashboard 3002 Docker, network_mode: host
Netbird in compose.yaml but not actively used

Rebuild Command#

SSH to proxy VM, then:

sudo docker compose -f /home/iluvatar/compose.yaml up -d --build homelab-dashboard

Known Issues#

  • Static files (public/) are baked into the Docker image at build time. Any CSS/HTML/JS or server changes require a rebuild. A volume mount would allow live edits without rebuild.

AI-Driven Monitoring Pipeline, n8n, PostgreSQL, MBTA Dashboard - Setup

Sources#

  • ingested/chats/Homelab-AI---2026-04-13.md
  • ingested/chats/183-Backend API call to push to website.md Homelab AI - 2026-04-18 · raw/conversations/chunks/2026-04-18-31-Homelab AI.json Codebase review - 2026-04-30 · /opt/homelab-dashboard/ Codebase review - 2026-05-01 · /opt/homelab-dashboard/