iobroker.js-controller
Version: 
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
326 lines (297 loc) • 14.2 kB
JavaScript
/**
 *      Multihost
 *
 *      Copyright 2013-2021 bluefox <dogafox@gmail.com>
 *
 *      MIT License
 *
 */
;
/** @class */
function Multihost(options) {
    const fs         = require('fs-extra');
    const tools      = require('../tools.js');
    const configName = tools.getConfigFileName();
    const that       = this;
    options          = options || {};
    const params     = options.params || {};
    const objects    = options.objects;
    function getConfig() {
        let config;
        // read actual configuration
        try {
            if (fs.existsSync(configName)) {
                config = fs.readJSONSync(configName);
            } else {
                config = require(`../../conf/${tools.appName}-dist.json`);
            }
        } catch {
            config = require(`../../conf/${tools.appName}-dist.json`);
        }
        return config;
    }
    function leftPad(text, len) {
        text = text || '';
        if (text.length >= len) {
            return len;
        }
        return new Array(len - text.length).join(' ') + text;
    }
    this.showHosts = function (list) {
        if (!list || !list.length) {
            console.info('No Multihost server found. Make sure iobroker is running on the host where you enabled multihost discovery (and it is not this host)!');
        } else {
            for (let i = 0; i < list.length; i++) {
                console.log(`${i + 1} | ${leftPad(list[i].hostname, 20)} | ${list[i].slave ? 'slave' : ' host'} | ${leftPad(list[i].ip, 20)} | ${JSON.stringify(list[i].info)}`);
            }
        }
    };
    this.browse = function (callback) {
        const MHClient = require('../multihostClient');
        const mhClient = new MHClient();
        mhClient.browse(2000, params.debug, (err, list) => {
            if (err) {
                callback(`Multihost discovery client: Cannot browse: ${err}`);
            } else {
                callback(null, list);
            }
        });
    };
    function showMHState(config, changed, callback) {
        if (config.multihostService.enabled) {
            let warningShown = false;
            if (tools.isLocalObjectsDbServer(config.objects.type, config.objects.host, true)) {
                console.log('Changing objects server to accept connections on all IP addresses.');
                config.objects.host = '0.0.0.0';
                changed = true;
            } else if (config.objects.type === 'redis') {
                warningShown = true;
                console.log('Please check the binding of redis service. By default it is only local: http://download.redis.io/redis-stable/redis.conf\nChange "bind 127.0.0.1" to "bind 0.0.0.0" or to others.');
            } else {
                warningShown = true;
                console.log('Please check the binding of the configured ' + config.objects.type + ' server to allow remote connections.');
            }
            if (tools.isLocalStatesDbServer(config.states.type, config.states.host, true)) {
                console.log('Changing states server to accept connections on all IP addresses.');
                config.states.host = '0.0.0.0';
                changed = true;
            } else if (config.states.type  === 'redis') {
                !warningShown && console.log('Please check the binding of redis service. By default it is only local: http://download.redis.io/redis-stable/redis.conf\nChange "bind 127.0.0.1" to "bind 0.0.0.0" or to others.');
            } else {
                !warningShown && console.log(`Please check the binding of the configured ${config.states.type} server to allow remote connections.`);
            }
        }
        if (!changed) {
            console.log('No configuration change needed.');
        } else {
            fs.writeFileSync(configName, JSON.stringify(config, null, 2));
            console.log('Please restart ioBroker for the changes to take effect: "iobroker restart"');
        }
        console.log('\n');
        console.log(`Multihost discovery server: ${config.multihostService.enabled ? 'enabled' : 'disabled'}`);
        console.log(`Discovery authentication:   ${config.multihostService.secure ? 'enabled' : 'disabled'}`);
        console.log(`Persistent activation:      ${config.multihostService.enabled && config.multihostService.persist ? 'enabled' : 'disabled'}`);
        console.log(`Objects:                    ${config.objects.type} on ${config.objects.host}`);
        console.log(`States:                     ${config.states.type} on ${config.states.host}`);
        callback();
    }
    /**
     * Enables or disables the multihost discovery server in the config json
     *
     * @param {boolean} isEnable - if the server should be activated or deactivated
     * @param {function} callback - callback function to be executed
     */
    this.enable = function (isEnable, callback) {
        let changed = false;
        const config = getConfig();
        config.multihostService = config.multihostService || {enabled: false, secure: true};
        if (isEnable && !config.multihostService.enabled) {
            changed = true;
            config.multihostService.enabled = true;
            config.multihostService.password = '';
            console.log('Multihost discovery server activated on this host. If iobroker is currently not running please start befeore trying to discover this host.');
            console.log('Important: Multihost discovery works with UDP packets. Make sure they are routed correctly in your network. If you use Docker you also need to configure this correctly.');
            if (!params.persist) {
                console.log('Multihost discovery will be automatically deactivated after 15 minutes. If you want to activate it permanently use the --persist flag');
            }
        } else if (!isEnable && config.multihostService.enabled) {
            changed = true;
            config.multihostService.enabled = false;
            config.multihostService.password = '';
            console.log('Multihost discovery server deactivated on this host.');
        }
        if (params.secure === undefined) {
            params.secure = true;
        }
        params.persist = !!params.persist;
        if (isEnable && (config.multihostService.secure !== params.secure || (config.multihostService.secure && !config.multihostService.password) || (config.multihostService.persist !== params.persist))) {
            changed = true;
            config.multihostService.secure = params.secure;
            config.multihostService.persist = params.persist;
            console.log(`Discovery authentication ${params.secure ? 'activated' : 'deactivated'}.`);
            if (config.multihostService.secure) {
                const prompt = require('prompt');
                prompt.message   = '';
                prompt.delimiter = '';
                const schema = {
                    properties: {
                        password: {
                            description: 'Enter secret phrase for connection:',
                            pattern:     /^[^'"]+$/,
                            message:     'No " are allowed',
                            hidden:      true
                        },
                        passwordRepeat: {
                            description: 'Repeat secret phrase for connection:',
                            pattern:     /^[^'"]+$/,
                            message:     'No " are allowed',
                            hidden:      true
                        }
                    }
                };
                prompt.start();
                prompt.get(schema, (err, password) => {
                    if (password && password.password) {
                        if (password.password !== password.passwordRepeat) {
                            callback('Secret phrases are not equal!');
                        } else {
                            objects.getObject('system.config', (err, obj) => {
                                config.multihostService.password = tools.encrypt(obj.native.secret, password.password);
                                showMHState(config, changed, callback);
                            });
                        }
                    } else {
                        callback('No secret phrase entered!');
                    }
                });
            } else {
                showMHState(config, changed, callback);
            }
        } else {
            showMHState(config, changed, callback);
        }
    };
    this.status = function (callback) {
        const config = getConfig();
        config.multihostService = config.multihostService || {enabled: false, secure: true};
        showMHState(config, false, callback);
    };
    function readPassword(callback) {
        const readline = require('readline');
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout
        });
        function hidden(query, callback) {
            const stdin = process.openStdin();
            process.stdin.on('data', char => {
                char = char.toString();
                switch (char) {
                    case '\n':
                    case '\r':
                    case '\u0004':
                        stdin.pause();
                        break;
                    default:
                        process.stdout.write('\x1B[2K\x1B[200D' + query + new Array(rl.line.length + 1).join('*'));
                        break;
                }
            });
            rl.question(query, value => {
                rl.history = rl.history.slice(1);
                callback(value);
            });
        }
        hidden('Enter secret phrase for connection: ', password =>
            callback(password));
    }
    function connect(mhClient, ip, pass, callback) {
        mhClient.connect(ip, pass, (err, oObjects, oStates, ipHost) => {
            if (err) {
                callback(`Cannot connect to "${ip}": ${err}`);
            } else if (oObjects && oStates) {
                const config = getConfig();
                config.objects = oObjects;
                config.states  = oStates;
                if (tools.isLocalObjectsDbServer(config.objects.type, config.objects.host, true) || tools.isLocalStatesDbServer(config.states.type, config.states.host, true)) {
                    callback('IP Address of the remote host is 127.0.0.1. Connections from this host will not be accepted. Please change the configuration of this host to accept remote connections.');
                } else {
                    if (config.states.host === '0.0.0.0') { // TODO: why we set the remote IP only when the local config allows full connectivity?
                        config.states.host = ipHost;
                    }
                    if (config.objects.host === '0.0.0.0') { // TODO: why we set the remote IP only when the local config allows full connectivity?
                        config.objects.host = ipHost;
                    }
                    fs.writeFileSync(configName, JSON.stringify(config, null, 2));
                    console.log('Config ok. Please restart ioBroker: "iobroker restart"');
                    callback();
                }
            } else {
                callback('No configuration received!');
            }
        });
    }
    this.connect = function (number, pass, callback) {
        if (typeof pass === 'function') {
            callback = pass;
            pass = null;
        }
        if (typeof number === 'function') {
            callback = number;
            number   = null;
        }
        const MHClient = require('../multihostClient');
        const mhClient = new MHClient();
        mhClient.browse(2000, params.debug, (err, list) => {
            if (err) {
                callback('Cannot browse: ' + err);
            } else {
                that.showHosts(list);
                if (number !== null && number !== undefined && parseInt(number, 10) > 0) {
                    number = parseInt(number, 10);
                    if (list && number < list.length + 1) {
                        if (!pass) {
                            callback('No password defined: please use "multihost connect <NUMBER> <PASSWORD>"');
                        } else {
                            connect(mhClient, list[number - 1].ip, pass, callback);
                        }
                    } else {
                        callback('Invalid index: ' + number);
                    }
                } else
                if (list && list.length) {
                    const readline = require('readline');
                    const rl = readline.createInterface({
                        input:  process.stdin,
                        output: process.stdout
                    });
                    rl.question('Please select host [1]: ', answer => {
                        if (answer === '' || answer === null || answer === undefined) {
                            answer = 1;
                        }
                        answer = parseInt(answer, 10) - 1;
                        if (!list[answer]) {
                            rl.close();
                            callback('Invalid index: ' + answer);
                        } else {
                            if (list[answer].auth) {
                                readPassword(password => {
                                    if (password) {
                                        connect(mhClient, list[answer].ip, password, callback);
                                    } else {
                                        callback('No password entered!');
                                    }
                                });
                            } else {
                                connect(mhClient, list[answer].ip, null, callback);
                            }
                        }
                    });
                } else {
                    callback(null, list);
                }
            }
        });
    };
}
module.exports = Multihost;