iobroker.js-controller
Version:
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
2,919 lines (2,703 loc) • 127 kB
JavaScript
/* eslint-disable no-inner-declarations */
/**
*
* ioBroker Command Line Interface (CLI)
*
* 7'2014-2020 bluefox <dogafox@gmail.com>
* 2014 hobbyquaker <hq@ccu.io>
*
*/
/* jshint -W097 */
/* jshint strict:false */
/* jslint node: true */
;
// TODO need info about progress of stopping
const fs = require('fs-extra');
const pathLib = require('path');
const tools = require('./tools.js');
const cli = require('./cli/index.js');
const EXIT_CODES = require('./exitCodes');
const {enumHosts} = require('./cli/cliTools');
const deepClone = require('deep-clone');
const { isDeepStrictEqual } = require('util');
const debug = require('debug')('iobroker:cli');
// @ts-ignore
require('events').EventEmitter.prototype._maxListeners = 100;
process.setMaxListeners(0);
/** @type {import('yargs')} */
let yargs;
function initYargs() {
yargs = require('yargs')
.scriptName(tools.appName)
.locale('en')// otherwise it could be mixed, because our implementations are in english
.version(false) // disable yargs own version handling, because we have our own depending on passed instances
.completion('_createCompletion', false) // can be created via iob _createCompletion >> ~/.bashrc or ~/.bash_profile for OSX
.command('setup', 'Setup ioBroker', {
redis: {
describe: 'Setup as redis',
type: 'boolean'
},
objects: {
describe: 'Objects <host>',
default: '127.0.0.1',
type: 'number'
},
states: {
describe: 'States <host>',
default: '127.0.0.1',
type: 'number'
},
'port <port>': {
describe: 'Port of redis',
default: 6379,
type: 'number'
},
custom: {
describe: 'Custom setup',
type: 'boolean'
},
first: {
describe: 'Initial setup',
type: 'boolean'
}
})
.command('start', 'Starts the js-controller', yargs => {
yargs
.command('all', 'Starts js-controller and all adapters')
.command('<adapter>[.<instance>]', 'Starts a specified adapter instance');
})
.command('stop', 'stops the js-controller', yargs => {
yargs
.command('<adapter>[.<instance>]', 'Stops a specified adapter instance');
})
.command('restart', 'Restarts js-controller', yargs => {
yargs
.command('<adapter>[.<instance>]', 'Restarts a specified adapter instance', {});
})
.command('debug <adapter>[.<instance>]', 'Starts a Node.js debugging session for the adapter instance', {
ip: {
describe: 'IP-address <ip>',
type: 'string'
},
port: {
describe: 'Port <port>',
type: 'number'
},
wait: {
describe: 'Wait',
type: 'boolean'
}
})
.command('info', 'Shows the host info', {})
.command('logs [<adapter>]', 'Monitor log', {
'lines=1000': { // TODO: it's the only place we use = we should avoid this
describe: 'Number of lines',
type: 'string'
},
watch: {
describe: 'Watch',
type: 'boolean'
}
})
.command('add <adapter> [desiredNumber]', 'Add instance of adapter', {
enabled: {
describe: 'Enable adapter',
type: 'boolean'
},
host: {
describe: 'Host <host>',
type: 'string'
},
port: {
describe: 'Port <port>',
type: 'number'
}
})
.command('install <adapter>', 'Installs a specified adapter', {})
.command('rebuild <adapter>|self', 'Rebuilds a specified adapter', {
install: {
describe: 'Install',
type: 'boolean'
}
})
.command('url <url> [<name>]', 'Install adapter from specified url, e.g. GitHub', {})
.command('del <adapter>', 'Remove adapter from system', {})
.command('del <adapter>.<instance>', 'Remove adapter instance', {})
.command('update [<repositoryUrl>]', 'Update repository and list adapters', {
updateable: {
describe: 'Only show updateable adapters',
alias: 'u',
type: 'boolean'
},
all: {
describe: 'Show all available adapters',
alias: 'a',
type: 'boolean'
},
force: {
describe: 'Bypass hash check',
alias: 'f',
type: 'boolean'
}
})
.command('upgrade', 'Upgrade management', yargs => {
yargs
.option('yes', {
describe: 'Bypass questionnaire',
alias: 'y',
type: 'boolean'
})
.command('[<repositoryUrl>]', 'Upgrade all adapters, optionally you can specify the repository url', {})
.command('all [<repositoryUrl>]', 'Upgrade all adapters, optionally you can specify the repository url', {})
.command('self [<repositoryUrl>]', 'Upgrade js-controller, optionally you can specify the repository url', {})
.command('<adapter> [<repositoryUrl>]', 'Upgrade specified adapter, optionally you can specify the repository url', {});
})
.command('upload', 'Upload management', yargs => {
yargs
.command(`<pathToLocalFile> <pathIn${tools.appName}>`, 'Upload given files to provided path to make them available for instances', {})
.command('all', 'Upload all adapter files to make them available for instances', {})
.command('<adapter>', 'Upload specified adapter files to make them available for instances', {});
})
.command('object', 'Object management', yargs => {
yargs
.command('get <id>', 'Get object specified by id', {})
.command('set <id> <json-value>', 'Set object with the given id by providing a new json object', {})
.command('set <id> propertyname=<value or json-value>', 'Update part of the object by providing a new value or partial object', {})
.command('extend <id> <json-value>', 'Extend object with the given id by providing a new json object', {})
.command('del <id|pattern>', 'Delete object with given id or all objects matching the pattern', {
y: {
describe: 'Bypass questionnaire',
alias: 'y',
type: 'boolean'
}
})
.command('chmod <object-mode> [state-mode] <id>', 'Change object rights', {})
.command('chown <user> <group> <id>', 'Change object ownership', {})
.command('list <pattern>', 'List object matching given pattern', {});
})
.command('state', 'State management', yargs => {
yargs
.command('get <id>', 'Get state, specified by id', {})
.command('getplain <id>', 'Get plain state, specified by id', {
pretty: {
describe: 'Prettify output',
type: 'boolean'
}
})
.command('getvalue <id>', 'Get state value, specified by id', {})
.command('set <id> <value> [<ack>]', 'Set state, specified by id', {})
.command('del <id>', 'Delete state, specified by id', {});
})
.command('message <adapter>[.instance] <command> [<message>]', 'Send message to adapter instance/s', {})
.command('list <type> [<filter>]', 'List all entries, like objects', {})
.command('chmod <mode> <file>', 'Change file rights', {})
.command('chown <user> <group> <file>', 'Change file ownership', {})
.command('touch <file>', 'Touch file', {})
.command('rm <file>', 'Remove file', {})
.command('file', 'File management', yargs => {
yargs
.command(`read <${tools.appName}-path-to-read> [<filesystem-path-to-write>]`, `Read file from ${tools.appName} path and optionally write to destination`, {})
.command(`write <filesystem-path-to-read> <${tools.appName}-path-to-write>`, `Read file from path and write it to ${tools.appName} path`,{})
.command(`rm <${tools.appName}-path-to-delete>`, 'Remove file', {})
.command('sync', 'Sync files', {});
})
.command('user', 'User commands', yargs => {
yargs
.command('add <user>', 'Add new user',yargs => {
yargs.option('ingroup', {
describe: 'User group',
type: 'string'
})
.option('password', {
describe: 'User password',
type: 'string'
});
})
.command('del <user>', 'Delete user', {})
.command('passwd <user>', 'Change user password', yargs => {
yargs
.option('password', {
describe: 'User password',
type: 'string'
});
})
.command('enable <user>', 'Enable user', {})
.command('disable <user>', 'Disable user', {})
.command('get <user>', 'Get user', {})
.command('check <user>', 'Check user password', yargs => {
yargs
.option('password', {
describe: 'User password',
type: 'string'
});
});
})
.command('group', 'group management', yargs => {
yargs
.command('add <group>', 'Add group', {})
.command('del <group>', 'Remove group', {})
.command('list <group>', 'List group', {})
.command('enable <group>', 'Enable group', {})
.command('disable <group>', 'Disable group', {})
.command('get <group>', 'Get group', {})
.command('adduser <group> <user>', 'Add user to group', {})
.command('deluser <group> <user>', 'Remove user from group', {});
})
.command('host <hostname>', 'Set host to given hostname', yargs => {
yargs
.command('this', 'Initialize current host', {})
.command('set <hostname>', 'Set host with specified hostname', {})
.command('remove <hostname>', 'Remove host with specified hostname', {});
})
.command('set <adapter>.<instance>', 'Change settings of adapter config', {
customOption: {
describe: 'Set the name of the parameter you want to change as option followed by its value, e. g. --port 80'
}
})
.command('license <license.file or license.text>', 'Update license by given file', {})
.command('cert', 'Certificate management', yargs => {
yargs
.command('create', 'Create certificate', {})
.command('view [<certificate name>]', 'Show certificate', {});
})
.command('clean <yes>', 'Clears all objects and states', {})
.command('backup', 'Create backup', {})
.command('restore <backup name or path>', 'Restore a specified backup', {})
.command('validate <backup name or path>', 'Validate a specified backup', {})
.command('status [all|<adapter>.<instance>]', 'Status of ioBroker or adapter instance', {})
.command('repo [<name>]', 'Show repo information', yargs => {
yargs
.command('set <name>', 'Set active repository')
.command('del <name>', 'Remove repository')
.command('add <name> <url>', 'Add repository')
.command('addset <name> <url>', 'Add repository and set it as active one')
.command('show', 'List repositories');
})
.command('uuid', 'Show uuid of the installation', {})
.command('unsetup', 'Reset license, installation secret and language', {})
.command('fix', 'Execute the installation fixer script, this updates your ioBroker installation', {})
.command('multihost', 'Multihost management', yargs => {
yargs
.command('enable', 'Enable multihost discovery', {
secure: {
describe: 'Use secure connection',
type: 'boolean'
},
persist: {
describe: 'Enable persistent discovery',
type: 'boolean'
}
})
.command('disable', 'Disable multihost discovery')
.command('browse', 'Browse for multihost server')
.command('connect', 'Connect to multihost server');
})
.command('compact', 'compact group management', yargs => {
yargs
.command('enable', 'Enable compact mode in general')
.command('on', 'Enable compact mode in general')
.command('disable', 'Disable compact mode in general')
.command('off', 'Disable compact mode in general')
.command('<adapter>.<instance> status', 'Show if compact mode is enabled for a specific instance')
.command('<adapter>.<instance> group <group-id>', 'Define compact group of a specific adapter')
.command('<adapter>.<instance> <disable|off> [group-id]', 'Enable or disable compact mode for specified adapter instance and set compact group optionally')
.command('<adapter>.<instance> <enable|on> [group-id]', 'Enable or disable compact mode for specified adapter instance and set compact group optionally');
})
.command('plugin', 'Plugin management', yargs => {
yargs
.command('enable <pluginname>', 'Enables a plugin for the specified host or instance. If no host is specified, the current one is used', {
host: {
describe: 'Hostname',
type: 'string'
},
instance: {
describe: 'Instance, e.g. hm-rpc.0',
type: 'string'
}
})
.command('disable <pluginname>', 'Disables a plugin for the specified host or instance. If no host is specified, the current one is used', {
host: {
describe: 'Hostname',
type: 'string'
},
instance: {
describe: 'Instance, e.g. hm-rpc.0',
type: 'string'
}
})
.command('status <pluginname>', 'Checks if a plugin is enabled for the specified host or instance. If no host is specified, the current one is used', {
host: {
describe: 'Hostname',
type: 'string'
},
instance: {
describe: 'Instance, e.g. hm-rpc.0',
type: 'string'
}
});
})
.command('version [<adapter>]', 'Show version of js-controller or specified adapter')
.option('version', {
describe: 'Show version',
type: 'boolean',
alias: 'v'
})
.wrap(null)
;
return yargs;
}
/**
* Show yargs help, if processCommand is used as import, yargs won't be initialized
* @param {object} _yargs - yargs instance
*/
function showHelp(_yargs) {
if (_yargs) {
_yargs.showHelp();
} else if (yargs) {
yargs.showHelp();
}
}
let Objects; // constructor
let objects; // instance
let States; // constructor
let states; // instance
/**
* Process the given CLI command
*
* @param {string|number} command - command to execute
* @param {any[]} args - arguments passed to yargs
* @param {object} params - object with parsed params by yargs, e. g. --force is params.force
* @param {(exitCode?: number) => void} callback
*/
function processCommand(command, args, params, callback) {
if (typeof args === 'function') {
callback = args;
args = null;
}
if (typeof params === 'function') {
callback = params;
params = null;
}
if (!params) {
params = {};
}
if (!args) {
args = [];
}
if (!callback) {
callback = processExit;
}
/** @type {import('./cli/cliCommand').CLICommandContext} */
const commandContext = {dbConnect, callback, showHelp};
/** @type {import('./cli/cliCommand').CLICommandOptions} */
const commandOptions = Object.assign({}, params, commandContext);
debug(`commandOptions: ${JSON.stringify(commandOptions)}`);
debug(`args: ${args}`);
switch (command) {
case 'start':
case 'stop': {
const procCommand = new cli.command.process(commandOptions);
procCommand[command](args);
break;
}
case 'debug': {
const debugCommand = new cli.command.debug(commandOptions);
debugCommand.execute(args);
break;
}
case 'status':
case 'isrun': {
const procCommand = new cli.command.process(commandOptions);
procCommand.status(args);
break;
}
case 'r':
case 'restart': {
const procCommand = new cli.command.process(commandOptions);
procCommand.restart(args);
break;
}
case '_restart':
restartController(callback);
break;
case 'update': {
Objects = require('./objects');
const repoUrl = args[0]; // Repo url or name
dbConnect(params, (_objects, _states) => {
const Repo = require('./setup/setupRepo.js');
const repo = new Repo({
objects: _objects,
states: _states
});
repo.showRepo(repoUrl, params, () => {
setTimeout(callback, 2000);
});
});
break;
}
case 'setup': {
const Setup = require('./setup/setupSetup.js');
const setup = new Setup({
dbConnect,
processExit: callback,
cleanDatabase,
restartController,
resetDbConnect,
params
});
if (args[0] === 'custom' || params.custom) {
setup.setupCustom(callback);
} else {
let isFirst;
let isRedis;
// we support "first" and "redis" without "--" flag
for (const arg of args) {
if (arg === 'first') {
isFirst = true;
} else if (arg === 'redis') {
isRedis = true;
}
}
// and as --flag
isRedis = params.redis || isRedis;
isFirst = params.first || isFirst;
setup.setup(async (isFirst, _isRedis) => {
if (isFirst) {
// Creates all instances that are needed on a fresh installation
const createInitialInstances = async () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
installNpm,
getRepository,
processExit: callback,
params
});
// In order to loop the instance creation, we need a promisified version of the method
const createInstanceAsync = tools.promisifyNoError(install.createInstance, install);
// Define the necessary instances
const initialInstances = ['admin', 'discovery', 'backitup'];
// And try to install each of them
for (const instance of initialInstances) {
try {
const path = require.resolve(tools.appName + '.' + instance);
if (path) {
await createInstanceAsync(instance, {enabled: true, ignoreIfExists: true});
}
} catch {
// not found, just continue
}
}
};
createInitialInstances()
.then(() => new Promise(resolve => {
// Creates a fresh certificate
const Cert = require('./cli/cliCert');
// Create a new instance of the cert command,
// but use the resolve method as a callback
const cert = new Cert(Object.assign({}, commandOptions, {callback: resolve}));
cert.create();
}))
.then(() => callback && callback());
} else {
// else we update existing stuff (this is executed on installation)
// Rename repositories
const Repo = require('./setup/setupRepo.js');
const repo = new Repo({
objects: objects,
states: states
});
try {
await repo.rename('default', 'stable', 'http://download.iobroker.net/sources-dist.json');
await repo.rename('latest', 'beta', 'http://download.iobroker.net/sources-dist-latest.json');
} catch (e) {
console.warn(e.message);
}
try {
const configFile = tools.getConfigFileName();
const configOrig = fs.readJSONSync(configFile);
const config = deepClone(configOrig);
config.objects.options = config.objects.options || {
'auth_pass' : null,
'retry_max_delay' : 5000
};
if (config.objects.options.retry_max_delay === 15000 || !config.objects.options.retry_max_delay) {
config.objects.options.retry_max_delay = 5000;
}
config.states.options = config.states.options || {
'auth_pass' : null,
'retry_max_delay' : 5000
};
if (config.states.options.retry_max_delay === 15000 || !config.states.options.retry_max_delay) {
config.states.options.retry_max_delay = 5000;
}
if (!isDeepStrictEqual(config, configOrig)) {
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
console.log('ioBroker configuration updated');
}
} catch(err) {
console.log('Could not update ioBroker configuration: ' + err.message);
}
return void callback();
}
}, isFirst, isRedis);
}
break;
}
case 'url': {
Objects = require('./objects');
let url = args[0];
const name = args[1];
if (!url) {
console.log('Please provide a URL to install from and optionally a name of the adapter to install');
callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (url[0] === '"' && url[url.length - 1] === '"') {
url = url.substring(1, url.length - 1);
}
url = url.trim();
dbConnect(params, () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
installNpm,
getRepository,
processExit: callback,
params
});
install.installAdapterFromUrl(url, name, callback);
});
break;
}
case 'info': {
Objects = require('./objects');
dbConnect(params, objects => {
tools.getHostInfo(objects, (err, data) => {
if (err) {
console.error('Cannot read host info: '+ err);
if (!data) {
return callback(EXIT_CODES.CANNOT_GET_HOST_INFO);
}
}
const formatters = require('./formatters');
const formatInfo = {
'Uptime': formatters.formatSeconds,
'System uptime': formatters.formatSeconds,
'RAM': formatters.formatRam,
'Speed': formatters.formatSpeed,
'Disk size': formatters.formatBytes,
'Disk free': formatters.formatBytes
};
for (const attr of Object.keys(data)) {
console.log(`${attr}${attr.length < 16 ? new Array(16 - attr.length).join(' ') : ''}: ${formatInfo[attr] ? formatInfo[attr](data[attr]) : data[attr] || ''}`);
}
return void callback();
});
});
break;
}
case 'a':
case 'add':
case 'install':
case 'i': {
Objects = require('./objects');
let name = args[0];
let instance = args[1];
let repoUrl = args[2];
if (instance === 0) {
instance = '0';
}
if (repoUrl === 0) {
repoUrl = '0';
}
if (parseInt(instance, 10).toString() !== (instance || '').toString()) {
repoUrl = instance;
instance = null;
}
if (parseInt(repoUrl, 10).toString() === (repoUrl || '').toString()) {
const temp = instance;
instance = repoUrl;
repoUrl = temp;
}
if (parseInt(instance, 10).toString() === (instance || '').toString()) {
instance = parseInt(instance, 10);
params.instance = instance;
}
// If user accidentally wrote tools.appName.adapter => remove adapter
name = cli.tools.normalizeAdapterName(name);
const parsedName = cli.tools.splitAdapterOrInstanceIdentifierWithVersion(name);
if (!parsedName) {
console.log('Invalid adapter name for install');
showHelp();
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
// split the adapter into its parts if necessary
if (parsedName.instance !== null) {
params.instance = parsedName.instance;
}
name = parsedName.name;
const installName = parsedName.nameWithVersion;
const adapterDir = tools.getAdapterDir(name);
dbConnect(params, async () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
installNpm,
getRepository,
processExit: callback,
params
});
if (params.host && params.host !== tools.getHostName()) {
// if host argument provided we should check, that host actually exists in mh environment
let obj;
try {
obj = await objects.getObjectAsync(`system.host.${params.host}`);
} catch (e) {
console.warn(`Could not check existence of host "${params.host}": ${e.message}`);
}
if (!obj) {
console.error(`Cannot add instance to non-existing host "${params.host}"`);
return void callback(EXIT_CODES.NON_EXISTING_HOST);
}
}
if (!fs.existsSync(adapterDir)) {
install.downloadPacket(repoUrl, installName, null, enableAdapterCallback => {
install.installAdapter(installName, repoUrl, () => {
enableAdapterCallback(() => {
if (command !== 'install' && command !== 'i') {
install.createInstance(name, params, callback);
} else {
return void callback();
}
});
});
});
} else {
if (command !== 'install' && command !== 'i') {
install.createInstance(name, params, callback);
} else {
console.log(`adapter "${name}" already installed. Use "upgrade" to upgrade to a newer version.`);
return void callback(EXIT_CODES.ADAPTER_ALREADY_INSTALLED);
}
}
});
break;
}
case 'rebuild': {
let name = args[0];
// If user accidentally wrote tools.appName.adapter => remove adapter
name = cli.tools.normalizeAdapterName(name);
if (name.indexOf('@') !== -1) {
name = name.split('@')[0];
}
if (!name) {
console.log('Please provide the name of the adapter to rebuild');
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
const rebuildCommand = params.install ? 'install' : 'rebuild';
installNpm(name, rebuildCommand, (err, _adapter) => {
if (err) {
processExit(err);
} else {
console.log();
console.log('Rebuild ' + name + ' done');
return void callback();
}
});
break;
}
case 'upload':
case 'u': {
Objects = require('./objects');
const name = args[0];
const subTree = args[1];
if (name) {
dbConnect(params, () => {
const Upload = require('./setup/setupUpload.js');
const upload = new Upload({
states: states,
objects: objects
});
if (name === 'all') {
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (_err, objs) => {
const adapters = [];
for (let i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'adapter') {
continue;
}
adapters.push(objs.rows[i].value.common.name);
}
upload.uploadAdapterFull(adapters, callback);
});
} else {
// if upload of file
if (name.indexOf('.') !== -1) {
if (!subTree) {
console.log('Please specify target name, like:\n ' + tools.appName + ' upload /file/picture.png /vis.0/main/img/picture.png');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
upload.uploadFile(name, subTree, (err, newName) => {
!err && console.log('File "' + name + '" is successfully saved under ' + newName);
return void callback(err ? EXIT_CODES.CANNOT_UPLOAD_DATA : undefined);
});
} else {
if (subTree) {
upload.uploadAdapter(name, false, true, subTree, callback);
} else {
upload.uploadAdapterFull([name], callback);
}
}
}
});
} else {
console.log('No adapter name found!');
showHelp();
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
break;
}
case 'delete':
case 'del': {
let adapter = args[0];
let instance = args[1];
// The adapter argument is required
if (!adapter) {
showHelp();
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
// If the user accidentally wrote <tools.appName>.adapter,
// remove <tools.appName> from the adapter name
adapter = cli.tools.normalizeAdapterName(adapter);
// Avoid deleting stuff we don't want to delete
// e.g. `system.adapter.*`
if (!instance) {
// Ensure that adapter contains a valid adapter (without instance nr)
// or instance (with instance nr) identifier
if (!cli.tools.validateAdapterOrInstanceIdentifier(adapter)) {
showHelp();
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
// split the adapter into adapter + instance if necessary
if (adapter.indexOf('.') > -1) {
([adapter, instance] = adapter.split('.', 2));
}
} else {
// ensure that adapter contains a valid adapter identifier
// and the instance is a number
if (
!cli.tools.validateAdapterIdentifier(adapter)
|| !/^\d+$/.test(instance)
) {
showHelp();
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
}
if (instance || instance === 0) {
dbConnect(params, () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
installNpm,
getRepository,
processExit: callback,
params
});
console.log('Delete adapter "' + adapter + '.' + instance + '"');
install.deleteInstance(adapter, instance, callback);
});
} else {
dbConnect(params, () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
installNpm,
getRepository,
processExit: callback,
params
});
console.log('Delete adapter "' + adapter + '"');
install.deleteAdapter(adapter, (_a, resultCode) => void callback(resultCode));
});
}
break;
}
case 'unsetup': {
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
if (params.yes || params.y || params.Y) {
unsetup(params, callback);
} else {
rl.question('UUID will be deleted. Are you sure? [y/N]: ', answer => {
rl.close();
answer = answer.toLowerCase();
if (answer === 'y' || answer === 'yes' || answer === 'ja' || answer === 'j') {
unsetup(params, callback);
} else {
console.log('Nothing deleted');
return void callback();
}
});
}
break;
}
case 'o':
case 'object': {
const objectsCommand = new cli.command.object(commandOptions);
objectsCommand.execute(args);
break;
}
case 's':
case 'state': {
const statesCommand = new cli.command.state(commandOptions);
statesCommand.execute(args);
break;
}
case 'msg':
case 'message': {
const messageCommand = new cli.command.message(commandOptions);
messageCommand.execute(args);
break;
}
case 'logs': {
const logsCommand = new cli.command.logs(commandOptions);
logsCommand.execute(args, params);
break;
}
case 'upgrade': {
Objects = require('./objects');
let adapter = cli.tools.normalizeAdapterName(args[0]);
let repoUrl = args[1];
if (adapter && !repoUrl && adapter.indexOf('/') !== -1) {
repoUrl = adapter;
adapter = null;
}
if (adapter === 'all') {
adapter = null;
}
dbConnect(params, () => {
const Upgrade = require('./setup/setupUpgrade.js');
const upgrade = new Upgrade({
objects,
states,
installNpm,
getRepository,
params,
processExit: callback,
restartController
});
if (adapter) {
if (adapter === 'self') {
states.getState(`system.host.${tools.getHostName()}.alive`, (err, hostAlive) =>
upgrade.upgradeController(repoUrl, params.force || params.f, hostAlive && hostAlive.val, callback));
} else {
upgrade.upgradeAdapter(repoUrl, adapter, params.force || params.f, params.y || params.yes, false, callback);
}
} else {
// upgrade all
getRepository(repoUrl, (err, links) => {
const result = [];
for (const name of Object.keys(links)) {
result.push(name);
}
if (err) {
console.log(err);
}
if (links) {
result.sort();
upgrade.upgradeAdapterHelper(links, result, false, params.y || params.yes, callback);
} else {
// No information
return void callback(EXIT_CODES.INVALID_REPO);
}
});
}
});
break;
}
case 'clean': {
const yes = args[0];
if (yes !== 'yes') {
console.log(`Command "clean" clears all Objects and States. To execute it write "${tools.appName} clean yes"`);
} else {
dbConnect(params, (_obj, _stat, isNotRun) => {
if (!isNotRun) {
console.error(`Stop ${tools.appName} first!`);
return void callback(EXIT_CODES.CONTROLLER_RUNNING);
}
cleanDatabase(true, count => {
console.log('Deleted ' + count + ' states');
restartController(() => {
console.log('Restarting ' + tools.appName + '...');
return void callback();
});
});
});
}
break;
}
case 'restore': {
const Backup = require('./setup/setupBackup.js');
dbConnect(params, (_obj, _stat, isNotRun) => {
if (!isNotRun) {
console.error('Stop ' + tools.appName + ' first!');
return void callback(EXIT_CODES.CONTROLLER_RUNNING);
}
const backup = new Backup({
states: states,
objects: objects,
cleanDatabase: cleanDatabase,
restartController: restartController,
processExit: callback
});
backup.restoreBackup(args[0], () => {
console.log('System successfully restored!');
return void callback(EXIT_CODES.NO_ERROR);
});
});
break;
}
case 'backup': {
const name = args[0];
const Backup = require('./setup/setupBackup.js');
dbConnect(params, () => {
const backup = new Backup({
states: states,
objects: objects,
cleanDatabase: cleanDatabase,
restartController: restartController,
processExit: callback
});
backup.createBackup(name, filePath => {
console.log('Backup created: ' + filePath);
return void callback(EXIT_CODES.NO_ERROR);
});
});
break;
}
case 'validate': {
const name = args[0];
const Backup = require('./setup/setupBackup.js');
dbConnect(params, () => {
const backup = new Backup({
states: states,
objects: objects,
cleanDatabase: cleanDatabase,
restartController: restartController,
processExit: callback
});
backup.validateBackup(name).then(() => {
console.log('Backup OK');
processExit(0);
}).catch(e => {
console.log(`Backup check failed: ${e.message}`);
processExit(1);
});
});
break;
}
case 'l':
case 'list': {
dbConnect(params, (_objects, _states, _isOffline, _objectsType, config) => {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback,
config: config
});
list.list(args[0], args[1], params);
});
break;
}
case 'touch': {
let pattern = args[0];
if (!pattern) {
console.log('No file path found. Example: "touch /vis.0/main/*"');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
dbConnect(params, () => {
// extract id
pattern = pattern.replace(/\\/g, '/');
if (pattern[0] === '/') {
pattern = pattern.substring(1);
}
if (pattern === '*') {
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => {
if (!err && arr && arr.rows) {
const files = [];
let count = 0;
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'adapter') {
continue;
}
count++;
objects.touch(arr.rows[i].value.common.name, '*', {user: 'system.user.admin'}, (err, processed, _id) => {
if (!err && processed) {
files.push({id: _id, processed: processed});
}
if (!--count) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
files.sort((a, b) => a.id.localeCompare(b.id));
for (let k = 0; k < files.length; k++) {
for (let t = 0; t < files[k].processed.length; t++) {
list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]);
}
}
setTimeout(callback, 1000);
}
});
}
if (!count) {
console.log('Nothing found');
return void callback();
}
}
});
} else {
const parts = pattern.split('/');
const id = parts.shift();
const path = parts.join('/');
objects.touch(id, path, {user: 'system.user.admin'}, (err, processed) => {
if (err) {
console.error(err);
} else {
if (processed) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
for (let i = 0; i < processed.length; i++) {
list.showFile(id, processed[i].path, processed[i]);
}
}
}
setTimeout(callback, 1000);
});
}
});
break;
}
case 'rm': {
let pattern = args[0];
if (!pattern) {
console.log('No file path found. Example: "touch /vis.0/main/*"');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
dbConnect(params, () => {
// extract id
pattern = pattern.replace(/\\/g, '/');
if (pattern[0] === '/') {
pattern = pattern.substring(1);
}
if (pattern === '*') {
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => {
if (!err && arr && arr.rows) {
const files = [];
let count = 0;
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'adapter') {
continue;
}
count++;
objects.rm(arr.rows[i].value.common.name, '*', {user: 'system.user.admin'}, (err, processed, _id) => {
if (!err && processed) {
files.push({id: _id, processed: processed});
}
if (!--count) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
files.sort((a, b) => a.id.localeCompare(b.id));
list.showFileHeader();
for (let k = 0; k < files.length; k++) {
for (let t = 0; t < files[k].processed.length; t++) {
list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]);
}
}
setTimeout(callback, 1000);
}
});
}
if (!count) {
console.log('Nothing found');
return void callback();
}
}
});
} else {
const parts = pattern.split('/');
const id = parts.shift();
const path = parts.join('/');
objects.rm(id, path, {user: 'system.user.admin'}, (err, processed) => {
if (err) {
console.error(err);
} else {
if (processed) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
list.showFileHeader();
for (let i = 0; i < processed.length; i++) {
list.showFile(id, processed[i].path, processed[i]);
}
}
}
setTimeout(callback, 1000);
});
}
});
break;
}
case 'chmod': {
let mode = args[0];
let pattern = args[1];
if (!mode) {
cli.error.requiredArgumentMissing('mode', 'chmod 777 /vis.0/main/*');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} else {
//yargs has converted it to number
mode = parseInt(mode.toString(), 16);
}
if (!pattern) {
cli.error.requiredArgumentMissing('file path', 'chmod 777 /vis.0/main/*');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
dbConnect(params, () => {
// extract id
pattern = pattern.replace(/\\/g, '/');
if (pattern[0] === '/') {
pattern = pattern.substring(1);
}
if (pattern === '*') {
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => {
if (!err && arr && arr.rows) {
const files = [];
let count = 0;
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'adapter') {
continue;
}
count++;
objects.chmodFile(arr.rows[i].value.common.name, '*', {user: 'system.user.admin', mode}, (err, processed, _id) => {
if (!err && processed) {
files.push({id: _id, processed: processed});
}
if (!--count) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
files.sort((a, b) => a.id.localeCompare(b.id));
list.showFileHeader();
for (let k = 0; k < files.length; k++) {
for (let t = 0; t < files[k].processed.length; t++) {
list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]);
}
}
setTimeout(callback, 1000);
}
});
}
if (!count) {
console.log('Nothing found');
return void callback();
}
}
});
} else {
const parts = pattern.split('/');
const id = parts.shift();
const path = parts.join('/');
objects.chmodFile(id, path, {user: 'system.user.admin', mode: mode}, (err, processed) => {
if (err) {
console.error(err);
} else {
if (processed) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
list.showFileHeader();
for (let i = 0; i < processed.length; i++) {
list.showFile(id, processed[i].path, processed[i]);
}
}
}
setTimeout(callback, 1000);
});
}
});
break;
}
case 'chown': {
let user = args[0];
let group = args[1];
let pattern = args[2];
if (!pattern) {
pattern = group;
group = undefined;
}
if (!user) {
cli.error.requiredArgumentMissing('user', 'chown user /vis.0/main/*');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} else if (user.substring(12) !== 'system.user.') {
user = 'system.user.' + user;
}
if (group && group.substring(13) !== 'system.group.') {
group = 'system.group.' + group;
}
if (!pattern) {
cli.error.requiredArgumentMissing('file path', 'chown user /vis.0/main/*');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
dbConnect(params, () => {
// extract id
pattern = pattern.replace(/\\/g, '/');
if (pattern[0] === '/') {
pattern = pattern.substring(1);
}
if (pattern === '*') {
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, (err, arr) => {
if (!err && arr && arr.rows) {
const files = [];
let count = 0;
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'adapter') {
continue;
}
count++;
objects.chownFile(arr.rows[i].value.common.name, '*', {user: 'system.user.admin', owner: user, ownerGroup: group}, (err, processed, _id) => {
if (!err && processed) {
files.push({id: _id, processed: processed});
}
if (!--count) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
files.sort((a, b) => a.id.localeCompare(b.id));
list.showFileHeader();
for (let k = 0; k < files.length; k++) {
for (let t = 0; t < files[k].processed.length; t++) {
list.showFile(files[k].id, files[k].processed[t].path, files[k].processed[t]);
}
}
setTimeout(callback, 1000);
}
});
}
if (!count) {
console.log('Nothing found');
return void callback();
}
}
});
} else {
const parts = pattern.split('/');
const id = parts.shift();
const path = parts.join('/');
objects.chownFile(id, path, {user: 'system.user.admin', owner: user, ownerGroup: group}, (err, processed) => {
if (err) {
console.error(err);
} else {
// call here list
if (processed) {
const List = require('./setup/setupList.js');
const list = new List({
states: states,
objects: objects,
processExit: callback
});
list.showFileHeader();
for (let i = 0; i < processed.length; i++) {
list.showFile(id, processed[i].path, processed[i]);
}
}
}
setTimeout(callback, 1000);
});
}
});
break;
}
case 'user': {
const command = args[0] || '';
let user = args[1] || '';
if (user && user.match(/^system\.user\./)) {
user = user.substring('system.user.'.length);
}
dbConnect(params, () => {
const Users = require('./setup/setupUsers.js');
const users = new Users({
objects: objects,
processExit: callback
});
const password = params.password;
const group = params.ingroup || 'system.group.administrator';
if (command === 'add') {
users.addUserPrompt(user, group, password, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" created (Group: ' + group.replace('system.group.', '') + ')');
return void callback();
}
});
} else if (command === 'del' || command === 'delete') {
users.delUser(user, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" deleted');
return void callback();
}
});
} else if (command === 'check') {
users.checkUserPassword(user, password, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Password for user "' + user + '" matches.');
return void callback();
}
});
} else if (command === 'set' || command === 'passwd') {
users.setUserPassword(user, password, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Password for "' + user + '" was successfully set.');
return void callback();
}
});
} else if (command === 'enable' || command === 'e') {
users.enableUser(user, true, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" was successfully enabled.');
return void callback();
}
});
} else if (command === 'disable' || command === 'd') {
users.enableUser(user, false, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" was successfully disabled.');
return void callback();
}
});
} else if (command === 'get') {
users.getUser(user, (err, isEnabled) => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" is ' + (isEnabled ? 'enabled' : 'disabled'));
return void callback();
}
});
} else {
console.warn(`Unknown command "${command}". Available commands are: add, del, passwd, enable, disable, check, get`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
});
break;
}
case 'g':
case 'group': {
const command = args[0] || '';
let group = args[1] || '';
let user = args[2] || '';
if (group && group.match(/^system\.group\./)) {
group = group.substring('system.group.'.length);
}
if (user && user.match(/^system\.user\./)) {
user = user.substring('system.user.'.length);
}
if (!command) {
console.warn('Unknown command "' + command + '". Available commands are: add, del, passwd, enable, disable, list, get');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (!group) {
console.warn('Please define group name: group ' + command + ' groupName');
return callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
}
dbConnect(params, () => {
const Users = require('./setup/setupUsers.js');
const users = new Users({
objects: objects,
processExit: callback
});
if (command === 'useradd' || command === 'adduser') {
if (!user) {
console.warn('Please define user name: group useradd groupName userName');
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
}
users.addUserToGroup(user, group, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" created');
return void callback();
}
});
} else if (command === 'userdel' || command === 'deluser') {
if (!user) {
console.warn('Please define user name: group userdel groupName userName');
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
}
users.removeUserFromGroup(user, group, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" created');
return void callback();
}
});
} else if (command === 'add') {
users.addGroup(group, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + group + '" created');
return void callback();
}
});
} else if (command === 'del' || command === 'delete') {
users.delGroup(group, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + group + '" deleted');
return void callback();
}
});
} else if (command === 'list' || command === 'l') {
users.getGroup(group, (err, isEnabled, list) => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Group "' + group + '" is ' + (isEnabled ? 'enabled' : 'disabled') + ' and has following members:');
if (list) {
for (let i = 0; i < list.length; i++) {
console.log(list[i].substring('system.user.'.length));
}
}
return void callback();
}
});
} else if (command === 'enable' || command === 'e') {
users.enableGroup(group, true, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Group "' + group + '" was successfully enabled.');
return void callback();
}
});
} else if (command === 'disable' || command === 'd') {
users.enableGroup(group, false, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Group "' + group + '" was successfully disabled.');
return void callback();
}
});
} else if (command === 'get') {
users.getGroup(group, (err, isEnabled, _list) => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Group "' + group + '" is ' + (isEnabled ? 'enabled' : 'disabled'));
return void callback();
}
});
} else {
console.warn('Unknown command "' + command + '". Available commands are: add, del, passwd, enable, disable, list, get');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
});
break;
}
case 'adduser': {
const user = args[0];
const group = params.ingroup || 'system.group.administrator';
const password = params.password;
dbConnect(params, () => {
const Users = require('./setup/setupUsers.js');
const users = new Users({
objects: objects,
processExit: callback
});
users.addUserPrompt(user, group, password, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" created (Group: ' + group.replace('system.group.', '') + ')');
return void callback();
}
});
});
break;
}
case 'passwd': {
const user = args[0];
const password = params.password;
dbConnect(params, () => {
const Users = require('./setup/setupUsers.js');
const users = new Users({
objects: objects,
processExit: callback
});
users.setUserPassword(user, password, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('Password for "' + user + '" was successfully set.');
return void callback();
}
});
});
break;
}
case 'ud':
case 'udel':
case 'userdel':
case 'deluser': {
const user = args[0];
dbConnect(params, () => {
const Users = require('./setup/setupUsers.js');
const users = new Users({
objects: objects,
processExit: callback
});
users.delUser(user, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
console.log('User "' + user + '" deleted');
return void callback();
}
});
});
break;
}
// Create package.json in /opt/' + tools.appName + '
case 'package': {
const json = {
name: tools.appName,
engines: {
node: '>=6'
},
optionalDependencies: {
},
dependencies: {},
author: 'bluefox <dogafox@gmail.com>'
};
json.dependencies[tools.appName + '.js-controller'] = '*';
json.dependencies[tools.appName + '.admin'] = '*';
tools.getRepositoryFile(null, null, (_err, sources, _sourcesHash) => {
if (sources) {
for (const s in sources) {
if (Object.prototype.hasOwnProperty.call(sources, s)) {
if (sources[s].url) {
if (!json.dependencies[tools.appName + '.' + s]) {
json.optionalDependencies[tools.appName + '.' + s] = sources[s].url;
}
} else {
if (!json.dependencies[tools.appName + '.' + s]) {
json.optionalDependencies[tools.appName + '.' + s] = '*';
}
}
}
}
}
fs.writeFileSync(__dirname + '/../../../package.json', JSON.stringify(json, null, 2));
return void callback();
});
break;
}
case 'set': {
const instance = args[0];
if (!instance) {
console.warn('please specify instance.');
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
if (instance.indexOf('.') === -1) {
console.warn('please specify instance, like "' + instance + '.0"');
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
dbConnect(params, () => {
objects.getObject('system.adapter.' + instance, (err, obj) => {
if (!err && obj) {
let changed = false;
for (let a = 0; a < process.argv.length; a++) {
if (process.argv[a].match(/^--/) && process.argv[a + 1] && !process.argv[a + 1].match(/^--/)) {
const attr = process.argv[a].substring(2);
/** @type {number | string | boolean} */
let val = process.argv[a + 1];
if (val === '__EMPTY__') {
val = '';
} else if (val === 'true') {
val = true;
} else if (val === 'false') {
val = false;
} else if (parseFloat(val).toString() === val) {
val = parseFloat(val);
}
if (attr.indexOf('.') !== -1) {
const parts = attr.split('.');
if (!obj.native[parts[0]] || obj.native[parts[0]][parts[1]] === undefined) {
console.warn('Adapter "' + instance + '" has no setting "' + attr + '".');
} else {
changed = true;
obj.native[parts[0]][parts[1]] = val;
console.log('New ' + attr + ' for "' + instance + '" is: ' + val);
}
} else {
if (obj.native[attr] === undefined) {
console.warn('Adapter "' + instance + '" has no setting "' + attr + '".');
} else {
changed = true;
obj.native[attr] = val;
console.log('New ' + attr + ' for "' + instance + '" is: ' + val);
}
}
a++;
}
}
if (changed) {
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.adapter.' + instance, obj, () => {
console.log('Instance settings for "' + instance + '" are changed.');
return void callback();
});
} else {
console.log('No parameters set.');
return void callback();
}
} else {
cli.error.invalidInstance(instance);
return void callback(EXIT_CODES.INVALID_ADAPTER_ID);
}
});
});
break;
}
case 'host': {
const hostCommand = new cli.command.host(commandOptions);
hostCommand.execute(args);
break;
}
case 'visdebug': {
let widgetset = args[0];
if (widgetset && widgetset.match('/^vis-/')) {
widgetset = widgetset.substring(4);
}
const VisDebug = require('./setup/setupVisDebug.js');
dbConnect(params, _objects => {
const visDebug = new VisDebug({
objects: _objects,
processExit: callback
});
visDebug.enableDebug(widgetset);
});
break;
}
case 'file':
case 'f': {
const cmd = args[0];
if (cmd !== 'read' && cmd !== 'r' && cmd !== 'w' && cmd !== 'write' && cmd !== 'sync' && cmd !== 'rm' && cmd !== 'unlink' && cmd !== 'del') {
console.log('Invalid parameters: write "file read /vis.0/main/img/picture.png /opt/picture/image.png" to read the file');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (cmd !== 'sync' && !args[1]) {
console.log('Invalid parameters: write "file read /vis.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk');
console.log('or "file write /opt/SOURCE/image.png /vis.0/main/DESTINATION/picture.png" to write the file into DB from disk');
console.log('or "file rm /vis.0/main/img/picture.png" to delete the file in DB');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
dbConnect(params, async (objects, _states, isOffline, objectType) => {
if (cmd === 'read' || cmd ==='r') {
const toRead = args[1];
const parts = toRead.replace(/\\/g, '/').split('/');
const path = (args[2] || process.cwd()).replace(/\\/g, '/').split('/');
const file = path[path.length - 1];
if (!file.match(/\.[a-zA-Z0-9]+$/)) { // If destination location seems to be a directory, add filename
if (file !== '') {
path.push(parts[parts.length - 1]);
} else { // trailing slash
path[path.length - 1] = parts[parts.length - 1];
}
}
let adapt = parts.shift();
if (!adapt) { // leading slash
adapt = parts.shift();
}
if (!adapt) {
console.log(`Invalid parameters: adapter cannot be found!`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (!parts.length) {
console.log('Invalid parameters: file cannot be found: file not provided');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
objects.readFile(adapt, parts.join('/'), (err, data) => {
err && console.error(err);
if (data) {
const destFilename = path.join('/');
fs.writeFileSync(destFilename, data);
console.log('File "' + toRead + '" stored as "' + destFilename + '"');
}
return void callback(EXIT_CODES.NO_ERROR);
});
} else if (cmd === 'write' || cmd ==='w') {
const toRead = args[1] || '';
const parts = toRead.replace(/\\/g, '/').split('/');
const path = (args[2] || '').replace(/\\/g, '/').split('/');
let adapt = path.shift();
if (!adapt) {
adapt = path.shift();
}
if (!path.length) {
path.push('');
}
const fileSrc = parts[parts.length - 1];
let fileDest = path[path.length - 1];
if (!fileDest || !fileDest.match(/\.[a-zA-Z0-9]+$/)) { // last portion of destination has no extension, consider being a directory
fileDest = '';
}
if (!fileSrc || !fs.existsSync(toRead)) {
console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis/DESTINATION/script.js"`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
const srcStat = fs.statSync(toRead);
if (!srcStat.isFile()) {
console.log(`Please provide a valid file name as source file: "file write /opt/SOURCE/script.js /vis/DESTINATION/script.js"`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (!fileDest) { // destination filename is not given, use same name as source file
fileDest = fileSrc;
}
if (fileDest !== path[path.length - 1]) { // if last part of path is different then filename, add filename
if (path[path.length - 1] !== '') {
path.push(fileDest);
} else { // trailing slash
path[path.length - 1] = fileDest;
}
}
const destFilename = path.length ? path.join('/') : '/';
const data = fs.readFileSync(toRead);
if (!adapt) {
console.log('Invalid parameters: destination adapter cannot be found!');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
objects.writeFile(adapt, destFilename, data, _err => {
console.log('File "' + toRead + '" stored as "' + destFilename + '"');
return void callback(EXIT_CODES.NO_ERROR);
});
} else if (cmd === 'del' || cmd ==='rm' || cmd ==='unlink') {
const toDelete = args[1];
const parts = toDelete.replace(/\\/g, '/').split('/');
let adapt = parts.shift();
if (!adapt) { // leading slash
adapt = parts.shift();
}
if (!adapt) {
console.log('Invalid parameters: adapter cannot be found!');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (!parts.length) {
console.log('Invalid parameters: file cannot be found: file not provided');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
objects.unlink(adapt, parts.join('/'), err => {
err && console.error(err);
!err && console.log('File "' + toDelete + '" was deleted');
return void callback(EXIT_CODES.NO_ERROR);
});
} else if (cmd === 'sync') { // Sync
if (objectType !== 'file') {
console.log('File Sync is only available when database type "file" is used.');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
if (!objects.syncFileDirectory || !objects.dirExists) { // functionality only exists in server class
console.log('Please stop ioBroker before syncing files and only use this command on the ioBroker master host!');
return void callback(EXIT_CODES.CONTROLLER_RUNNING);
}
// check meta.user
try {
const objExists = await objects.objectExists('meta.user');
if (objExists) {
// check if dir is missing
const dirExists = objects.dirExists('meta.user');
if (!dirExists) {
// create meta.user, so users see them as upload target
await objects.mkdirAsync('meta.user');
console.log('Successfully created "meta.user" directory');
}
}
} catch (e) {
console.warn(`Could not create directory "meta.user": ${e.message}`);
}
try {
const {numberSuccess, notifications} = objects.syncFileDirectory(args[1]);
console.log(`${numberSuccess} file(s) successfully synchronized with ioBroker storage`);
if (notifications.length) {
console.log();
console.log('The following notifications happened during sync: ');
notifications.forEach(el => console.log('- ' + el));
}
return void callback(EXIT_CODES.NO_ERROR);
} catch (err) {
console.error(`Error on sync: ${err.message}. Partial content might have been synced.`);
return void callback(EXIT_CODES.CANNOT_SYNC_FILES);
}
} else {
console.log('Invalid parameters: write "file read /vis.0/main/img/picture.png /opt/picture/image.png" to read the file from DB and store it on disk');
console.log('or "file write /opt/SOURCE/image.png /vis.0/main/DESTINATION/picture.png" to write the file into DB from disk');
console.log('or "file rm /vis.0/main/img/picture.png" to delete the file in DB');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
});
break;
}
case 'id':
case 'uuid': {
dbConnect(params, objects => {
objects.getObject('system.meta.uuid', (err, obj) => {
if (err) {
console.error('Error: ' + err);
return void callback(EXIT_CODES.CANNOT_GET_UUID);
}
if (obj && obj.native) {
console.log(obj.native.uuid);
return void callback();
} else {
console.error('Error: no UUID found');
return void callback(EXIT_CODES.CANNOT_GET_UUID);
}
});
});
break;
}
case 'v':
case 'version': {
const adapter = args[0];
let iopckg;
if (adapter) {
try {
iopckg = require(tools.appName + '.' + adapter + '/package.json');
} catch {
iopckg = {version: '"' + adapter + '" not found'};
}
} else {
iopckg = require('../package.json');
}
console.log(iopckg.version);
return void callback();
}
case 'checklog': {
dbConnect(params, (objects, states, isOffline, objectType) => {
if (isOffline && tools.objectsDbHasServer(objectType)) {
console.log(tools.appName + ' is not running');
return void callback(EXIT_CODES.CONTROLLER_NOT_RUNNING);
} else {
console.log(tools.appName + ' is running');
objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.' + '\u9999'}, null, (err, res) => {
if (!err && res.rows.length) {
for (let i = 0; i < res.rows.length; i++) {
const parts = res.rows[i].id.split('.');
// ignore system.host.name.alive and so on
if (parts.length === 3) {
states.pushMessage(res.rows[i].id, {command: 'checkLogging', message: null, from: 'console'});
}
}
}
setTimeout(callback, 200);
});
}
});
break;
}
case 'repo': {
Objects = require('./objects');
let repoUrlOrCommand = args[0]; // Repo url or name or "add" / "del" / "set" / "show" / "addset"
const repoName = args[1]; // Repo url or name
let repoUrl = args[2]; // Repo url or name
if (repoUrlOrCommand !== 'add' && repoUrlOrCommand !== 'del' && repoUrlOrCommand !== 'set' && repoUrlOrCommand !== 'show' && repoUrlOrCommand !== 'addset') {
repoUrl = repoUrlOrCommand;
repoUrlOrCommand = 'show';
}
dbConnect(params, (_objects, _states) => {
const Repo = require('./setup/setupRepo.js');
const repo = new Repo({
objects: _objects,
states: _states
});
if (repoUrlOrCommand === 'show') {
repo.showRepoStatus(callback);
} else if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'del' || repoUrlOrCommand === 'set' || repoUrlOrCommand === 'addset') {
if (!repoName || !repoName.match(/[-_\w\d]+/)) {
console.error(`Invalid repository name: "${repoName}"`);
return void callback();
} else {
if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'addset') {
if (!repoUrl) {
console.warn(`Please define repository URL or path: ${tools.appName} add <repoName> <repoUrlOrPath>`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} else {
repo.add(repoName, repoUrl, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.INVALID_REPO);
} else {
if (repoUrlOrCommand === 'addset') {
repo.setActive(repoName, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.INVALID_REPO);
} else {
console.log('Repository "' + repoName + '" set as active: "' + repoUrl + '"');
repo.showRepoStatus(callback);
}
});
} else {
console.log('Repository "' + repoName + '" added as "' + repoUrl + '"');
repo.showRepoStatus(callback);
}
}
});
}
} else if (repoUrlOrCommand === 'set') {
repo.setActive(repoName, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.INVALID_REPO);
} else {
console.log('Repository "' + repoName + '" set as active.');
repo.showRepoStatus(callback);
}
});
} else if (repoUrlOrCommand === 'del') {
repo.del(repoName, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.INVALID_REPO);
} else {
console.log('Repository "' + repoName + '" deleted.');
repo.showRepoStatus(callback);
}
});
} else {
console.warn('Unknown repo command: ' + repoUrlOrCommand);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
}
}
});
break;
}
case 'multihost':
case 'mh': {
const cmd = args[0];
if (cmd !== 'c' && cmd !== 'connect' && cmd !== 's' && cmd !== 'status' && cmd !== 'b' && cmd !== 'browse' && cmd !== 'e' && cmd !== 'enable' && cmd !== 'd' && cmd !== 'disable') {
console.log('Invalid parameters. Following is possible: enable, browse, connect, status');
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} else {
dbConnect(params, () => {
const Multihost = require('./setup/setupMultihost.js');
const mh = new Multihost({
params: params,
processExit: callback,
objects: objects
});
if (cmd === 's' || cmd === 'status') {
mh.status(() => void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP));
} else
if (cmd === 'b' || cmd === 'browse') {
mh.browse((err, list) => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_CREATE_USER_OR_GROUP);
} else {
mh.showHosts(list);
return void callback();
}
});
} else if (cmd === 'e' || cmd === 'enable') {
mh.enable(true, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_ENABLE_MULTIHOST);
} else {
states.pushMessage(`system.host.${tools.getHostName()}`, {
command: 'updateMultihost',
message: null,
from: 'setup'
}, callback);
}
});
} else if (cmd === 'd' || cmd === 'disable') {
mh.enable(false, err => {
if (err) {
console.error(err);
return void callback(EXIT_CODES.CANNOT_ENABLE_MULTIHOST);
} else {
states.pushMessage(`system.host.${tools.getHostName()}`, {command: 'updateMultihost', message: null, from: 'setup'}, callback);
}
});
} else if (cmd === 'c' || cmd === 'connect') {
mh.connect(args[1], args[2], err => {
if (err) {
console.error(err);
}
return void callback(err ? 1 : 0);
});
}
});
}
break;
}
case 'vendor': {
const password = args[0];
const file = args[1];
if (!password) {
console.warn(`Please specify the password to update the vendor information!\n${tools.appName.toLowerCase()} vendor <PASS_PHRASE> <vendor.json>`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} if (!file) {
console.warn(`Please specify the path to the vendor file to update the vendor information!\n${tools.appName.toLowerCase()} vendor <PASS_PHRASE> <vendor.json>`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} else {
dbConnect(params, () => {
const Vendor = require('./setup/setupVendor');
const vendor = new Vendor({
objects: objects
});
vendor.checkVendor(file, password).then(() => {
console.log(`Synchronised vendor information.`);
return void callback();
}).catch(err => {
console.error(`Cannot update vendor information: ${err.message}`);
return void callback(EXIT_CODES.CANNOT_UPDATE_VENDOR);
});
});
}
break;
}
case 'cert': {
const certCommand = new cli.command.cert(commandOptions);
certCommand.execute(args);
break;
}
case 'compact': {
const compactCommand = new cli.command.compact(commandOptions);
compactCommand.execute(args);
break;
}
case 'plugin': {
const pluginCommand = new cli.command.plugin(commandOptions);
pluginCommand.execute(args);
break;
}
case 'license': {
const file = args[0];
if (!file) {
console.warn(`Please specify the path to the license file or place license text directly!\n${tools.appName.toLowerCase()} license <license.file or license.text>`);
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
} else {
dbConnect(params, () => {
const License = require('./setup/setupLicense');
const license = new License({
objects: objects
});
license.setLicense(file).then(type => {
console.log(`License ${type} updated.`);
return void callback();
}).catch(err => {
console.error(`Cannot update license: ${err.message}`);
return void callback(EXIT_CODES.CANNOT_UPDATE_LICENSE);
});
});
}
break;
}
default: {
if (params.v || params.version) {
let iopckg;
if (command) {
try {
iopckg = require(tools.appName + '.' + command + '/package.json');
} catch {
iopckg = {version: '"' + command + '" not found'};
}
} else {
iopckg = require('../package.json');
}
console.log(iopckg.version);
} else {
showHelp();
return void callback(EXIT_CODES.INVALID_ARGUMENTS);
}
return void callback();
}
}
}
// Save objects before exit
async function processExit(exitCode) {
if (objects && objects.destroy) {
await objects.destroy();
}
if (states && states.destroy) {
await states.destroy();
}
setTimeout(() => {
process.exit(exitCode);
}, 1000);
}
function delObjects(ids, callback) {
if (!ids || !ids.length) {
return void callback();
} else {
const id = ids.shift();
objects.delObject(id, err => {
if (err &&
id !== 'system.group.user' &&
id !== 'system.group.administrator' &&
id !== 'system.user.admin'
) {
console.warn(`[Not critical] Cannot delete object ${id}: ${JSON.stringify(err)}`);
}
setImmediate(delObjects, ids, callback);
});
}
}
function cleanDatabase(isDeleteDb, callback) {
let taskCnt = 0;
if (isDeleteDb) {
objects.destroyDB(() => {
// Clean up states
states.getKeys('*', (_err, obj) => {
const delState = [];
let i;
if (obj) {
for (i = 0; i < obj.length; i++) {
delState.push(obj[i]);
}
}
taskCnt = 0;
for (i = 0; i < obj.length; i++) {
taskCnt++;
states.delState(delState[i], () => !(--taskCnt) && callback && callback(obj.length));
}
});
});
} else {
// Clean only objects, not the views
objects.getObjectList({startkey: '\u0000', endkey: '\u9999'}, (err, res) => {
let ids = [];
if (!err && res.rows.length) {
console.log('clean ' + res.rows.length + ' objects...');
ids = res.rows.map(e => e.id);
}
delObjects(ids, () => {
// Clean up states
states.getKeys('*', (_err, obj) => {
const delState = [];
let i;
if (obj) {
for (i = 0; i < obj.length; i++) {
delState.push(obj[i]);
}
}
taskCnt = 0;
console.log('clean ' + obj.length + ' states...');
for (i = 0; i < obj.length; i++) {
taskCnt++;
states.delState(delState[i], () => !(--taskCnt) && callback && callback(obj.length));
}
if (!taskCnt && callback) {
return void callback(obj.length);
}
});
});
});
}
}
function unsetup(params, callback) {
dbConnect(params, () => {
objects.delObject('system.meta.uuid', err => {
if (err) {
console.log('uuid cannot be deleted: ' + err);
} else {
console.log('system.meta.uuid deleted');
}
objects.getObject('system.config', (_err, obj) => {
if (obj.common.licenseConfirmed || obj.common.language || (obj.native && obj.native.secret)) {
obj.common.licenseConfirmed = false;
obj.common.language = '';
if (!params.keepsecret) {
obj.native && delete obj.native.secret;
}
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.config', obj, err => {
if (err) {
console.log('not found: ' + err);
return void callback(EXIT_CODES.CANNOT_SET_OBJECT);
} else {
console.log('system.config reset');
return void callback();
}
});
} else {
console.log('system.config is OK');
return void callback();
}
});
});
});
}
function restartController(callback) {
const spawn = require('child_process').spawn;
console.log('Starting node restart.js');
const child = spawn('node', [__dirname + '/restart.js'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
windowsHide: true
});
child.unref();
if (callback) {
callback();
} else {
processExit();
}
}
function installNpm(adapter, rebuildCommand, callback) {
if (typeof rebuildCommand === 'function') {
callback = rebuildCommand;
rebuildCommand = false;
}
let path = __dirname;
if (typeof adapter === 'function') {
callback = adapter;
adapter = undefined;
}
if (adapter) {
if (rebuildCommand && adapter === 'self') {
path = pathLib.join(__dirname, '..');
} else {
path = tools.getAdapterDir(adapter);
}
}
let debug = false;
for (let i = 0; i < process.argv.length; i++) {
if (process.argv[i] === '--debug') {
debug = true;
break;
}
}
if (!path) {
console.log(`Cannot install ${tools.appName}.${adapter}: adapter path not found`);
return (callback || processExit)(EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
}
const npmCommand = typeof rebuildCommand === 'string' ? rebuildCommand : 'install';
// iob_npm.done file was created if "npm i" yet called there
if (fs.existsSync(pathLib.join(path, 'package.json')) && (rebuildCommand || !fs.existsSync(pathLib.join(path, 'iob_npm.done')))) {
let cmd = `npm ${npmCommand} ${debug ? '' : '--loglevel error'}`;
if (npmCommand === 'install') {
cmd += ' --production';
}
console.log(`${cmd} (System call1) in "${path}"`);
// Install node modules as system call
// System call used for update of js-controller itself,
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
const exec = require('child_process').exec;
const child = exec(cmd, {
cwd: path,
windowsHide: true
});
tools.pipeLinewise(child.stderr, process.stdout);
debug && tools.pipeLinewise(child.stdout, process.stdout);
child.on('exit', (code, _signal) => {
// code 1 is strange error that cannot be explained. Everything is installed but error :(
if (code && code !== 1) {
console.log(`Cannot install ${tools.appName}.${adapter}: ${code}`);
(callback || processExit)(EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
return;
}
// command succeeded
if (!rebuildCommand || rebuildCommand === 'install') {
fs.writeFileSync(path + '/iob_npm.done', ' ');
}
typeof callback === 'function' && callback(null, adapter);
});
} else if (typeof callback === 'function') {
callback(null, adapter);
}
}
function getRepository(repoUrl, params, callback) {
if (typeof params === 'function') {
callback = params;
params = {};
}
params = params || {};
if (!repoUrl || typeof repoUrl !== 'object') {
if (!objects) {
dbConnect(params, () => getRepository(repoUrl, params, callback));
} else {
// try to read repository
objects.getObject('system.config', (_err, systemConfig) => {
objects.getObject('system.repositories', (err, repos) => {
// Check if repositories exists
if (!err && repos && repos.native && repos.native.repositories) {
const active = systemConfig.common.activeRepo;
if (repos.native.repositories[active]) {
if (typeof repos.native.repositories[active] === 'string') {
repos.native.repositories[active] = {
link: repos.native.repositories[active],
json: null
};
}
// If repo is not yet loaded
if (!repos.native.repositories[active].json) {
console.log(`Update repository "${active}" under "${repos.native.repositories[active].link}"`);
// Load it
tools.getRepositoryFile(repos.native.repositories[active].link, {
hash: repos.native.repositories[active].hash,
sources: repos.native.repositories[active].json,
controller: require('../io-package.json').common.version,
node: process.version,
name: tools.appName
}, (_err, sources, sourcesHash) => {
if (_err && !sources) {
callback(_err, sources);
} else {
repos.native.repositories[active].json = sources;
repos.native.repositories[active].hash = sourcesHash;
repos.from = `system.host.${tools.getHostName()}.cli`;
repos.ts = new Date().getTime();
// Store uploaded repo
objects.setObject('system.repositories', repos, () => void callback(null, sources));
}
});
} else {
// We have already repo, give it back
return void callback(null, repos.native.repositories[active].json);
}
} else {
console.log('Requested repository "' + active + '" does not exist in config.');
return void callback(EXIT_CODES.INVALID_REPO);
}
} else {
console.log('No repositories defined.');
return void callback(EXIT_CODES.INVALID_REPO);
}
});
});
}
} else {
return void callback(null, repoUrl);
}
}
async function resetDbConnect(_callback) {
if (objects) {
await objects.destroy();
objects = null;
}
if (states) {
await states.destroy();
states = null;
}
if (Objects) {
delete require.cache[require.resolve(__dirname + '/objects')];
Objects = null;
}
if (States) {
delete require.cache[require.resolve(__dirname + '/states')];
States = null;
}
}
// function showConfig(config, root) {
// root = root || [];
// const prefix = root.join('/').toUpperCase();
// for (const attr in config) {
// if (!config.hasOwnProperty(attr)) continue;
// if (attr.match(/comment$/i)) continue;
// if (typeof config[attr] === 'object') {
// const nextRoot = deepClone(root);
// nextRoot.push(attr);
// showConfig(config[attr], nextRoot);
// } else {
// console.log(`${prefix}${(prefix ? '/' : '') + attr}: ` + config[attr]);
// }
// }
// }
function checkSystemOffline(onlyCheck, callback) {
if (!objects || !states) { // should never happen
callback && callback(true);
return;
}
if (onlyCheck) {
callback && callback(true);
return;
}
setTimeout(() => { // Slight delay to allow "setup first" from Pre 2.0 to 2.0
enumHosts(objects).then(hosts => {
const hostToCheck = hosts.map(host => 'system.host.' + host.common.hostname + '.alive');
states.getStates(hostToCheck, (err, res) => {
!err && Array.isArray(res) && res.forEach(aliveState => {
if (aliveState && aliveState.val) {
callback && callback(false);
callback = null;
}
});
callback && callback(true);
});
}).catch(() => {
callback && callback(true);
});
}, 500);
}
/**
* Connects to the DB or tests the connection. The callback has the following signature:
* `(objects: any, states: any, isOffline?: boolean, objectsDBType?: string) => void`
*/
function dbConnect(onlyCheck, params, callback) {
if (typeof onlyCheck === 'object') {
callback = params;
params = onlyCheck;
onlyCheck = false;
}
if (typeof onlyCheck === 'function') {
callback = onlyCheck;
onlyCheck = false;
}
if (typeof params === 'function') {
callback = params;
params = null;
}
params = params || {};
const config = fs.readJSONSync(tools.getConfigFileName());
if (objects && states) {
return void callback(objects, states, false, config.objects.type, config);
}
config.states = config.states || {type: 'file'};
config.objects = config.objects || {type: 'file'};
Objects = require('./objects'); // Objects DB Client object
States = require('./states'); // States DB Client object
// Give to controller 2 seconds for connection
let isObjectConnected = false;
let isStatesConnected = false;
// Detect timeout or try to open file itself
setTimeout(async () => {
if (isObjectConnected && isStatesConnected) {
return;
}
if (!isObjectConnected) {
if (objects) { // Destroy Client we tried to connect with
await objects.destroy();
objects = null;
}
if (tools.objectsDbHasServer(config.objects.type)) {
// Just open in memory DB itself
Objects = require(`@iobroker/db-objects-${config.objects.type}`).Server;
objects = new Objects({
connection: config.objects,
logger: {
silly: _msg => {},
debug: _msg => {},
info: _msg => {},
warn: msg => console.log(msg),
error: msg => console.log(msg)
},
connected: () => {
isObjectConnected = true;
if (isStatesConnected && typeof callback === 'function') {
return void callback(objects, states, true, config.objects.type, config);
}
}
});
} else {
console.log('No connection to objects ' + config.objects.host + ':' + config.objects.port + '[' + config.objects.type + ']');
if (onlyCheck) {
callback && callback(objects, states, true, config.objects.type, config);
callback = null;
} else {
processExit(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);
}
}
}
if (!isStatesConnected) {
if (states) { // Destroy Client we tried to connect with
await states.destroy();
states = null;
}
if (tools.statesDbHasServer(config.states.type)) {
// Just open in memory DB itself
States = require(`@iobroker/db-states-${config.states.type}`).Server;
states = new States({
connection: config.states,
logger: {
silly: _msg => {},
debug: _msg => {},
info: _msg => {},
warn: msg => console.log(msg),
error: msg => console.log(msg)
},
connected: () => {
isStatesConnected = true;
if (isObjectConnected && typeof callback === 'function') {
return void callback(objects, states, true, config.objects.type, config);
}
},
// react on change
change: (id, msg) => states.onChange && states.onChange(id, msg)
});
states.onChange = null; // here the custom onChange handler could be installed
} else {
if (states) { // Destroy Client we tried to connect with
await states.destroy();
states = null;
}
if (objects) { // Destroy Client we tried to connect with
await objects.destroy();
objects = null;
}
console.log('No connection to states ' + config.states.host + ':' + config.states.port + '[' + config.states.type + ']');
if (onlyCheck) {
callback && callback(objects, states, true, config.objects.type, config);
callback = null;
} else {
processExit(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);
}
}
}
setTimeout(() => { // Failsafe
if (isObjectConnected && isStatesConnected) {
return;
}
console.log('No connection to databases possible ...');
if (onlyCheck) {
callback && callback(null, null, true, config.objects.type, config);
callback = null;
} else {
processExit(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);
}
}, params.timeout || (10000 + config.objects.connectTimeout) || 12000);
}, params.timeout || (config.objects.connectTimeout * 2) || 4000);
// try to connect as client
objects = new Objects({
connection: config.objects,
logger: {
silly: _msg => {},
debug: _msg => {},
info: _msg => {},
warn: msg => console.log(msg),
error: msg => console.log(msg)
},
connected: () => {
if (isObjectConnected) {
return;
}
isObjectConnected = true;
if (isStatesConnected && typeof callback === 'function') {
checkSystemOffline(onlyCheck, isOffline => {
callback(objects, states, isOffline, config.objects.type, config);
});
}
}
});
states = new States({
connection: config.states,
logger: {
silly: _msg => {},
debug: _msg => {},
info: _msg => {},
warn: msg => console.log(msg),
error: msg => console.log(msg)
},
connected: () => {
if (isStatesConnected) {
return;
}
isStatesConnected = true;
if (isObjectConnected && typeof callback === 'function') {
checkSystemOffline(onlyCheck, isOffline => {
callback(objects, states, isOffline, config.objects.type, config);
});
}
},
change: (id, state) => states.onChange && states.onChange(id, state)
});
}
module.exports.processCommand = function (_objects, _states, command, args, params, callback) {
objects = _objects;
states = _states;
processCommand(command, args, params, callback);
};
const processCommandAsync = tools.promisifyNoError(module.exports.processCommand);
module.exports.processCommandAsync = function (_objects, _states, _command, _args, _params) {
return processCommandAsync.apply(undefined, arguments);
};
module.exports.execute = function () {
// direct call
const _yargs = initYargs();
const command = _yargs.argv._[0];
const args = [];
for (let a = 1; a < _yargs.argv._.length; a++) {
args.push(_yargs.argv._[a]);
}
processCommand(command, args, _yargs.argv, processExit);
};
process.on('unhandledRejection', err => {
console.error(`Uncaught Rejection: ${err.stack || err}`);
processExit(EXIT_CODES.UNCAUGHT_EXCEPTION);
});