signalk-mosquitto
Version:
SignalK plugin for managing Mosquitto MQTT broker with bridge connections and security
517 lines • 22.9 kB
JavaScript
"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