diff --git a/server/services/mqtt/docker/eclipse-mosquitto-env.sh b/server/services/mqtt/docker/eclipse-mosquitto-env.sh deleted file mode 100755 index 04c85395be..0000000000 --- a/server/services/mqtt/docker/eclipse-mosquitto-env.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -# Configuration path -mosquitto_dir=/var/lib/gladysassistant/mosquitto -# Configuration file -mosquitto_config_file=${mosquitto_dir}/mosquitto.conf -# Password file -mosquitto_passwd_file=${mosquitto_dir}/mosquitto.passwd -internal_mosquitto_passwd_file=/mosquitto/config/mosquitto.passwd - -# Create configuration path (if not exists) -mkdir -p $mosquitto_dir - -# Check if config file not already exists -if [ ! -f "$mosquitto_config_file" ]; then - echo "Writting default eclipse-mosquitto configuration..." - - # Create config file - touch $mosquitto_config_file - - # Write defaults - echo "allow_anonymous false" >> $mosquitto_config_file - echo "connection_messages false" >> $mosquitto_config_file - echo "password_file ${internal_mosquitto_passwd_file}" >> $mosquitto_config_file - - echo "Default eclipse-mosquitto configuration written" -else - echo "eclipse-mosquitto configuration file already exists." -fi - -# Check for breaking change -if ! grep -q "listener 1883" "$mosquitto_config_file"; then - echo "listener 1883" >> $mosquitto_config_file -fi - -# Create passwd file if not exists -touch ${mosquitto_passwd_file} diff --git a/server/services/mqtt/lib/configureContainer.js b/server/services/mqtt/lib/configureContainer.js new file mode 100644 index 0000000000..1953233140 --- /dev/null +++ b/server/services/mqtt/lib/configureContainer.js @@ -0,0 +1,54 @@ +const fs = require('fs/promises'); +const { constants } = require('fs'); +const os = require('os'); + +const logger = require('../../../utils/logger'); +const { DEFAULT } = require('./constants'); + +const MOSQUITTO_DIRECTORY = '/var/lib/gladysassistant/mosquitto'; +const MOSQUITTO_CONFIG_FILE_PATH = `${MOSQUITTO_DIRECTORY}/mosquitto.conf`; +const MOSQUITTO_PASSWORD_FILE_PATH = `${MOSQUITTO_DIRECTORY}/mosquitto.passwd`; + +const MOSQUITTO_CONFIG_PORT = 'listener 1883'; +const MOSQUITTO_CONFIG_CONTENT = [ + 'allow_anonymous false', + 'connection_messages false', + `password_file ${DEFAULT.PASSWORD_FILE_PATH}`, + MOSQUITTO_CONFIG_PORT, +]; + +/** + * @description Configure MQTT container. + * @example + * mqtt.configureContainer(); + */ +async function configureContainer() { + logger.info('MQTT broker Docker container is being configured...'); + + // Create configuration path (if not exists) + await fs.mkdir(MOSQUITTO_DIRECTORY, { recursive: true }); + + // Check if config file not already exists + try { + // eslint-disable-next-line no-bitwise + await fs.access(MOSQUITTO_CONFIG_FILE_PATH, constants.R_OK | constants.W_OK); + logger.info('eclipse-mosquitto configuration file already exists.'); + + // Check for breaking change + const configContent = await fs.readFile(MOSQUITTO_CONFIG_FILE_PATH); + if (!configContent.includes(MOSQUITTO_CONFIG_PORT)) { + await fs.appendFile(MOSQUITTO_CONFIG_FILE_PATH, `${os.EOL}${MOSQUITTO_CONFIG_PORT}`); + } + } catch (e) { + logger.info('Writting default eclipse-mosquitto configuration...'); + await fs.writeFile(MOSQUITTO_CONFIG_FILE_PATH, MOSQUITTO_CONFIG_CONTENT.join(os.EOL)); + } + + // Create empty password file if not already exists + const pwdFile = await fs.open(MOSQUITTO_PASSWORD_FILE_PATH, 'w'); + await pwdFile.close(); +} + +module.exports = { + configureContainer, +}; diff --git a/server/services/mqtt/lib/constants.js b/server/services/mqtt/lib/constants.js index b3e0d1ee31..cd3cba46a5 100644 --- a/server/services/mqtt/lib/constants.js +++ b/server/services/mqtt/lib/constants.js @@ -17,6 +17,7 @@ const DEFAULT = { ERROR: 'ERROR', }, MOSQUITTO_VERSION: '3', + PASSWORD_FILE_PATH: '/mosquitto/config/mosquitto.passwd', }; module.exports = { diff --git a/server/services/mqtt/lib/index.js b/server/services/mqtt/lib/index.js index 28be33d79a..4d53464887 100644 --- a/server/services/mqtt/lib/index.js +++ b/server/services/mqtt/lib/index.js @@ -10,6 +10,7 @@ const { status } = require('./status'); const { getConfiguration } = require('./getConfiguration'); const { saveConfiguration } = require('./saveConfiguration'); const { installContainer } = require('./installContainer'); +const { configureContainer } = require('./configureContainer'); const { updateContainer } = require('./updateContainer'); const { checkDockerNetwork } = require('./checkDockerNetwork'); const { setValue } = require('./setValue'); @@ -45,6 +46,7 @@ MqttHandler.prototype.status = status; MqttHandler.prototype.getConfiguration = getConfiguration; MqttHandler.prototype.saveConfiguration = saveConfiguration; MqttHandler.prototype.installContainer = installContainer; +MqttHandler.prototype.configureContainer = configureContainer; MqttHandler.prototype.updateContainer = updateContainer; MqttHandler.prototype.checkDockerNetwork = checkDockerNetwork; MqttHandler.prototype.setValue = setValue; diff --git a/server/services/mqtt/lib/installContainer.js b/server/services/mqtt/lib/installContainer.js index b38320c853..ee27137983 100644 --- a/server/services/mqtt/lib/installContainer.js +++ b/server/services/mqtt/lib/installContainer.js @@ -1,6 +1,5 @@ const cloneDeep = require('lodash.clonedeep'); const logger = require('../../../utils/logger'); -const { exec } = require('../../../utils/childProcess'); const { generate } = require('../../../utils/password'); const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../utils/constants'); const { PlatformNotCompatible } = require('../../../utils/coreErrors'); @@ -31,8 +30,7 @@ async function installContainer(saveConfiguration = true) { // Prepare broker env logger.info(`Preparing broker environment...`); - const brokerEnv = await exec('sh ./services/mqtt/docker/eclipse-mosquitto-env.sh'); - logger.trace(brokerEnv); + await this.configureContainer(); logger.info(`Creating container...`); const containerDescriptorToMutate = cloneDeep(containerDescriptor); diff --git a/server/services/mqtt/lib/saveConfiguration.js b/server/services/mqtt/lib/saveConfiguration.js index 19279a0a6c..6eab60bcd3 100644 --- a/server/services/mqtt/lib/saveConfiguration.js +++ b/server/services/mqtt/lib/saveConfiguration.js @@ -51,14 +51,14 @@ async function saveConfiguration({ mqttUrl, mqttUsername, mqttPassword, useEmbed if (oldUser) { // Delete old user await this.gladys.system.exec(container.id, { - Cmd: ['mosquitto_passwd', '-D', '/mosquitto/config/mosquitto.passwd', oldUser], + Cmd: ['mosquitto_passwd', '-D', DEFAULT.PASSWORD_FILE_PATH, oldUser], }); } if (mqttUsername) { // Generate password await this.gladys.system.exec(container.id, { - Cmd: ['mosquitto_passwd', '-b', '/mosquitto/config/mosquitto.passwd', mqttUsername, mqttPassword], + Cmd: ['mosquitto_passwd', '-b', DEFAULT.PASSWORD_FILE_PATH, mqttUsername, mqttPassword], }); } diff --git a/server/test/services/mqtt/lib/configureContainer.test.js b/server/test/services/mqtt/lib/configureContainer.test.js new file mode 100644 index 0000000000..c808e86bb0 --- /dev/null +++ b/server/test/services/mqtt/lib/configureContainer.test.js @@ -0,0 +1,96 @@ +const sinon = require('sinon'); +const os = require('os'); +const { constants } = require('fs'); +const proxiquire = require('proxyquire').noCallThru(); + +const { assert, fake } = sinon; + +const gladys = {}; +const mqttClient = {}; +const serviceId = 'faea9c35-759a-44d5-bcc9-2af1de37b8b4'; + +describe('mqttHandler.configureContainer', () => { + const fsMock = {}; + const configureContainer = proxiquire('../../../../services/mqtt/lib/configureContainer', { + 'fs/promises': fsMock, + }); + const MqttHandler = proxiquire('../../../../services/mqtt/lib', { + './configureContainer': configureContainer, + }); + + beforeEach(() => { + fsMock.mkdir = fake.resolves(true); + fsMock.access = fake.resolves(true); + fsMock.readFile = fake.resolves('read'); + fsMock.appendFile = fake.resolves(true); + fsMock.writeFile = fake.resolves('write'); + fsMock.open = fake.resolves({ close: () => {} }); + }); + + afterEach(() => { + sinon.reset(); + }); + + it('should not write configuration', async () => { + fsMock.readFile = fake.resolves(`1st line${os.EOL}listener 1883`); + + const mqttHandler = new MqttHandler(gladys, mqttClient, serviceId); + await mqttHandler.configureContainer(); + + assert.calledOnceWithExactly(fsMock.mkdir, '/var/lib/gladysassistant/mosquitto', { recursive: true }); + assert.calledOnceWithExactly( + fsMock.access, + '/var/lib/gladysassistant/mosquitto/mosquitto.conf', + // eslint-disable-next-line no-bitwise + constants.R_OK | constants.W_OK, + ); + assert.calledOnceWithExactly(fsMock.readFile, '/var/lib/gladysassistant/mosquitto/mosquitto.conf'); + assert.calledOnceWithExactly(fsMock.open, '/var/lib/gladysassistant/mosquitto/mosquitto.passwd', 'w'); + assert.notCalled(fsMock.appendFile); + assert.notCalled(fsMock.writeFile); + }); + + it('should write default port', async () => { + const mqttHandler = new MqttHandler(gladys, mqttClient, serviceId); + await mqttHandler.configureContainer(); + + assert.calledOnceWithExactly(fsMock.mkdir, '/var/lib/gladysassistant/mosquitto', { recursive: true }); + assert.calledOnceWithExactly( + fsMock.access, + '/var/lib/gladysassistant/mosquitto/mosquitto.conf', + // eslint-disable-next-line no-bitwise + constants.R_OK | constants.W_OK, + ); + assert.calledOnceWithExactly(fsMock.readFile, '/var/lib/gladysassistant/mosquitto/mosquitto.conf'); + assert.calledOnceWithExactly( + fsMock.appendFile, + '/var/lib/gladysassistant/mosquitto/mosquitto.conf', + `${os.EOL}listener 1883`, + ); + assert.calledOnceWithExactly(fsMock.open, '/var/lib/gladysassistant/mosquitto/mosquitto.passwd', 'w'); + assert.notCalled(fsMock.writeFile); + }); + + it('should create default configuration file', async () => { + fsMock.access = fake.rejects(); + + const mqttHandler = new MqttHandler(gladys, mqttClient, serviceId); + await mqttHandler.configureContainer(); + + assert.calledOnceWithExactly(fsMock.mkdir, '/var/lib/gladysassistant/mosquitto', { recursive: true }); + assert.calledOnceWithExactly( + fsMock.access, + '/var/lib/gladysassistant/mosquitto/mosquitto.conf', + // eslint-disable-next-line no-bitwise + constants.R_OK | constants.W_OK, + ); + assert.notCalled(fsMock.readFile); + assert.notCalled(fsMock.appendFile); + assert.calledOnceWithExactly( + fsMock.writeFile, + '/var/lib/gladysassistant/mosquitto/mosquitto.conf', + `allow_anonymous false${os.EOL}connection_messages false${os.EOL}password_file /mosquitto/config/mosquitto.passwd${os.EOL}listener 1883`, + ); + assert.calledOnceWithExactly(fsMock.open, '/var/lib/gladysassistant/mosquitto/mosquitto.passwd', 'w'); + }); +}); diff --git a/server/test/services/mqtt/lib/installContainer.test.js b/server/test/services/mqtt/lib/installContainer.test.js index f4d38acba1..2329c83db0 100644 --- a/server/test/services/mqtt/lib/installContainer.test.js +++ b/server/test/services/mqtt/lib/installContainer.test.js @@ -47,13 +47,14 @@ describe('mqttHandler.installContainer', () => { }; const mqttHandler = new MqttHandler(gladys, MockedMqttClient, serviceId); + mqttHandler.configureContainer = fake.resolves(null); try { await mqttHandler.installContainer(); assert.fail('should have fail'); } catch (e) { expect(e).to.be.eq(error); - assert.notCalled(execMock.exec); + assert.notCalled(mqttHandler.configureContainer); assert.calledOnce(gladys.event.emit); assert.calledWith(gladys.event.emit, EVENTS.WEBSOCKET.SEND_ALL, { type: WEBSOCKET_MESSAGE_TYPES.MQTT.INSTALLATION_STATUS, @@ -89,11 +90,12 @@ describe('mqttHandler.installContainer', () => { }; const mqttHandler = new MqttHandler(gladys, MockedMqttClient, serviceId); + mqttHandler.configureContainer = fake.resolves(null); await mqttHandler.installContainer(); assert.callCount(gladys.variable.setValue, 5); - assert.calledOnce(execMock.exec); + assert.calledOnce(mqttHandler.configureContainer); assert.calledOnce(gladys.system.pull); assert.calledOnce(gladys.system.createContainer); assert.calledOnce(gladys.system.getNetworkMode); @@ -130,11 +132,12 @@ describe('mqttHandler.installContainer', () => { }; const mqttHandler = new MqttHandler(gladys, MockedMqttClient, serviceId); + mqttHandler.configureContainer = fake.resolves(null); await mqttHandler.installContainer(false); assert.notCalled(gladys.variable.setValue); - assert.calledOnce(execMock.exec); + assert.calledOnce(mqttHandler.configureContainer); assert.calledOnce(gladys.system.pull); assert.calledOnce(gladys.system.createContainer); assert.calledOnce(gladys.system.getNetworkMode);