2 * Startup Tenant-MeshCentral Sync
4 * Automatically runs on backend startup to ensure all tenants
5 * have device groups in MeshCentral. Now that MeshCentral uses
6 * PostgreSQL, mesh groups persist across restarts.
9 * 1. Fetch all tenants from PostgreSQL database
10 * 2. Fetch all existing mesh groups from MeshCentral
11 * 3. Match tenants to mesh groups by ID or name
12 * 4. Create missing mesh groups for any unmatched tenants
13 * 5. Tag all devices in each mesh with tenant UUID
16const db = require('../db');
17const MeshCentralAPI = require('../lib/meshcentral-api');
19const MESHCENTRAL_URL = process.env.MESHCENTRAL_URL || 'https://rmm-psa-meshcentral-aq48h.ondigitalocean.app';
20const MESHCENTRAL_USER = process.env.MESHCENTRAL_ADMIN_USER || 'admin';
21const MESHCENTRAL_PASSWORD = process.env.MESHCENTRAL_ADMIN_PASS || 'admin';
23// Delay before running sync (to let MeshCentral start up)
24const STARTUP_DELAY = parseInt(process.env.TENANT_SYNC_DELAY || '30000'); // 30 seconds
27 * Ensure tenant has a device group in MeshCentral
28 * Matches existing groups by ID or name, creates new ones if needed
31 * @param existingMeshes
33async function ensureTenantMesh(api, tenant, existingMeshes) {
35 const expectedMeshName = `${tenant.name} Devices`;
37 // Check if tenant already has a mesh ID
38 if (tenant.meshcentral_group_id) {
39 // Verify it still exists in MeshCentral
40 const meshExists = existingMeshes.some(m => m._id === tenant.meshcentral_group_id);
43 console.log(` ā
Tenant "${tenant.name}" has mesh: ${tenant.meshcentral_group_id}`);
44 await tagDevicesInMesh(api, tenant);
45 return { meshId: tenant.meshcentral_group_id, created: false, linked: false };
47 console.log(` ā ļø Tenant "${tenant.name}" mesh ${tenant.meshcentral_group_id} not found`);
48 // Continue to try finding by name or create new
52 // Try to find existing mesh by name
53 const matchingMesh = existingMeshes.find(m => {
54 const meshName = m.name || '';
55 return meshName === expectedMeshName ||
56 meshName === tenant.name ||
57 meshName.includes(tenant.name);
61 console.log(` š Found existing mesh "${matchingMesh.name}" - linking to tenant "${tenant.name}"`);
63 // Link this mesh to the tenant
65 'UPDATE tenants SET meshcentral_group_id = $1, meshcentral_setup_complete = true WHERE tenant_id = $2',
66 [matchingMesh._id, tenant.tenant_id]
69 console.log(` ā
Linked mesh ${matchingMesh._id} to tenant "${tenant.name}"`);
70 await tagDevicesInMesh(api, { ...tenant, meshcentral_group_id: matchingMesh._id });
72 return { meshId: matchingMesh._id, created: false, linked: true };
75 // No existing mesh found - create new one
76 console.log(` š¦ Creating new mesh: ${expectedMeshName}`);
78 const result = await api.createMesh(expectedMeshName, `Device group for ${tenant.name} (tenant:${tenant.tenant_id})`);
80 if (!result || !result.meshId) {
81 throw new Error('Failed to create mesh in MeshCentral');
84 // Store mesh ID in database
86 'UPDATE tenants SET meshcentral_group_id = $1, meshcentral_setup_complete = true WHERE tenant_id = $2',
87 [result.meshId, tenant.tenant_id]
90 console.log(` ā
Created mesh ${result.meshId} for "${tenant.name}"`);
92 return { meshId: result.meshId, created: true, linked: false };
95 console.error(` ā Error for tenant "${tenant.name}":`, error.message);
101 * Tag all devices in a mesh with tenant UUID
105async function tagDevicesInMesh(api, tenant) {
107 const nodes = await api.getNodes();
108 const meshNodes = nodes.filter(n => n.meshid === tenant.meshcentral_group_id);
110 if (meshNodes.length === 0) {
111 console.log(` ā¹ļø No devices in mesh for tenant "${tenant.name}"`);
115 console.log(` š·ļø Tagging ${meshNodes.length} device(s) for tenant "${tenant.name}"`);
117 for (const node of meshNodes) {
118 const parsed = api.constructor.parseNodeData(node);
119 const tagData = api.constructor.parseDeviceTags(node.tags);
121 // Only update if tenant tag is missing or different
122 if (tagData.tenantId !== tenant.tenant_id) {
123 console.log(` āā ${parsed.hostname} ā tenant:${tenant.tenant_id}`);
124 await api.updateDeviceMetadata(parsed.nodeId, {
125 tenantId: tenant.tenant_id,
126 customerId: tagData.customerId,
127 tags: tagData.customTags
132 console.error(` ā ļø Error tagging devices for tenant "${tenant.name}":`, error.message);
137 * Run the startup sync
139async function runStartupSync() {
140 console.log(`\n${'='.repeat(60)}`);
141 console.log('š TENANT-MESHCENTRAL STARTUP SYNC');
142 console.log(`${'='.repeat(60)}\n`);
145 // Wait for MeshCentral to be ready
146 console.log(`ā³ Waiting ${STARTUP_DELAY}ms for MeshCentral to start...`);
147 await new Promise(resolve => setTimeout(resolve, STARTUP_DELAY));
150 const tenantsResult = await db.query(
151 'SELECT tenant_id, name, meshcentral_group_id, meshcentral_setup_complete FROM tenants ORDER BY created_at'
154 if (tenantsResult.rows.length === 0) {
155 console.log('ā¹ļø No tenants found, skipping sync\n');
159 console.log(`š Found ${tenantsResult.rows.length} tenants to sync\n`);
161 // Connect to MeshCentral
162 console.log(`š Connecting to MeshCentral: ${MESHCENTRAL_URL}`);
163 const api = new MeshCentralAPI({
164 url: MESHCENTRAL_URL,
165 username: MESHCENTRAL_USER,
166 password: MESHCENTRAL_PASSWORD
170 console.log(`ā
Connected to MeshCentral as ${MESHCENTRAL_USER}\n`);
172 // Fetch all existing mesh groups
173 console.log(`š¦ Fetching existing mesh groups from MeshCentral...`);
174 const existingMeshes = await api.getMeshes();
175 console.log(`ā
Found ${existingMeshes.length} existing mesh group(s)\n`);
177 // Process each tenant
183 for (const tenant of tenantsResult.rows) {
184 console.log(`\nš Processing tenant: ${tenant.name} (${tenant.tenant_id})`);
186 const result = await ensureTenantMesh(api, tenant, existingMeshes);
187 if (result.created) {
189 } else if (result.linked) {
199 console.log(`\n${'='.repeat(60)}`);
200 console.log('ā
STARTUP SYNC COMPLETE');
201 console.log(`${'='.repeat(60)}`);
202 console.log(`š Results:`);
203 console.log(` - Created: ${created} new mesh group(s)`);
204 console.log(` - Linked: ${linked} existing mesh group(s)`);
205 console.log(` - Already synced: ${existing} mesh group(s)`);
206 console.log(` - Failed: ${failed} tenant(s)`);
207 console.log(`${'='.repeat(60)}\n`);
210 console.error('\nā STARTUP SYNC FAILED:', error.message);
211 console.error('Stack:', error.stack);
212 console.log('\nā¹ļø Note: This is non-fatal. You can manually trigger sync via:');
213 console.log(' POST /api/tenant-meshcentral-sync/sync-all\n');
218 * Initialize startup sync if enabled
220function initStartupSync() {
221 const enabled = process.env.TENANT_SYNC_ON_STARTUP === 'true'; // Disabled by default (changed to prevent duplicates)
224 console.log('ā¹ļø Tenant-MeshCentral startup sync is disabled (set TENANT_SYNC_ON_STARTUP=true to enable)');
228 // Run in background (don't block server startup)
230 runStartupSync().catch(err => {
231 console.error('ā Startup sync error:', err);