3 * MeshCentral Webhook Monitor
5 * Monitors MeshCentral for device events and sends webhooks to the backend.
6 * This is an alternative to the MeshCentral plugin approach.
9 * node meshcentral-webhook-monitor.js
11 * Environment Variables:
12 * MESHCENTRAL_URL - MeshCentral server URL
13 * MESHCENTRAL_ADMIN_USER - Admin username
14 * MESHCENTRAL_ADMIN_PASS - Admin password
15 * MESHCENTRAL_WEBHOOK_SECRET - Secret for webhook authentication
16 * BACKEND_URL - Backend API URL
19const https = require('https');
20const http = require('http');
21const MeshCentralAPI = require('../lib/meshcentral-api');
23const MESHCENTRAL_URL = process.env.MESHCENTRAL_URL || 'https://rmm-psa-meshcentral-aq48h.ondigitalocean.app';
24const MESHCENTRAL_USER = process.env.MESHCENTRAL_ADMIN_USER || 'admin';
25const MESHCENTRAL_PASS = process.env.MESHCENTRAL_ADMIN_PASS || 'admin';
26const WEBHOOK_SECRET = process.env.MESHCENTRAL_WEBHOOK_SECRET || '';
27const BACKEND_URL = process.env.BACKEND_URL || 'https://everydaytech.au';
28const WEBHOOK_PATH = '/api/webhooks/meshcentral';
30// Track device connection states
31const deviceStates = new Map();
34 * Send webhook to backend
38async function sendWebhook(event, nodeData) {
40 const webhookUrl = `${BACKEND_URL}${WEBHOOK_PATH}`;
41 const url = new URL(webhookUrl);
42 const protocol = url.protocol === 'https:' ? https : http;
44 const payload = JSON.stringify({
46 nodeId: nodeData.nodeId,
47 nodeName: nodeData.hostname,
48 hostname: nodeData.hostname,
49 connected: nodeData.connected,
50 timestamp: new Date().toISOString(),
52 platform: nodeData.platform,
53 osdesc: nodeData.osdesc,
54 netif: nodeData.netif || {},
55 mac: nodeData.macAddresses && nodeData.macAddresses.length > 0 ? nodeData.macAddresses[0] : null
60 hostname: url.hostname,
65 'Content-Type': 'application/json',
66 'Content-Length': Buffer.byteLength(payload),
67 'X-MeshCentral-Secret': WEBHOOK_SECRET
71 return new Promise((resolve, reject) => {
72 const req = protocol.request(options, (res) => {
74 res.on('data', chunk => data += chunk);
76 if (res.statusCode >= 200 && res.statusCode < 300) {
77 const responseData = JSON.parse(data || '{}');
78 if (responseData.linkResult) {
79 const result = responseData.linkResult;
81 console.log(`š [WebhookMonitor] Created new agent ${result.agentId} for ${nodeData.hostname}`);
82 } else if (result.alreadyLinked) {
83 console.log(`ā
[WebhookMonitor] Device already linked to agent ${result.agentId}`);
84 } else if (result.success) {
85 console.log(`š [WebhookMonitor] Linked agent ${result.agentId} via ${result.method}`);
87 console.log(`ā¹ļø [WebhookMonitor] No action: ${result.reason}`);
90 console.log(`š¤ [WebhookMonitor] Webhook sent: ${event} for ${nodeData.hostname}`);
92 resolve(responseData);
94 console.error(`ā [WebhookMonitor] Webhook failed: ${res.statusCode} ${data}`);
95 reject(new Error(`HTTP ${res.statusCode}`));
100 req.on('error', error => {
101 console.error('ā [WebhookMonitor] Webhook error:', error.message);
110 console.error('ā [WebhookMonitor] Error sending webhook:', error.message);
115 * Check devices and detect state changes
117async function checkDevices() {
119 const api = new MeshCentralAPI({
120 url: MESHCENTRAL_URL,
121 username: MESHCENTRAL_USER,
122 password: MESHCENTRAL_PASS
126 const nodes = await api.getNodes();
128 // console.log(`š [WebhookMonitor] Checking ${nodes.length} devices...`);
130 for (const node of nodes) {
131 const parsed = MeshCentralAPI.parseNodeData(node);
132 const nodeId = parsed.nodeId;
133 const wasConnected = deviceStates.get(nodeId);
134 const isConnected = parsed.connected;
136 // Store current state
137 deviceStates.set(nodeId, isConnected);
139 // Detect state change
140 if (wasConnected === undefined) {
141 // First time seeing this device
143 console.log(`š± [WebhookMonitor] New device detected: ${parsed.hostname} (connected)`);
144 await sendWebhook('device.connect', parsed);
146 } else if (wasConnected !== isConnected) {
147 // Connection state changed
149 console.log(`ā
[WebhookMonitor] Device connected: ${parsed.hostname}`);
150 await sendWebhook('device.connect', parsed);
152 console.log(`š“ [WebhookMonitor] Device disconnected: ${parsed.hostname}`);
153 await sendWebhook('device.disconnect', parsed);
159 console.error('ā [WebhookMonitor] Error checking devices:', error.message);
164 * Main monitoring loop
166async function startMonitoring() {
167 console.log('\nš MeshCentral Webhook Monitor Starting...');
168 console.log(` MeshCentral: ${MESHCENTRAL_URL}`);
169 console.log(` Backend: ${BACKEND_URL}`);
170 console.log(` Webhook: ${WEBHOOK_PATH}`);
171 console.log(` Secret: ${WEBHOOK_SECRET ? 'ā
Configured' : 'ā ļø Not configured'}`);
175 await checkDevices();
177 // Poll every 10 seconds
178 setInterval(async () => {
179 await checkDevices();
182 console.log('ā
[WebhookMonitor] Monitoring started (checking every 10 seconds)');
185// Handle graceful shutdown
186process.on('SIGTERM', () => {
187 console.log('\nš [WebhookMonitor] Shutting down...');
191process.on('SIGINT', () => {
192 console.log('\nš [WebhookMonitor] Shutting down...');
197startMonitoring().catch(error => {
198 console.error('ā [WebhookMonitor] Fatal error:', error);