1// ======================================================================
2// Backend API Entry — Digital Ocean App Platform Version
3// v1.0.3 - Add manual sync API endpoint
4// ======================================================================
6const express = require("express");
7const cors = require("cors");
8const cookieParser = require("cookie-parser");
9const path = require("path");
10require("dotenv").config();
15// ----------------------------------------------------
16const allowedOrigins = [
17 "http://localhost:5173",
18 "http://localhost:5174",
19 "http://192.168.0.188:5173",
20 "http://192.168.122.1:5173",
21 "https://demo.everydayoffice.au",
22 "https://everydayoffice.au",
23 "https://everydaytech.au",
24 "https://www.everydaytech.au",
25 "https://dashboard.everydaytech.au",
26 "https://api.everydaytech.au",
27 "https://rmm.everydaytech.au",
28 "https://psa.everydaytech.au",
29 "https://rmm-psa-dashboard-5jyun.ondigitalocean.app",
30 "https://rmm-psa-backend-t9f7k.ondigitalocean.app",
34 origin: (origin, callback) => {
35 // Allow requests with no origin (mobile apps, curl, etc.)
36 if (!origin) return callback(null, true);
38 // Check if origin is in allowed list
39 if (allowedOrigins.includes(origin)) return callback(null, true);
41 // Allow any subdomain of everydaytech.au (safe regex test)
43 if (/^https:\/\/([a-z0-9-]+\.)?everydaytech\.au$/i.test(origin)) {
44 return callback(null, true);
47 // Allow any subdomain of everydayoffice.au
48 if (/^https:\/\/([a-z0-9-]+\.)?everydayoffice\.au$/i.test(origin)) {
49 return callback(null, true);
52 console.error('CORS regex error:', err);
55 console.warn(`CORS blocked origin: ${origin}`);
56 return callback(new Error("Not allowed by CORS"));
61app.use(cookieParser());
62app.use(express.json());
64// =========================================
66// =========================================
67app.get("/", (req, res) => {
69 name: "RMM PSA Backend API",
73 health: "/api/health",
74 docs: "https://github.com/Independent-Business-Group/rmm-psa-backend"
79app.get("/api", (req, res) => {
81 message: "RMM PSA API",
93// =========================================
94// PUBLIC BUILDER REDIRECTS (DEPRECATED - KEPT FOR BACKWARD COMPATIBILITY)
95// =========================================
96// Old agents may still reference these URLs
97// Redirect to download status page
98app.get("/builder/dl/:os", (req, res) => {
99 const job = req.query.job;
100 if (!job) return res.status(410).json({ error: "Builder service discontinued. Agent v1 no longer supported. Please use Agent v3 (MeshCentral)." });
101 return res.status(410).json({ error: "Builder service discontinued. Agent v1 no longer supported. Please use Agent v3 (MeshCentral)." });
104app.get("/builder/status", (req, res) => {
105 const job = req.query.job;
106 if (!job) return res.status(410).json({ error: "Builder service discontinued. Agent v1 no longer supported. Please use Agent v3 (MeshCentral)." });
107 return res.status(410).json({ error: "Builder service discontinued. Agent v1 no longer supported. Please use Agent v3 (MeshCentral)." });
111// ----------------------------------------------------
113// ----------------------------------------------------
115 const { ensureAuthExtras } = require("./services/initAuth");
116 ensureAuthExtras().catch(e => console.error("Auth bootstrap failed:", e));
118 console.error("initAuth load failed:", e);
122// ----------------------------------------------------
124// ----------------------------------------------------
125const authenticateToken = require("./middleware/auth");
126const { setTenantContext } = require("./middleware/tenant");
129// ----------------------------------------------------
136function debugRoute(path, mod) {
137 const type = typeof mod;
138 const isRouter = mod && mod.stack && Array.isArray(mod.stack);
139 console.log(`[DEBUG] Mounting ${path}: type=${type}, isRouter=${isRouter}`);
143 ["/api", require("./routes/ai")],
144 ["/api", require("./routes/downloadJob")],
145 ["/api/download", require("./routes/download")],
146 ["/api/users", require("./routes/users")],
147 ["/api/tickets", require("./routes/tickets")],
148 ["/api/customers", require("./routes/customers")],
149 ["/api/invoices", require("./routes/invoices")],
150 ["/api/invoices", require("./routes/invoice-payments")], // Stripe payment processing for invoices
151 ["/api/invoices", require("./routes/invoice-pdf")], // PDF generation for invoices
152 ["/api/invoices", require("./routes/refunds")], // Invoice refunds (tenant admin only)
153 ["/api/public", require("./routes/public-invoice-payment")], // Public invoice payment (no auth - token-based)
154 ["/api/public/pay", require("./routes/invoice-pdf")], // Public invoice PDF (no auth)
155 ["/api/stripe/webhook", require("./routes/stripe-webhook")], // Stripe webhooks (no auth - signature verified)
156 ["/api/stripe", require("./routes/stripe-connect")], // Stripe Connect dashboard integration (authenticated)
157 ["/api/notifications", require("./routes/notifications")],
158 ["/api/slack", require("./routes/slack")],
159 ["/api/documents", require("./routes/documents")],
160 ["/api/monitoring", require("./routes/monitoring")],
161 ["/api/reports", require("./routes/reports")],
162 ["/api/settings", require("./routes/settings")],
163 ["/api/auth", require("./routes/auth")],
164 ["/api/products", require("./routes/products")],
165 ["/api/purchase-orders", require("./routes/purchaseOrders")],
166 ["/api/contracts", require("./routes/contracts")],
167 ["/api/domains", require("./routes/domains")],
168 ["/api/domain-requests", require("./routes/domainRequests")],
169 ["/api/cloudflare-dns", require("./routes/cloudflare-dns")], // Cloudflare DNS Management
170 ["/api/office365", require("./routes/office365")], // Office 365 / Pax8 Integration
171 ["/api/hosting", require("./routes/hosting")], // DigitalOcean Hosting Management
172 ["/api/wordpress", require("./routes/wordpress")], // WordPress Site Creation
173 ["/api/customers", require("./routes/customerMerge")], // Customer Merge Tool
174 ["/api/dashboard", require("./routes/dashboard")],
175 ["/api/tenants", require("./routes/tenants")],
176 ["/api/tenants", require("./routes/tenantIcon")],
177 ["/api/integrations", require("./routes/integrations")],
178 // ["/api/plugins", require("./routes/plugins").router], // Disabled: requires agent directory not in backend-only deployment
179 ["/api/rds", require("./routes/rds")],
180 ["/api/scripts", require("./routes/scripts")],
181 ["/api/email", require("./routes/emailAutomation")],
182 ["/api/email", require("./routes/emailTracking")], // Email tracking pixel and analytics
183 ["/api/orders", require("./routes/orders")],
184 ["/api/github", require("./routes/github-webhook").router],
185 // Agent routes (grouped) - order matters: specific routes before general ones
186 ["/api/agent/v2", require("./routes/agentV2")],
187 ["/api/agent", require("./routes/agentVersion")],
188 ["/api/agent", require("./routes/agentManifest")],
189 ["/api/agent", require("./routes/agent")],
190 ["/api/agent/config", require("./routes/config")],
191 // ["/api/agent/plugins", require("./routes/plugins").router], // Disabled: requires agent directory not in backend-only deployment
192 ["/api/agent/commands", require("./routes/commands")],
193 ["/api/agent/contracts", require("./routes/contracts")],
194 ["/api/agent/cert", require("./routes/agentCert")],
195 ["/api/agent/update", require("./routes/agentUpdate")],
196 ["/api/agent/files", require("./routes/agentFiles")],
197 // ["/api/releases", require("./routes/agent-download")], // DEPRECATED: Agent v2 downloads directly from GitHub (public repo)
199for (const [path, mod] of routes) {
200 debugRoute(path, mod);
203// ...existing code...
206// ----------------------------------------------------
207// BUILDER LAYER (Correct Placement + Crash Protection)
208// ----------------------------------------------------
209// DEPRECATED: Builder service no longer used (Agent v1 discontinued)
210// Agent v2 is deprecated, Agent v3 uses MeshCentral's native agent
211// Kept for backward compatibility only
213 const downloadJobRoute = require("./routes/downloadJob");
214 app.use("/api", downloadJobRoute);
215 console.log("⚠️ Builder downloadJob route loaded (DEPRECATED - Agent v1 discontinued)");
217 console.error("❌ Failed loading /routes/downloadJob:", e);
221 const builderRoutes = require("./builder/builderRoutes");
222 app.use("/api/builder", builderRoutes);
223 console.log("⚠️ Builder routes loaded (DEPRECATED - Agent v1 discontinued)");
225 console.error("❌ Failed loading builderRoutes:", e);
229// ----------------------------------------------------
231// ----------------------------------------------------
232const { xeroRouter } = require('./plugins/xero');
233app.use('/api/xero', xeroRouter);
235// ----------------------------------------------------
236// REMOTE ACCESS ROUTING
237// ----------------------------------------------------
238// Note: Guacamole and RustDesk routes removed - MeshCentral-only architecture
240// ----------------------------------------------------
242// ----------------------------------------------------
244 const meshcentralRoutes = require('./routes/meshcentral');
245 app.use('/api/meshcentral', meshcentralRoutes);
246 console.log('✅ MeshCentral routes loaded');
248 console.error('❌ Failed loading meshcentral routes:', e.message);
249 console.error('Stack:', e.stack);
252// ----------------------------------------------------
253// HEALTH CHECK ROUTES (llama.cpp, Redis, DB)
254// ----------------------------------------------------
256 const healthRoutes = require('./routes/health');
257 app.use('/api', healthRoutes);
258 console.log('✅ Health check routes loaded');
260 console.error('❌ Failed loading health routes:', e);
263// ----------------------------------------------------
264// WEBHOOK ROUTES (MeshCentral events, etc.)
265// ----------------------------------------------------
267 const webhookRoutes = require('./routes/webhooks');
268 app.use('/api/webhooks', webhookRoutes);
269 console.log('✅ Webhook routes loaded');
271 console.error('❌ Failed loading webhook routes:', e);
274// ----------------------------------------------------
275// SYNC ROUTES (Manual sync triggers)
276// ----------------------------------------------------
278 const syncRoutes = require('./routes/sync');
279 app.use('/api/sync', syncRoutes);
280 console.log('✅ Sync routes loaded');
282 console.error('❌ Failed loading sync routes:', e);
285// ----------------------------------------------------
286// AGENT V3 ROUTES (MeshCentral's native agent - ACTIVE)
287// ----------------------------------------------------
288// Agent v3 = MeshCentral's white-labeled agent (no custom builds)
289// Provides download endpoints for Windows/Linux/macOS installers
291 const agentV3Routes = require('./routes/agent-v3');
292 app.use('/api/agent-v3', agentV3Routes);
293 console.log('✅ Agent v3 routes loaded (MeshCentral native agent)');
295 console.error('❌ Failed loading agent-v3 routes:', e);
298// ----------------------------------------------------
299// TENANT MESHCENTRAL SYNC (Auto-create device groups)
300// ----------------------------------------------------
302 const tenantSyncRoutes = require('./routes/tenant-meshcentral-sync');
303 app.use('/api/tenant-meshcentral-sync', tenantSyncRoutes);
304 console.log('✅ Tenant MeshCentral sync routes loaded');
306 console.error('❌ Failed loading tenant-meshcentral-sync routes:', e);
309// ----------------------------------------------------
310// DEBUG ENDPOINTS (Development/troubleshooting)
311// ----------------------------------------------------
313 const debugRoutes = require('./routes/debug');
314 app.use('/api/debug', debugRoutes);
315 console.log('✅ Debug routes loaded');
317 console.error('❌ Failed loading debug routes:', e);
320// ----------------------------------------------------
321// CUSTOMER PORTAL ROUTES (Customer self-service)
322// ----------------------------------------------------
324 const portalAuthRoutes = require('./routes/portalAuth');
325 app.use('/api/portal/auth', portalAuthRoutes);
326 console.log('✅ Customer portal auth routes loaded');
328 console.error('❌ Failed loading portal auth routes:', e);
332 const portalCustomerRoutes = require('./routes/portalCustomer');
333 app.use('/api/portal/customer', portalCustomerRoutes);
334 console.log('✅ Customer portal data routes loaded');
336 console.error('❌ Failed loading portal customer routes:', e);
339// ----------------------------------------------------
340// CUSTOMER PORTAL USER MANAGEMENT (Admin interface)
341// ----------------------------------------------------
343 const customerUsersRoutes = require('./routes/customerUsers');
344 app.use('/api/customer-users', customerUsersRoutes);
345 console.log('✅ Customer users admin routes loaded');
347 console.error('❌ Failed loading customer users routes:', e);
350// ----------------------------------------------------
351// STATIC FILES FOR AGENT DOWNLOADS
352// ----------------------------------------------------
353const publicPath = path.join(__dirname, 'public');
354app.use('/agent', express.static(publicPath, {
355 setHeaders: (res, filepath) => {
356 // Set proper content type for executables and scripts
357 if (filepath.endsWith('.exe')) {
358 res.setHeader('Content-Type', 'application/octet-stream');
359 res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filepath)}"`);
360 } else if (filepath.endsWith('.sh')) {
361 res.setHeader('Content-Type', 'application/x-sh');
362 } else if (filepath.endsWith('.bat')) {
363 res.setHeader('Content-Type', 'application/x-msdos-program');
364 } else if (filepath.endsWith('.ps1')) {
365 res.setHeader('Content-Type', 'application/octet-stream');
367 // Prevent caching for downloads
368 if (filepath.includes('/downloads/')) {
369 res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
373console.log('✅ Static files mounted at /agent/ from', publicPath);
377// ----------------------------------------------------
379// ----------------------------------------------------
380// Guacamole test pages removed - MeshCentral-only architecture
382// ----------------------------------------------------
384// ----------------------------------------------------
385app.get("/api/health", (req, res) => res.status(200).send("OK"));
388// ----------------------------------------------------
390// ----------------------------------------------------
391const http = require("http");
392const server = http.createServer(app);
393const wsManager = require("./services/websocketManager");
395wsManager.init(server);
398 const stats = wsManager.getStats();
400 `[WS Monitor] Agents: ${stats.agents}, Dashboards: ${stats.dashboards}, Subscriptions: ${stats.subscriptions} @ ${new Date().toISOString()}`
405// ----------------------------------------------------
407// ----------------------------------------------------
408const PORT = process.env.PORT || 3000;
410// Start pluginBuilder Redis worker on backend startup - DISABLED (requires agent directory)
412// const { spawn } = require('child_process');
413// const pluginBuilderPath = path.resolve(__dirname, 'services', 'pluginBuilder.js');
414// const pluginBuilder = spawn('node', [pluginBuilderPath], {
415// stdio: ['ignore', 'inherit', 'inherit'],
418// console.log(`✅ pluginBuilder Redis worker started (PID: ${pluginBuilder.pid})`);
420// console.error('❌ Failed to start pluginBuilder worker:', err.message);
423// RustDesk sync service removed - MeshCentral-only architecture
425// Start agentDeleteWorker on backend startup
427 const { spawn } = require('child_process');
428 const agentDeleteWorkerPath = path.resolve(__dirname, 'services', 'agentDeleteWorker.js');
429 const agentDeleteWorker = spawn('node', [agentDeleteWorkerPath], {
430 stdio: ['ignore', 'inherit', 'inherit'],
433 console.log(`✅ agentDeleteWorker started (PID: ${agentDeleteWorker.pid})`);
435 console.error('❌ Failed to start agentDeleteWorker:', err.message);
438// Start all other workers on backend startup
440 'emailPollWorker.js',
441 'emailToTicketWorker.js',
444 'contractBillingWorker.js',
445 'digitalOceanSyncWorker.js',
446 'domainSyncWorker.js',
447 'dnsRecordsSyncWorker.js',
449 'workers/agentMonitor.js',
450 'workers/meshcentralSync.js'
451 // '../neto-xero/workers/netoSyncWorker.js' // Disabled: not in backend-only deployment
454// Start MeshCentral Device Auto-Sync (self-initializing)
455// This worker polls MeshCentral and auto-creates agents in our DB
457 require('./workers/meshcentral-device-sync');
458 console.log('✅ MeshCentral device auto-sync worker loaded');
460 console.error('❌ Failed to load MeshCentral device sync worker:', err.message);
462for (const file of workerFiles) {
464 const { spawn } = require('child_process');
465 const workerPath = path.resolve(__dirname, file);
466 const worker = spawn('node', [workerPath], {
467 stdio: ['ignore', 'inherit', 'inherit'],
470 console.log(`✅ ${file} started (PID: ${worker.pid})`);
472 console.error(`❌ Failed to start ${file}:`, err.message);
476// ----------------------------------------------------
477// TENANT-MESHCENTRAL STARTUP SYNC
478// Auto-creates device groups for all tenants
479// ----------------------------------------------------
481 const { initStartupSync, runStartupSync } = require('./startup/tenant-meshcentral-sync');
485 console.log('✅ Tenant-MeshCentral startup sync initialized');
487 // Periodic sync every hour to ensure consistency
488 const SYNC_INTERVAL = parseInt(process.env.TENANT_SYNC_INTERVAL || '3600000'); // 1 hour default
489 if (SYNC_INTERVAL > 0) {
491 console.log('\n⏰ Running scheduled tenant-mesh sync...');
492 runStartupSync().catch(err => {
493 console.error('❌ Scheduled sync error:', err.message);
496 console.log(`✅ Periodic sync scheduled every ${SYNC_INTERVAL / 60000} minutes`);
499 console.error('❌ Failed to initialize tenant-meshcentral sync:', err.message);
502server.listen(PORT, () => {
503 console.log(`✅ Backend API online (HTTP + WS) on port ${PORT}`);