diff --git a/docs/getting-started.md b/docs/getting-started.md index f9c2037..6553e3e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -105,6 +105,10 @@ services: - SONOS2MQTT_DEVICE=192.168.50.4 # Service discovery doesn't work very well inside docker, so start with one device. - SONOS2MQTT_MQTT=mqtt://emqx:1883 # EMQX is a nice mqtt broker # - SONOS2MQTT_DISTINCT=true # if your want distinct topics + # - SONOS2MQTT_MQTT_CA_PATH=/path/to/ca.crt # If you use a self-signed certificate + # - SONOS2MQTT_MQTT_CERT_PATH=/path/to/cert.crt # If you want a secure connection + # - SONOS2MQTT_MQTT_KEY_PATH=/path/to/key.key # If you want a secure connection + # - SONOS2MQTT_MQTT_REJECT_UNAUTHORIZED=true # If you use official signed certificates - SONOS_LISTENER_HOST=192.168.50.44 # Docker host IP - SONOS_TTS_ENDPOINT=http://sonos-tts:5601/api/generate # If you deployed the TTS with the same docker-compose depends_on: diff --git a/src/config.ts b/src/config.ts index f41f439..51e9dd1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,6 +20,17 @@ export interface Config { tvUuid?: string; tvVolume?: number; experimental?: boolean; + secure?: SecureConfig; +} + +export interface SecureConfig { + key?: string | string[] | Buffer | Buffer[] | any[]; + keyPath?: string; + cert?: string | string[] | Buffer | Buffer[]; + certPath?: string; + ca?: string | string[] | Buffer | Buffer[]; + caPaths?: string | string[]; + rejectUnauthorized?: boolean; } const defaultConfig: Config = { @@ -34,9 +45,10 @@ const defaultConfig: Config = { } export class ConfigLoader { - static LoadConfig(): Config { - const config = {...defaultConfig, ...(ConfigLoader.LoadConfigFromFile() ?? ConfigLoader.LoadConfigFromArguments())}; - + static async LoadConfig(): Promise { + const extraConfig = ConfigLoader.LoadConfigFromFile() ?? await ConfigLoader.LoadConfigFromArguments(); + const config = {...defaultConfig, ...extraConfig}; + if (config.ttsendpoint !== undefined && process.env.SONOS_TTS_ENDPOINT === undefined) { process.env.SONOS_TTS_ENDPOINT = config.ttsendpoint } @@ -60,12 +72,18 @@ export class ConfigLoader { return; } - private static LoadConfigFromArguments(): Partial { + private static async LoadConfigFromArguments(): Promise> { const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()) - return yargs + const config = await yargs .usage(pkg.name + ' ' + pkg.version + '\n' + pkg.description + '\n\nUsage: $0 [options]') .describe('prefix', 'instance name. used as prefix for all topics') .describe('mqtt', 'mqtt broker url. See https://sonos2mqtt.svrooij.io/getting-started.html#configuration') + .describe('mqtt_cert_path', 'Path to the certificate file for secure mqtt connections.') + + .describe('mqtt_key_path', 'Path to the key file for secure mqtt connections.') + .describe('mqtt_ca_path', 'Path to the ca file for secure mqtt connections.') + .describe('mqtt_reject_unauthorized', 'Reject unauthorized connections.') + .boolean('mqtt_reject_unauthorized') .describe('clientid', 'Specify the client id to be used') .describe('wait', 'Number of seconds to search for speakers') .describe('log', 'Set the loglevel') @@ -103,6 +121,17 @@ export class ConfigLoader { .version() .help('help') .env('SONOS2MQTT') - .argv as Partial + .argv; + + + if (config.mqtt_cert_path !== undefined || config.mqtt_key_path !== undefined || config.mqtt_ca_path !== undefined || config.mqtt_reject_unauthorized === true) { + config.secure = { + keyPath: config.mqtt_key_path, + certPath: config.mqtt_cert_path, + caPaths: config.mqtt_ca_path, + rejectUnauthorized: config.mqtt_reject_unauthorized + } + } + return config as Partial; } } diff --git a/src/index.ts b/src/index.ts index 00da6cf..518ee0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,24 +7,26 @@ import {SonosToMqtt} from './sonos-to-mqtt' import {ConfigLoader} from './config' import { StaticLogger } from './static-logger' -const sonosToMqtt = new SonosToMqtt(ConfigLoader.LoadConfig()) const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()) StaticLogger.Default().info(`Starting ${pkg.name} v${pkg.version}`) -const stop = function () { - StaticLogger.Default().info('Shutdown sonos2mqtt, please wait.') - sonosToMqtt.stop() - setTimeout(() => { process.exit(0) }, 800) +async function main() { + const sonosToMqtt = new SonosToMqtt(await ConfigLoader.LoadConfig()) + const result = await sonosToMqtt.start(); + if (!result) { + StaticLogger.Default().fatal('Failed to start sonos2mqtt') + process.exit(1) + } + function stop() { + StaticLogger.Default().info('Shutdown sonos2mqtt, please wait.') + sonosToMqtt.stop() + setTimeout(() => { process.exit(0) }, 800) + } + process.on('SIGINT', stop) + process.on('SIGTERM', stop) } -sonosToMqtt - .start() - .then(success => { - if(success) { - process.on('SIGINT', () => stop()) - process.on('SIGTERM', () => stop()) - } - }) +main() .catch(err => { StaticLogger.Default().fatal(err, 'Error starting sonos2mqtt') }) diff --git a/src/smarthome-mqtt.ts b/src/smarthome-mqtt.ts index b33191e..19e4cb6 100644 --- a/src/smarthome-mqtt.ts +++ b/src/smarthome-mqtt.ts @@ -5,6 +5,7 @@ import {EventEmitter} from 'events' import { DeviceControl } from './device-control' import {StaticLogger} from './static-logger' import { AutoDiscoveryMessage } from './ha-discovery'; +import { SecureConfig } from './config'; type MqttEvents = { connected: (connected: boolean) => void; @@ -16,8 +17,9 @@ export class SmarthomeMqtt{ private readonly log = StaticLogger.CreateLoggerForSource('sonos2mqtt.SmarthomeMqtt') private readonly uri: URL private mqttClient?: MqttClient; + private readonly security?: SecureConfig; public readonly Events: TypedEmitter = new EventEmitter() as TypedEmitter; - constructor(mqttUrl: string, private readonly prefix: string = 'sonos', private readonly clientId?: string) { + constructor(mqttUrl: string, private readonly prefix: string = 'sonos', private readonly clientId?: string, security?: SecureConfig) { this.uri = new URL(mqttUrl) } @@ -30,7 +32,14 @@ export class SmarthomeMqtt{ retain: true }, keepalive: 60000, - clientId: this.clientId + clientId: this.clientId, + rejectUnauthorized: (this.uri.protocol === 'mqtts' || this.uri.protocol === 'ssl') && this.security?.rejectUnauthorized === true, + ca: this.security?.ca, + key: this.security?.key, + cert: this.security?.cert, + caPaths: this.security?.caPaths, + keyPath: this.security?.keyPath, + certPath: this.security?.certPath, }); this.mqttClient.on('connect',() => { this.log.debug('Connected to server {server}', this.uri.host) diff --git a/src/sonos-to-mqtt.ts b/src/sonos-to-mqtt.ts index 1a81c91..f7d4158 100644 --- a/src/sonos-to-mqtt.ts +++ b/src/sonos-to-mqtt.ts @@ -21,7 +21,7 @@ export class SonosToMqtt { private readonly _soundbarTrackUri = 'x-sonos-htastream:RINCON_' constructor(private config: Config) { this.sonosManager = new SonosManager(); - this.mqtt = new SmarthomeMqtt(config.mqtt, config.prefix, config.clientid); + this.mqtt = new SmarthomeMqtt(config.mqtt, config.prefix, config.clientid, config.secure); } async start(): Promise { diff --git a/tsconfig.json b/tsconfig.json index 3c4124b..e91ca81 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -62,7 +62,8 @@ /* Advanced Options */ // "resolveJsonModule": true, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ }, "include": ["src"], "exclude": ["node_modules", "**/__tests__/*"],