EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
meshcentral-webhook-monitor.js
Go to the documentation of this file.
1#!/usr/bin/env node
2/**
3 * MeshCentral Webhook Monitor
4 *
5 * Monitors MeshCentral for device events and sends webhooks to the backend.
6 * This is an alternative to the MeshCentral plugin approach.
7 *
8 * Usage:
9 * node meshcentral-webhook-monitor.js
10 *
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
17 */
18
19const https = require('https');
20const http = require('http');
21const MeshCentralAPI = require('../lib/meshcentral-api');
22
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';
29
30// Track device connection states
31const deviceStates = new Map();
32
33/**
34 * Send webhook to backend
35 * @param event
36 * @param nodeData
37 */
38async function sendWebhook(event, nodeData) {
39 try {
40 const webhookUrl = `${BACKEND_URL}${WEBHOOK_PATH}`;
41 const url = new URL(webhookUrl);
42 const protocol = url.protocol === 'https:' ? https : http;
43
44 const payload = JSON.stringify({
45 event: event,
46 nodeId: nodeData.nodeId,
47 nodeName: nodeData.hostname,
48 hostname: nodeData.hostname,
49 connected: nodeData.connected,
50 timestamp: new Date().toISOString(),
51 node: {
52 platform: nodeData.platform,
53 osdesc: nodeData.osdesc,
54 netif: nodeData.netif || {},
55 mac: nodeData.macAddresses && nodeData.macAddresses.length > 0 ? nodeData.macAddresses[0] : null
56 }
57 });
58
59 const options = {
60 hostname: url.hostname,
61 port: url.port,
62 path: url.pathname,
63 method: 'POST',
64 headers: {
65 'Content-Type': 'application/json',
66 'Content-Length': Buffer.byteLength(payload),
67 'X-MeshCentral-Secret': WEBHOOK_SECRET
68 }
69 };
70
71 return new Promise((resolve, reject) => {
72 const req = protocol.request(options, (res) => {
73 let data = '';
74 res.on('data', chunk => data += chunk);
75 res.on('end', () => {
76 if (res.statusCode >= 200 && res.statusCode < 300) {
77 const responseData = JSON.parse(data || '{}');
78 if (responseData.linkResult) {
79 const result = responseData.linkResult;
80 if (result.created) {
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}`);
86 } else {
87 console.log(`ā„¹ļø [WebhookMonitor] No action: ${result.reason}`);
88 }
89 } else {
90 console.log(`šŸ“¤ [WebhookMonitor] Webhook sent: ${event} for ${nodeData.hostname}`);
91 }
92 resolve(responseData);
93 } else {
94 console.error(`āŒ [WebhookMonitor] Webhook failed: ${res.statusCode} ${data}`);
95 reject(new Error(`HTTP ${res.statusCode}`));
96 }
97 });
98 });
99
100 req.on('error', error => {
101 console.error('āŒ [WebhookMonitor] Webhook error:', error.message);
102 reject(error);
103 });
104
105 req.write(payload);
106 req.end();
107 });
108
109 } catch (error) {
110 console.error('āŒ [WebhookMonitor] Error sending webhook:', error.message);
111 }
112}
113
114/**
115 * Check devices and detect state changes
116 */
117async function checkDevices() {
118 try {
119 const api = new MeshCentralAPI({
120 url: MESHCENTRAL_URL,
121 username: MESHCENTRAL_USER,
122 password: MESHCENTRAL_PASS
123 });
124
125 await api.login();
126 const nodes = await api.getNodes();
127
128 // console.log(`šŸ” [WebhookMonitor] Checking ${nodes.length} devices...`);
129
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;
135
136 // Store current state
137 deviceStates.set(nodeId, isConnected);
138
139 // Detect state change
140 if (wasConnected === undefined) {
141 // First time seeing this device
142 if (isConnected) {
143 console.log(`šŸ“± [WebhookMonitor] New device detected: ${parsed.hostname} (connected)`);
144 await sendWebhook('device.connect', parsed);
145 }
146 } else if (wasConnected !== isConnected) {
147 // Connection state changed
148 if (isConnected) {
149 console.log(`āœ… [WebhookMonitor] Device connected: ${parsed.hostname}`);
150 await sendWebhook('device.connect', parsed);
151 } else {
152 console.log(`šŸ““ [WebhookMonitor] Device disconnected: ${parsed.hostname}`);
153 await sendWebhook('device.disconnect', parsed);
154 }
155 }
156 }
157
158 } catch (error) {
159 console.error('āŒ [WebhookMonitor] Error checking devices:', error.message);
160 }
161}
162
163/**
164 * Main monitoring loop
165 */
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'}`);
172 console.log('');
173
174 // Initial check
175 await checkDevices();
176
177 // Poll every 10 seconds
178 setInterval(async () => {
179 await checkDevices();
180 }, 10000);
181
182 console.log('āœ… [WebhookMonitor] Monitoring started (checking every 10 seconds)');
183}
184
185// Handle graceful shutdown
186process.on('SIGTERM', () => {
187 console.log('\nšŸ‘‹ [WebhookMonitor] Shutting down...');
188 process.exit(0);
189});
190
191process.on('SIGINT', () => {
192 console.log('\nšŸ‘‹ [WebhookMonitor] Shutting down...');
193 process.exit(0);
194});
195
196// Start monitoring
197startMonitoring().catch(error => {
198 console.error('āŒ [WebhookMonitor] Fatal error:', error);
199 process.exit(1);
200});