UNPKG

signalk-mosquitto

Version:

SignalK plugin for managing Mosquitto MQTT broker with bridge connections and security

517 lines 22.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const mosquitto_manager_1 = require("./services/mosquitto-manager"); const bridge_manager_1 = require("./services/bridge-manager"); const security_manager_1 = require("./services/security-manager"); const process_monitor_1 = require("./services/process-monitor"); const mosquitto_installer_1 = require("./services/mosquitto-installer"); const express = __importStar(require("express")); const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const file_utils_1 = require("./utils/file-utils"); const defaultPluginConfig = { enabled: false, brokerPort: 1883, brokerHost: '0.0.0.0', enableSecurity: true, autoStart: true }; const defaultCompleteConfig = { ...defaultPluginConfig, enableWebsockets: true, websocketPort: 9001, maxConnections: 1000, allowAnonymous: true, enableLogging: true, logLevel: 'information', persistence: true, persistenceLocation: '/tmp/mosquitto.db', tlsEnabled: false, tlsCertPath: '', tlsKeyPath: '', tlsCaPath: '', bridges: [], users: [], acls: [] }; function plugin(app) { let mosquittoManager; let bridgeManager; let securityManager; let processMonitor; let mosquittoInstaller; let currentPluginConfig; let currentCompleteConfig; let configDir; // Configuration management functions const getConfigPath = () => { if (!configDir) { configDir = file_utils_1.FileUtils.getDataDir('signalk-mosquitto'); } return configDir; }; const getWebappConfigPath = () => { return path.join(getConfigPath(), 'webapp-config.json'); }; const loadWebappConfig = async () => { try { const configPath = getWebappConfigPath(); const bridgesPath = path.join(getConfigPath(), 'bridges.json'); const usersPath = path.join(getConfigPath(), 'users.json'); const aclsPath = path.join(getConfigPath(), 'acls.json'); let savedConfig = {}; let bridges = []; let users = []; let acls = []; if (await fs.pathExists(configPath)) { savedConfig = await fs.readJson(configPath); } if (await fs.pathExists(bridgesPath)) { bridges = await fs.readJson(bridgesPath); } if (await fs.pathExists(usersPath)) { users = await fs.readJson(usersPath); } if (await fs.pathExists(aclsPath)) { acls = await fs.readJson(aclsPath); } return { ...defaultCompleteConfig, ...currentPluginConfig, ...savedConfig, bridges, users, acls }; } catch (error) { console.warn('Failed to load webapp config, using defaults:', error); } return { ...defaultCompleteConfig, ...currentPluginConfig }; }; const saveWebappConfig = async (config) => { try { await fs.ensureDir(getConfigPath()); const configPath = getWebappConfigPath(); // Only save webapp-managed settings, exclude plugin settings const { enabled, brokerPort, brokerHost, enableSecurity, autoStart, ...webappConfig } = config; await fs.writeJson(configPath, webappConfig, { spaces: 2 }); } catch (error) { console.error('Failed to save webapp config:', error); throw error; } }; const reloadCompleteConfig = async () => { try { // Reload the complete configuration currentCompleteConfig = await loadWebappConfig(); // Update managers with new configuration if they exist if (mosquittoManager) { // Restart mosquitto to apply new bridge configurations await mosquittoManager.restart(); } } catch (error) { console.error('Failed to reload complete config:', error); throw error; } }; const pluginInstance = { id: 'signalk-mosquitto', name: 'SignalK MQTT Mosquitto Manager', schema: () => ({ type: 'object', required: ['enabled'], properties: { enabled: { type: 'boolean', title: 'Enable Mosquitto Broker', description: 'Start/stop the Mosquitto MQTT broker service', default: false }, brokerPort: { type: 'number', title: 'MQTT Port', description: 'Primary MQTT port for broker connections', default: 1883, minimum: 1, maximum: 65535 }, brokerHost: { type: 'string', title: 'Bind Address', description: 'IP address to bind the broker to (0.0.0.0 for all interfaces)', default: '0.0.0.0' }, enableSecurity: { type: 'boolean', title: 'Enable Authentication', description: 'Require username/password authentication for connections', default: true }, autoStart: { type: 'boolean', title: 'Auto-start on SignalK Start', description: 'Automatically start Mosquitto when SignalK server starts', default: true } } }), start: async (config) => { currentPluginConfig = { ...defaultPluginConfig, ...config }; try { // Load complete configuration from webapp config file currentCompleteConfig = await loadWebappConfig(); mosquittoInstaller = new mosquitto_installer_1.MosquittoInstaller(app); mosquittoManager = new mosquitto_manager_1.MosquittoManagerImpl(app, currentCompleteConfig); bridgeManager = new bridge_manager_1.BridgeManagerImpl(app, currentCompleteConfig); securityManager = new security_manager_1.SecurityManagerImpl(app, currentCompleteConfig); processMonitor = new process_monitor_1.ProcessMonitorImpl(app, mosquittoManager); if (currentPluginConfig.enabled && currentPluginConfig.autoStart) { const isInstalled = await mosquittoInstaller.isInstalled(); if (!isInstalled) { console.log('Mosquitto not installed, installing...'); await mosquittoInstaller.install(); } console.log('Starting Mosquitto broker...'); await mosquittoManager.start(); processMonitor.start(); console.log('Mosquitto broker running'); } else { console.log('Mosquitto broker disabled or auto-start disabled'); } } catch (error) { console.error(`Failed to start Mosquitto: ${error.message}`); throw error; } }, stop: async () => { try { if (processMonitor) { processMonitor.stop(); } if (mosquittoManager) { await mosquittoManager.stop(); } console.log('Mosquitto plugin stopped'); } catch (error) { console.error(`Error stopping Mosquitto: ${error.message}`); throw error; } }, registerWithRouter: (router) => { // Serve the main web interface at the root of the plugin path router.get('/', (_req, res) => { res.sendFile(path.resolve(__dirname, '..', 'public', 'index.html')); }); // API routes router.get('/status', async (_req, res) => { try { if (!mosquittoManager) { return res.status(503).json({ error: 'Mosquitto manager not initialized' }); } const status = await mosquittoManager.getStatus(); res.json(status); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.post('/restart', async (_req, res) => { try { if (!mosquittoManager) { return res.status(503).json({ error: 'Mosquitto manager not initialized' }); } await mosquittoManager.restart(); res.json({ success: true, message: 'Mosquitto broker restarted' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.get('/bridges', async (_req, res) => { try { if (!bridgeManager) { return res.status(503).json({ error: 'Bridge manager not initialized' }); } const bridges = await bridgeManager.getBridges(); res.json(bridges); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.post('/bridges/test', async (req, res) => { try { if (!bridgeManager) { return res.status(503).json({ error: 'Bridge manager not initialized' }); } const bridge = req.body; const isConnected = await bridgeManager.testBridgeConnection(bridge); res.json({ connected: isConnected }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.post('/bridges', async (req, res) => { try { if (!bridgeManager) { return res.status(503).json({ error: 'Bridge manager not initialized' }); } const bridge = req.body; await bridgeManager.addBridge(bridge); await reloadCompleteConfig(); res.json({ success: true, message: 'Bridge added successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); router.put('/bridges/:bridgeId', async (req, res) => { try { if (!bridgeManager) { return res.status(503).json({ error: 'Bridge manager not initialized' }); } const { bridgeId } = req.params; const bridge = req.body; await bridgeManager.updateBridge(bridgeId, bridge); await reloadCompleteConfig(); res.json({ success: true, message: 'Bridge updated successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); router.delete('/bridges/:bridgeId', async (req, res) => { try { if (!bridgeManager) { return res.status(503).json({ error: 'Bridge manager not initialized' }); } const { bridgeId } = req.params; await bridgeManager.removeBridge(bridgeId); await reloadCompleteConfig(); res.json({ success: true, message: 'Bridge deleted successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); router.get('/monitoring', async (_req, res) => { try { if (!mosquittoManager) { return res.status(503).json({ error: 'Mosquitto manager not initialized' }); } const monitoring = await mosquittoManager.getMonitoringMetrics(); res.json(monitoring); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); // User management routes router.get('/users', async (_req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const users = await securityManager.getUsers(); res.json(users); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.post('/users', async (req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const user = req.body; await securityManager.addUser(user); res.json({ success: true, message: 'User added successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); router.put('/users/:username', async (req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const { username } = req.params; const user = req.body; await securityManager.updateUser(username, user); res.json({ success: true, message: 'User updated successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); router.delete('/users/:username', async (req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const { username } = req.params; await securityManager.removeUser(username); res.json({ success: true, message: 'User deleted successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); // ACL management routes router.get('/acls', async (_req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const acls = await securityManager.getAcls(); res.json(acls); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.post('/acls', async (req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const acl = req.body; await securityManager.addAcl(acl); res.json({ success: true, message: 'ACL rule added successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); router.delete('/acls', async (req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } const acl = req.body; await securityManager.removeAcl(acl); res.json({ success: true, message: 'ACL rule deleted successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(400).json({ error: errorMessage }); } }); // Certificate generation route router.post('/certificates/generate', async (_req, res) => { try { if (!securityManager) { return res.status(503).json({ error: 'Security manager not initialized' }); } await securityManager.generateCertificates(); res.json({ success: true, message: 'Self-signed certificates generated successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); // Configuration management routes router.get('/config', async (_req, res) => { try { // Return complete configuration for webapp management const completeConfig = await loadWebappConfig(); res.json(completeConfig); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); router.post('/config', async (req, res) => { try { const newConfig = { ...currentCompleteConfig, ...req.body }; // Validate the configuration const validation = require('./utils/validation').ValidationUtils.validateConfig(newConfig); if (!validation.valid) { return res.status(400).json({ error: `Configuration validation failed: ${validation.errors.join(', ')}` }); } // Save webapp-managed configuration await saveWebappConfig(newConfig); // Update current complete configuration currentCompleteConfig = newConfig; // Apply configuration changes to services (without restarting plugin) if (mosquittoManager) { await mosquittoManager.restart(); } res.json({ success: true, message: 'Configuration saved successfully' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: errorMessage }); } }); // Serve static files for the web interface const staticPath = path.resolve(__dirname, '..', 'public'); router.use(express.static(staticPath)); }, signalKApiRoutes: (router) => { console.log('Webapp API routes registered'); return router; } }; return pluginInstance; } module.exports = plugin; //# sourceMappingURL=index.js.map