EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
MeshAgent Auto-Installation and Linking

Overview

This document describes how to automatically install MeshAgent alongside our RMM agent and link them together.

Architecture

EverydayTech Agent v2 → Installs MeshAgent → MeshCentral Server
↓ ↓ ↓
agent_uuid Hardware UUID meshcentral_nodeid
↓ ↓ ↓
└──────────── Links in Database ───────────┘

Matching Strategy

Primary Method: Hardware UUID

Both our agent and MeshAgent can access the same hardware identifiers:

  • Windows: Use WMI to get motherboard UUID (wmic csproduct get UUID)
  • Linux: Read /sys/class/dmi/id/product_uuid or /etc/machine-id
  • macOS: Use system_profiler SPHardwareDataType | grep UUID

Fallback Method: Hostname + IP

If hardware UUID is unavailable, match on:

  • Hostname
  • Primary IP address
  • OS platform

Implementation Steps

1. Agent v2: Add MeshAgent Installer

Add to /rmm-psa-agent-v2/lib/meshagent-installer.js:

const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const https = require('https');
class MeshAgentInstaller {
constructor(config) {
this.meshServerUrl = config.meshServerUrl || 'https://rmm-psa-meshcentral-aq48h.ondigitalocean.app';
this.meshId = config.meshId; // Get from backend
this.tenantId = config.tenantId;
}
async install() {
console.log('🔧 Installing MeshAgent...');
try {
// 1. Check if already installed
if (await this.isInstalled()) {
console.log('✅ MeshAgent already installed');
return true;
}
// 2. Get hardware ID to pass to MeshAgent
const hardwareId = await this.getHardwareId();
console.log(`Hardware ID: ${hardwareId}`);
// 3. Download installer
const installerPath = await this.downloadInstaller();
// 4. Install silently
await this.runInstaller(installerPath);
// 5. Wait for agent to connect
await this.waitForConnection();
// 6. Report back to backend
await this.reportInstallation(hardwareId);
console.log('✅ MeshAgent installed successfully');
return true;
} catch (error) {
console.error('❌ MeshAgent installation failed:', error);
return false;
}
}
async isInstalled() {
// Check for MeshAgent service/process
const platform = process.platform;
if (platform === 'win32') {
return new Promise((resolve) => {
exec('sc query "MeshAgent"', (error, stdout) => {
resolve(!error && stdout.includes('RUNNING'));
});
});
} else if (platform === 'linux') {
return fs.existsSync('/usr/local/mesh');
} else if (platform === 'darwin') {
return fs.existsSync('/usr/local/mesh_services');
}
return false;
}
async getHardwareId() {
const platform = process.platform;
return new Promise((resolve, reject) => {
let cmd;
if (platform === 'win32') {
cmd = 'wmic csproduct get UUID';
} else if (platform === 'linux') {
cmd = 'cat /etc/machine-id || cat /sys/class/dmi/id/product_uuid';
} else if (platform === 'darwin') {
cmd = 'system_profiler SPHardwareDataType | grep "Hardware UUID"';
}
exec(cmd, (error, stdout) => {
if (error) {
reject(error);
} else {
const uuid = stdout.trim().split('\n').pop().trim();
resolve(uuid);
}
});
});
}
async downloadInstaller() {
const platform = process.platform;
const ext = platform === 'win32' ? '.exe' : '';
const installerUrl = `${this.meshServerUrl}/meshagents?id=${this.meshId}`;
const installerPath = path.join(process.env.TEMP || '/tmp', `meshagent-installer${ext}`);
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(installerPath);
https.get(installerUrl, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
resolve(installerPath);
});
}).on('error', (err) => {
fs.unlink(installerPath, () => {});
reject(err);
});
});
}
async runInstaller(installerPath) {
const platform = process.platform;
return new Promise((resolve, reject) => {
let cmd;
if (platform === 'win32') {
cmd = `"${installerPath}" /S`;
} else {
cmd = `chmod +x "${installerPath}" && "${installerPath}" install`;
}
exec(cmd, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
}
async waitForConnection(timeout = 30000) {
// Poll backend to check if MeshAgent connected
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
await new Promise(resolve => setTimeout(resolve, 2000));
// Check with backend if MeshAgent is connected
// (Backend will have received webhook or we'll poll MeshCentral API)
const connected = await this.checkConnection();
if (connected) return true;
}
throw new Error('MeshAgent failed to connect within timeout');
}
async checkConnection() {
// Call backend API to check if MeshAgent connected
// Backend will match by hardware ID
return false; // TODO: implement
}
async reportInstallation(hardwareId) {
// Report to backend that we installed MeshAgent
// Backend will link the two agents
console.log('📡 Reporting MeshAgent installation to backend...');
// TODO: implement API call
}
}
module.exports = MeshAgentInstaller;

2. Backend: Add Auto-Link Endpoint

Add to /rmm-psa-backend/routes/meshcentral.js:

/**
* Auto-link MeshAgent with our agent by hardware ID
*/
router.post('/auto-link', authenticateToken, async (req, res) => {
try {
const { agentUuid, hardwareId } = req.body;
if (!agentUuid || !hardwareId) {
return res.status(400).json({ error: 'agentUuid and hardwareId required' });
}
// Get our agent
const agentResult = await db.query(
'SELECT agent_id, hostname FROM agents WHERE agent_uuid = $1',
[agentUuid]
);
if (agentResult.rows.length === 0) {
return res.status(404).json({ error: 'Agent not found' });
}
const agent = agentResult.rows[0];
// Get all MeshCentral nodes
const api = await getMeshAPI();
const nodes = await api.getNodes();
// Find matching node by hardware ID, hostname, or IP
let matchedNode = null;
for (const node of nodes) {
const parsed = MeshCentralAPI.parseNodeData(node);
// Try to match by hardware ID (if MeshAgent reports it)
// Or match by hostname
if (parsed.hostname?.toLowerCase() === agent.hostname?.toLowerCase()) {
matchedNode = parsed;
break;
}
}
if (!matchedNode) {
return res.status(404).json({
error: 'No matching MeshCentral node found',
message: 'Please ensure MeshAgent is installed and connected'
});
}
// Link the two agents
await db.query(
'UPDATE agents SET meshcentral_nodeid = $1 WHERE agent_uuid = $2',
[matchedNode.nodeId, agentUuid]
);
res.json({
success: true,
agentId: agent.agent_id,
agentUuid: agentUuid,
meshcentralNodeId: matchedNode.nodeId,
matchMethod: 'hostname',
message: 'Agents linked successfully'
});
} catch (error) {
console.error('Error auto-linking agents:', error);
res.status(500).json({ error: 'Failed to auto-link', message: error.message });
}
});

3. Backend: Add Periodic Sync Job

Create /rmm-psa-backend/jobs/meshcentral-sync.js:

const cron = require('node-cron');
const db = require('../db');
const MeshCentralAPI = require('../lib/meshcentral-api');
// Run every 5 minutes
cron.schedule('*/5 * * * *', async () => {
console.log('🔄 Running MeshCentral auto-link job...');
try {
// Get all agents without MeshCentral node ID
const result = await db.query(`
SELECT agent_id, agent_uuid, hostname, hardware_id
FROM agents
WHERE meshcentral_nodeid IS NULL
AND last_seen > NOW() - INTERVAL '1 hour'
`);
if (result.rows.length === 0) {
console.log('✅ All active agents are linked');
return;
}
// Get MeshCentral nodes
const api = new MeshCentralAPI(
process.env.MESHCENTRAL_URL,
process.env.MESHCENTRAL_USERNAME,
process.env.MESHCENTRAL_PASSWORD
);
await api.login();
const nodes = await api.getNodes();
let linkedCount = 0;
for (const agent of result.rows) {
// Try to find matching node
const matchedNode = nodes.find(node => {
const parsed = MeshCentralAPI.parseNodeData(node);
return parsed.hostname?.toLowerCase() === agent.hostname?.toLowerCase();
});
if (matchedNode) {
const parsed = MeshCentralAPI.parseNodeData(matchedNode);
await db.query(
'UPDATE agents SET meshcentral_nodeid = $1 WHERE agent_uuid = $2',
[parsed.nodeId, agent.agent_uuid]
);
linkedCount++;
console.log(`✅ Linked agent ${agent.agent_uuid} to MeshCentral node ${parsed.nodeId}`);
}
}
console.log(`✅ Auto-linked ${linkedCount} agents`);
} catch (error) {
console.error('❌ MeshCentral auto-link job failed:', error);
}
});

Deployment Checklist

  • Add MeshAgentInstaller to agent v2
  • Call meshAgentInstaller.install() after agent registration
  • Add /meshcentral/auto-link endpoint to backend
  • Add periodic sync job (every 5 minutes)
  • Update agent v2 to report hardware ID to backend
  • Test on Windows VM
  • Test on Linux VM
  • Add logging and monitoring

Testing

# 1. Install agent v2
./EverydayTechAgent-v2.exe --install
# 2. Check if MeshAgent installed
sc query "MeshAgent" # Windows
systemctl status meshagent # Linux
# 3. Check backend logs for auto-link
tail -f /var/log/rmm-psa-backend.log | grep "auto-link"
# 4. Query database to verify link
psql -c "SELECT agent_uuid, hostname, meshcentral_nodeid FROM agents WHERE agent_id = 115;"

Current Status

  • ✅ MeshCentral deployed and working
  • ✅ Backend API can query MeshCentral devices
  • ✅ Manual linking working (agent 115 linked)
  • ⏳ Auto-installer not yet implemented
  • ⏳ Auto-link endpoint not yet implemented
  • ⏳ Periodic sync job not yet implemented

Next Steps

  1. Implement MeshAgentInstaller class in agent v2
  2. Add auto-link endpoint to backend
  3. Set up periodic sync job
  4. Test end-to-end on fresh VM