Skip to content

ZeWinny/zelthrMonitor

Repository files navigation

Zelthr Monitor

Zelthr Monitor is a Node.js and TypeScript homelab monitoring foundation for 1 to 10 systems. Phase 1 provides the core API server, WebSocket server, MariaDB access through Prisma ORM, agent registration, agent heartbeat ingestion, basic metric ingestion, authentication, and health checks.

No Docker is required.

Requirements

  • Node.js 22 or newer.
  • npm.
  • MariaDB running locally or on your LAN.
  • A MariaDB database created for Zelthr Monitor.

Environment Files

The root .env.example and apps/core/.env.example contain the same required settings.

Copy the root example for root commands:

copy .env.example .env

Prisma workspace commands may execute with apps/core as the working directory. If Prisma cannot find DATABASE_URL, also copy the core example:

copy apps\core\.env.example apps\core\.env

Set both files to the same MariaDB URL when both are present:

DATABASE_URL=mysql://zelthr:zelthr@localhost:3306/zelthr_monitor

The Prisma datasource uses provider = "mysql", which targets MariaDB through Prisma's MySQL/MariaDB connector.

Install

npm install
npm run generate
npm run migrate

npm run migrate applies committed Prisma migrations to MariaDB. It does not require Docker.

Run

npm run dev

The core server listens on PORT from .env, defaulting to 3000.

Verify

npm run generate
npm run migrate
npm run build
npm test
npm run lint

Phase 1 Endpoints

  • GET /health
  • POST /api/v1/auth/token
  • GET /api/v1/status
  • POST /api/v1/agents/register
  • GET /api/v1/agents
  • POST /api/v1/agents/:id/heartbeat
  • POST /api/v1/metrics/:id
  • WebSocket: ws://localhost:3000/ws

Realtime WebSocket Testing

Open a WebSocket client:

npx wscat -c ws://localhost:3000/ws

The server sends an initial connection message:

{"type":"connected","service":"zelthr-core"}

Keep wscat open in one terminal, then register an agent or run the Phase 2 agent from another terminal. Realtime events are broadcast as JSON:

{"type":"agent_registered","agent_id":"...","data":{}}
{"type":"heartbeat","agent_id":"...","data":{}}
{"type":"metrics","agent_id":"...","data":{"samples":[]}}

For Phase 3 probe testing, keep wscat open and watch for probe events:

{"type":"probe","agent_id":"...","data":{"results":[]}}

Phase 2.5 Query APIs

All Phase 2.5 query endpoints require an admin bearer token.

Get a token:

$tokenResponse = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/auth/token" `
  -ContentType "application/json" `
  -Body (@{
    username = "admin"
    password = "change-me"
  } | ConvertTo-Json)

$token = $tokenResponse.data.token

Latest metric samples for all agents:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/metrics" `
  -Headers @{ Authorization = "Bearer $token" }

Recent metric samples for one agent:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/metrics/$agentId?limit=100&metric_name=cpu.usage_percent" `
  -Headers @{ Authorization = "Bearer $token" }

Newest sample for each metric name for one agent:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/metrics/$agentId/latest" `
  -Headers @{ Authorization = "Bearer $token" }

Latest heartbeat for one agent:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/agents/$agentId/heartbeat" `
  -Headers @{ Authorization = "Bearer $token" }

Online status for one agent:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/agents/$agentId/status" `
  -Headers @{ Authorization = "Bearer $token" }

Phase 3 Probe APIs

Probe ingestion uses the agent API key:

Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/probes/$agentId" `
  -Headers @{ "X-Agent-Key" = $agentApiKey } `
  -ContentType "application/json" `
  -Body (@{
    results = @(
      @{
        target = "8.8.8.8"
        reachable = $true
        latency_ms = 21.4
        packet_loss_percent = 0
        jitter_ms = 1.2
        reported_at = (Get-Date).ToUniversalTime().ToString("o")
      }
    )
  } | ConvertTo-Json -Depth 5)

Latest probe result per target for one agent:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/probes/$agentId/latest" `
  -Headers @{ Authorization = "Bearer $token" }

Recent probe history:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/probes/$agentId?limit=100" `
  -Headers @{ Authorization = "Bearer $token" }

Recent probe history for one target:

Invoke-RestMethod `
  -Method Get `
  -Uri "http://localhost:3000/api/v1/probes/$agentId?target=8.8.8.8&limit=100" `
  -Headers @{ Authorization = "Bearer $token" }

Phase 2 Agent

The Phase 2 agent runs on Windows or Linux, collects local system metrics every 15 seconds by default, sends a heartbeat to the core, and uploads metrics through the Phase 1 ingestion endpoint.

1. Register An Agent

Start the core server first:

npm run dev

Request an admin token:

$tokenResponse = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/auth/token" `
  -ContentType "application/json" `
  -Body (@{
    username = "admin"
    password = "change-me"
  } | ConvertTo-Json)

$token = $tokenResponse.data.token

Register the agent:

$agentResponse = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/agents/register" `
  -Headers @{ Authorization = "Bearer $token" } `
  -ContentType "application/json" `
  -Body (@{
    name = "local-agent"
    hostname = $env:COMPUTERNAME
    os_family = "windows"
  } | ConvertTo-Json)

$agentResponse.data.agent.id
$agentResponse.data.api_key

Save the returned agent ID and API key.

2. Configure The Agent

Copy the example:

copy apps\agent\.env.example apps\agent\.env

Set:

AGENT_SERVER_URL=http://localhost:3000
AGENT_ID=<agent id from registration>
AGENT_API_KEY=<agent api key from registration>
AGENT_REPORT_INTERVAL=15
AGENT_PROBE_TARGETS=8.8.8.8,1.1.1.1
AGENT_PROBE_INTERVAL=60
AGENT_PROBE_COUNT=3
AGENT_PROBE_TIMEOUT_MS=1000
AGENT_NAME=local-agent
AGENT_VERSION=0.1.0

3. Run The Agent

npm run dev:agent

Expected cycle logs:

[agent] heartbeat sent
[metrics] CPU 12.4% | RAM 45.1% | Disk 60.2% | Net RX 123456789 | TX 987654321
[probes] 8.8.8.8 reachable=true latency=21.4ms loss=0% jitter=1.2ms

4. Confirm Metrics Are Being Ingested

Confirm ingestion with the Phase 2.5 query APIs, by watching the agent logs for successful cycles, checking core logs for request errors, or inspecting the MariaDB heartbeats and metrics tables.

Example MariaDB checks:

select id, agent_id, status, received_at from heartbeats order by received_at desc limit 5;
select agent_id, metric_name, value, reported_at from metrics order by received_at desc limit 10;

Development Control Mode

Dev Control Mode is for local development testing only. It is disabled by default, only runs when NODE_ENV=development, and rejects non-local requests when DEV_CONTROL_LOCAL_ONLY=true. It does not run arbitrary shell commands, expose credentials, delete files, or provide remote desktop access.

Enable it in .env and restart the core server:

NODE_ENV=development
DEV_CONTROL_ENABLED=true
DEV_CONTROL_LOCAL_ONLY=true

Open:

http://localhost:3000/dev/control

Get an admin JWT token:

$tokenResponse = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/auth/token" `
  -ContentType "application/json" `
  -Body (@{
    username = "admin"
    password = "change-me"
  } | ConvertTo-Json)

$token = $tokenResponse.data.token
$token

Paste the token into the page before clicking actions. The API endpoint is:

Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/dev/control" `
  -Headers @{ Authorization = "Bearer $token" } `
  -ContentType "application/json" `
  -Body (@{ action = "system_info" } | ConvertTo-Json)

Supported actions are system_info, metrics_snapshot, collect_now, pause_agent, resume_agent, set_interval, test_heartbeat, test_metrics, and test_ws_event.

Disable it by setting:

DEV_CONTROL_ENABLED=false

Then restart the core server.

Development Test Viewers

These are temporary development-only pages, not a dashboard. They are served only when NODE_ENV=development. They require you to paste an admin JWT and agent ID, then they call the authenticated APIs from the browser. They do not bypass authentication and do not expose secrets.

Open the probe test viewer:

http://localhost:3000/dev/probes

Open the combined monitor viewer:

http://localhost:3000/dev/monitor

Get a JWT token with the auth command in the previous section. Get an agent ID from registration:

$agentResponse = Invoke-RestMethod `
  -Method Post `
  -Uri "http://localhost:3000/api/v1/agents/register" `
  -Headers @{ Authorization = "Bearer $token" } `
  -ContentType "application/json" `
  -Body (@{
    name = "local-agent"
    hostname = $env:COMPUTERNAME
    os_family = "windows"
  } | ConvertTo-Json)

$agentId = $agentResponse.data.agent.id
$agentId

Paste the JWT token and agent ID into /dev/probes or /dev/monitor. The probe viewer refreshes GET /api/v1/probes/:agentId/latest every 5 seconds and listens for realtime probe events.

Verify realtime events with wscat:

npx wscat -c ws://localhost:3000/ws

Phase 1 Scope

Included:

  • Core Express server.
  • WebSocket server.
  • MariaDB + Prisma connection.
  • Prisma migrations.
  • Authentication endpoint.
  • Agent registration endpoint.
  • Agent heartbeat endpoint.
  • Basic metrics ingestion endpoint.
  • Health check endpoint.
  • Zod validation.
  • Consistent JSON error handling.

Not included:

  • Dashboard.
  • Discord monitoring.
  • Alerts.
  • Prometheus.
  • Docker.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors