Skip to content

mikeathan/nodeherder

Repository files navigation

Node Herder Setup Guide

Node Herder is a smart home management system with Go backend, Vue.js frontend, MQTT broker, and Zigbee2MQTT integration.

Quick Start

1. Initial Setup

Create a .env file in the project root:

# .env (project root)
DATA_ROOT=/opt/nodeherder
Z2M_DEVICE=/dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0001-if00-port0
Z2M_SERIAL_PORT=/dev/ttyUSB0

2. Run Setup Script

The setup script creates MQTT and Zigbee2MQTT configurations:

./backend/scripts/setup-backend.sh

What it does

  • Reads DATA_ROOT from .env at project root
  • Creates MQTT directories at ${DATA_ROOT}/mqtt/
  • Generates random MQTT credentials
  • Creates mosquitto.conf and password file
  • Writes MQTT credentials to backend/.env
  • Creates/updates Zigbee2MQTT configuration.yaml

Example output:

📁 DATA_ROOT = /opt/nodeherder
✅ Generating Mosquitto password...
✅ Password written → /opt/nodeherder/mqtt/config/password.txt
✅ Created → /opt/nodeherder/mqtt/config/mosquitto.conf
✅ MQTT credentials generated
   USER = mqttuser
   PASS = [randomly generated]
✅ MQTT_URL written:
   tcp://mqttuser:[password]@mqtt:1883

3. Start Services

docker-compose -f docker-compose.backend.yml up -d

The compose file reads DATA_ROOT, Z2M_DEVICE, and Z2M_SERIAL_PORT from .env to mount volumes.


Environment Configuration

Root .env (for Docker Compose)

Location: /path/to/node-herder/.env

# Data directory for MQTT and Zigbee2MQTT (outside repo)
DATA_ROOT=/opt/nodeherder

# Zigbee USB device
Z2M_DEVICE=/dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0001-if00-port0

# Serial port inside Zigbee2MQTT container
Z2M_SERIAL_PORT=/dev/ttyUSB0

Variables:

  • DATA_ROOT - Base directory for MQTT and Z2M data (outside git repo)
  • Z2M_DEVICE - Host USB device path for Zigbee dongle
  • Z2M_SERIAL_PORT - Serial port path inside container (usually /dev/ttyUSB0)

Backend Configuration

Backend .env (Auto-generated)

Location: backend/.env

Generated by setup script:

MQTT_USER=mqttuser
MQTT_PASS=<randomly-generated>
MQTT_URL=tcp://mqttuser:<password>@mqtt:1883

Backend .env.development

Location: backend/.env.development

APP_ENV=development
FRONTEND_BASE_URL=http://localhost:4100
OAUTH_CALLBACK_URL=http://localhost:4110/api/auth/callback
JWT_SECRET_KEY=<your-jwt-secret>
SERVICE_CLIENTS=service-a,service-b
SERVICE_SECRET_service-a=<service-a-secret>
SERVICE_SECRET_service-b=<service-b-secret>
OFFLINE_STRICT_LOCAL=true

Variables:

  • APP_ENV - Environment name: development, staging, or production
  • FRONTEND_BASE_URL - Frontend URL for CORS and redirects
  • OAUTH_CALLBACK_URL - Google OAuth callback URL (use {PORT} as placeholder)
  • JWT_SECRET_KEY - Secret key for JWT token signing (minimum 32 characters)
  • SERVICE_CLIENTS - Comma-separated list of service client IDs
  • SERVICE_SECRET_<ID> - Secret for each service client in SERVICE_CLIENTS
  • OFFLINE_STRICT_LOCAL - When true, local requests use offline auth mode

Generate strong secrets:

openssl rand -base64 32

Backend .env.production

Location: backend/.env.production

APP_ENV=production
FRONTEND_BASE_URL=http://nodeherder.local
OAUTH_CALLBACK_URL=http://nodeherder.local/api/auth/callback
GOOGLE_CLIENT_ID=<your-google-client-id>
GOOGLE_CLIENT_SECRET=<your-google-client-secret>
JWT_SECRET_KEY=<your-jwt-secret>
SERVICE_CLIENTS=service-a,service-b
SERVICE_SECRET_service-a=<service-a-secret>
SERVICE_SECRET_service-b=<service-b-secret>
OFFLINE_STRICT_LOCAL=false

Additional production variables:

  • GOOGLE_CLIENT_ID - OAuth 2.0 Client ID from Google Cloud Console
  • GOOGLE_CLIENT_SECRET - OAuth 2.0 Client Secret
  • JWT_SECRET_KEY - Secret key for JWT token signing (minimum 32 characters)
  • SERVICE_CLIENTS - Comma-separated list of service client IDs
  • SERVICE_SECRET_<ID> - Secret for each service client in SERVICE_CLIENTS

Generate strong secrets:

openssl rand -base64 32

MCP Server Integration

Node Herder implements the Model Context Protocol (MCP), allowing AI assistants (like Claude or custom LLM proxies) to directly query smart home metrics and discover devices over HTTP.

Configuration

The MCP server is managed via the Settings -> MCP Server page in the frontend. You can enable/disable it and see the connection status there.

MCP Endpoints

When --mcp is enabled, the following endpoints are available:

Endpoint Method Description
/api/mcp POST JSON-RPC endpoint for MCP requests
/api/mcp/events GET SSE stream for live resource update notifications

Testing the MCP Server

You can test the MCP server using curl commands.

1. List Resources

Discover available resources exposed by the server.

Request:

curl -X POST http://localhost:4110/api/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "resources/list"
  }'

2. Read Device Context

Fetch the current state of all devices.

Request:

curl -X POST http://localhost:4110/api/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "resources/read",
    "params": {
      "uri": "nodeherder://devices"
    }
  }'

3. Call Tools

Execute a tool (e.g., querying device metrics).

Request:

curl -X POST http://localhost:4110/api/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 4,
    "method": "tools/call",
    "params": {
      "name": "query_device",
      "arguments": {
        "target_name": "Living room presence sensor",
        "metrics": ["presence"],
        "aggregation": "last"
      }
    }
  }'

4. Subscribe to Live Updates

Listen for real-time changes via Server-Sent Events (SSE).

Step 1: Start Listening Open a terminal and run:

curl -N http://localhost:4110/api/mcp/events

Step 2: Subscribe to Resource In a separate terminal, tell the server you want updates for specific resources:

curl -X POST http://localhost:4110/api/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 5,
    "method": "resources/subscribe",
    "params": {"uri": "nodeherder://devices"}
  }'

Result: When a device updates, the SSE stream will emit a notifications/resources/updated event.

Available Capabilities

  • Resources:

    • nodeherder://devices: JSON list of all devices and their metrics. Supports live subscriptions.
    • nodeherder://system-prompt: Domain guidance for LLMs.
  • Tools:

    • query_device: Query metrics with natural language (e.g., "temperature in the kitchen").

Frontend Configuration

Frontend .env.development

Location: frontend/.env.development

VITE_API_BASE_URL=http://localhost:4110/api
VITE_WS_BASE_URL=ws://localhost:4110/ws
PORT=4100

Variables:

  • VITE_API_BASE_URL - Backend API URL (must end with /api)
  • VITE_WS_BASE_URL - WebSocket URL (must end with /ws)
  • PORT - Frontend dev server port

Frontend .env.production

Location: frontend/.env.production

VITE_API_BASE_URL=http://nodeherder.local/api
VITE_WS_BASE_URL=ws://nodeherder.local/ws

Directory Structure

After running setup script:

/opt/nodeherder/                    (DATA_ROOT location, outside repo)
├── mqtt/
│   ├── config/
│   │   ├── mosquitto.conf
│   │   └── password.txt
│   ├── data/
│   └── log/
└── zigbee2mqtt-data/
    └── configuration.yaml

/path/to/node-herder/               (repo location)
├── .env                            (Docker Compose config)
├── docker-compose.backend.yml
├── backend/
│   ├── .env                        (MQTT credentials - auto-generated)
│   ├── .env.development
│   ├── .env.production
│   ├── configs/                    (mounted in container)
│   ├── data/                       (mounted in container)
│   └── scripts/
│       └── setup-backend.sh
└── frontend/
    ├── .env.development
    └── .env.production

Manual Metrics Query

You can also query the backend metrics API directly without going through MCP. This requires authentication.

1. Get Access Token

First, obtain a JWT token using your service credentials (from .env):

export CLIENT_ID="llm-proxy"
export CLIENT_SECRET="<your-service-secret>"

export TOKEN=$(curl -s -X POST http://localhost:4110/api/auth/token \
  -H "Content-Type: application/json" \
  -d "{\"client_id\": \"$CLIENT_ID\", \"client_secret\": \"$CLIENT_SECRET\"}" | jq -r .access_token)

2. Query Metrics

Use the token to query metrics directly:

curl -X POST http://localhost:4110/api/metrics/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "deviceIds": ["0xa4c1389b273366c3"],
    "exposes": ["alarm"],
    "time": { "lookback": "1h" },
    "aggregation": "last"
  }' | jq

Development Workflow

Local Development

  1. Create root .env:

    echo "DATA_ROOT=$HOME/nodeherder-data" > .env
    echo "Z2M_DEVICE=/dev/serial/by-id/your-zigbee-dongle" >> .env
    echo "Z2M_SERIAL_PORT=/dev/ttyUSB0" >> .env
  2. Run setup:

    ./backend/scripts/setup-backend.sh
  3. Start backend services:

    docker-compose -f docker-compose.backend.yml up -d
  4. Run backend locally (optional):

    cd backend
    go run main.go -port 4110
  5. Run frontend:

    cd frontend
    npm install
    npm run dev  # Uses .env.development

Production Deployment

  1. Create root .env on server:

    DATA_ROOT=/opt/nodeherder
    Z2M_DEVICE=/dev/serial/by-id/your-zigbee-dongle
    Z2M_SERIAL_PORT=/dev/ttyUSB0
  2. Create backend/.env.production with secrets:

    APP_ENV=production
    FRONTEND_BASE_URL=https://yourdomain.com
    OAUTH_CALLBACK_URL=https://yourdomain.com/api/auth/callback
    GOOGLE_CLIENT_ID=your-client-id
    GOOGLE_CLIENT_SECRET=your-client-secret
    JWT_SECRET_KEY=your-random-32-char-secret
    SERVICE_CLIENTS=service-a,service-b
    SERVICE_SECRET_service-a=your-service-a-secret
    SERVICE_SECRET_service-b=your-service-b-secret
    OFFLINE_STRICT_LOCAL=false
  3. Run setup:

    ./backend/scripts/setup-backend.sh
  4. Build and start:

    docker-compose -f docker-compose.backend.yml up -d --build

Google OAuth Setup

  1. Go to Google Cloud Console
  2. Navigate to: APIs & Services → Credentials
  3. Create OAuth 2.0 Client ID (or edit existing)
  4. Add Authorized JavaScript origins:
    • http://localhost:4100 (development)
    • https://yourdomain.com (production)
  5. Add Authorized redirect URIs:
    • http://localhost:4110/api/auth/callback (development)
    • https://yourdomain.com/api/auth/callback (production)
  6. Copy Client ID and Client Secret to backend/.env.production

Troubleshooting

CORS Errors

Ensure FRONTEND_BASE_URL in backend matches the actual frontend URL:

  • Development: http://localhost:4100
  • Production: https://yourdomain.com (no port if using proxy)

MQTT Connection Issues

Check generated credentials:

cat backend/.env | grep MQTT

Verify MQTT is running:

docker logs nodeherder-mqtt

Zigbee2MQTT Not Starting

Check device permissions:

ls -l /dev/serial/by-id/

Ensure your user has access to the serial device:

sudo usermod -a -G dialout $USER

OAuth "invalid_request" Error

  1. Verify redirect URI is authorized in Google Cloud Console
  2. Check OAUTH_CALLBACK_URL matches the actual backend URL
  3. Ensure you're using the correct Client ID/Secret

Port Reference

Service Port Protocol Purpose
Frontend 4100 HTTP Vue.js dev server
Backend 4110 HTTP/WS Go API and WebSocket
MQTT 1883 MQTT Internal (not exposed)
Zigbee2MQTT UI 8089 HTTP Z2M frontend

Security Notes

  • Never commit .env files to git - they contain secrets
  • backend/.env is auto-generated with random MQTT password
  • Use strong JWT_SECRET_KEY (32+ characters, random)
  • For production, use HTTPS and secure WebSocket (WSS)
  • Keep GOOGLE_CLIENT_SECRET confidential
  • Regularly rotate MQTT credentials if exposed

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors