iobroker.js-controller
Version:
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
1,183 lines (1,093 loc) • 141 kB
JavaScript
/**
*
* ioBroker Command Line Interface (CLI)
*
* 7'2014-2022 bluefox <dogafox@gmail.com>
* 2014 hobbyquaker <hq@ccu.io>
*
*/
/* jshint -W097 */
/* jshint strict:false */
/* jslint node: true */
'use strict';
// TODO need info about progress of stopping
const fs = require('fs-extra');
const { tools } = require('@iobroker/js-controller-common');
const cli = require('@iobroker/js-controller-cli');
const { EXIT_CODES } = require('@iobroker/js-controller-common');
const deepClone = require('deep-clone');
const { isDeepStrictEqual } = require('util');
const debug = require('debug')('iobroker:cli');
const { tools: dbTools, getObjectsConstructor, getStatesConstructor } = require('@iobroker/js-controller-common-db');
const path = require('path');
const { PluginHandler } = require('@iobroker/plugin-base');
let pluginHandler;
// @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 [all|<adapter>.<instance>]',
'Starts the js-controller or a specified adapter instance',
yargs => {
yargs
.command('all', 'Starts js-controller and all adapters')
.command('<adapter>[.<instance>]', 'Starts a specified adapter instance');
}
)
.command('stop [<adapter>.<instance>]', 'stops the js-controller or a specified adapter instance', yargs => {
yargs.command('<adapter>[.<instance>]', 'Stops a specified adapter instance');
})
.command(
['restart [<adapter>.<instance>]', 'r [<adapter>.<instance>]'],
'Restarts js-controller or a specified adapter instance',
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]', 'a <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>', 'i <adapter>'], 'Installs a specified adapter', {})
.command('rebuild [<module>]', 'Rebuild all native modules or path', {
path: {
describe: 'Executes rebuild command in given path',
type: 'string'
}
})
.command('url <url> [<name>]', 'Install adapter from specified url, e.g. GitHub', {})
.command(['del <adapter>', 'delete <adapter>'], 'Remove adapter and all instances from this host', {
custom: {
describe: 'Remove adapter custom attribute from all objects',
type: 'boolean'
}
})
.command(['del <adapter>.<instance>', 'delete <adapter>.<instance>'], 'Remove adapter instance', {
custom: {
describe: 'Remove instance custom attribute from all objects',
type: 'boolean'
}
})
.command('update [<repositoryUrl>]', 'Update repository and list adapters', {
updatable: {
describe: 'Only show updatable 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 [all|<adapter>]', 'u [all|<adapter>]'], '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', 'o'], '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('setDBVersion <version>', 'Sets the protocol version of the objects database')
.command('getDBVersion', 'Get the protocol version of the objects database')
.command('activateSets', 'Activate the usage of Redis Sets')
.command('deactivateSets', 'Deactivate the usage of Redis Sets');
})
.command(['state', 's'], '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('getBinary <id>', 'Get binary state, specified by id', {
encoding: {
describe: 'Encoding for the binary state, like utf-8, ascii, hex, base64, binary',
type: 'string',
default: 'binary'
}
})
.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('setDBVersion <version>', 'Sets the protocol version of the states database')
.command('getDBVersion', 'Get the protocol version of the states database');
})
.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', {
force: {
describe: 'Restore backup of different controller version',
alias: 'f',
type: 'boolean'
}
})
.command('validate <backup name or path>', 'Validate a specified backup', {})
.command(['status [all|<adapter>.<instance>]', 'isrun'], 'Status of ioBroker or adapter instance', yargs => {
yargs
.command('all', 'Show entire config')
.command('<adapter>[.<instance>]', 'Status of a specified 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', 'id'], '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>]', 'v [<adapter>]'], 'Show version of js-controller or specified adapter')
.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
*/
async 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 {CLICommandContext} */
const commandContext = { dbConnect, callback, showHelp };
/** @type {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 = getObjectsConstructor();
const repoUrl = args[0]; // Repo url or name
dbConnect(params, async (_objects, _states) => {
const Repo = require('./setup/setupRepo.js');
const repo = new Repo({
objects: _objects,
states: _states
});
await repo.showRepo(repoUrl, params);
setTimeout(callback, 1000);
});
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 () => {
if (isFirst) {
// Creates all instances that are needed on a fresh installation
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
getRepository,
processExit: callback,
params
});
// Define the necessary instances
const initialInstances = ['admin', 'discovery', 'backitup'];
// And try to install each of them
for (const instance of initialInstances) {
try {
const adapterInstalled = !!require.resolve(`${tools.appName}.${instance}`);
if (adapterInstalled) {
let otherInstanceExists = false;
try {
// check if another instance exists
const res = await objects.getObjectViewAsync('system', 'instance', {
startkey: `system.adapter.${instance}`,
endkey: `system.adapter.${instance}\u9999`
});
otherInstanceExists = res && res.rows && res.rows.length;
} catch {
// ignore - on install we have no object views
}
if (!otherInstanceExists) {
await install.createInstance(instance, {
enabled: true,
ignoreIfExists: true
});
}
}
} catch {
// not found, just continue
}
}
await new Promise(resolve => {
// Creates a fresh certificate
const Cert = cli.command.cert;
// 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();
});
}
// we update existing things, in first as well as normnal setup
// Rename repositories
const Repo = require('./setup/setupRepo.js');
const repo = new Repo({ objects, 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 (err) {
console.warn(`Cannot rename: ${err.message}`);
}
// there has been a bug that user can upload js-controller
try {
await objects.delObjectAsync('system.adapter.js-controller');
} catch {
// ignore
}
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;
}
let migrated = '';
// We migrate file to jsonl
if (config.states.type === 'file') {
config.states.type = 'jsonl';
if (dbTools.isLocalStatesDbServer('file', config.states.host)) {
// silent config change on secondaries
console.log('States DB type migrated from "file" to "jsonl"');
migrated += 'States';
}
}
if (config.objects.type === 'file') {
config.objects.type = 'jsonl';
if (dbTools.isLocalObjectsDbServer('file', config.objects.host)) {
// silent config change on secondaries
console.log('Objects DB type migrated from "file" to "jsonl"');
migrated += migrated ? ' and Objects' : 'Objects';
}
}
if (migrated) {
const NotificationHandler = require('./../lib/notificationHandler');
const hostname = tools.getHostName();
const notificationSettings = {
states: states,
objects: objects,
log: console,
logPrefix: '',
host: hostname
};
const notificationHandler = new NotificationHandler(notificationSettings);
try {
const ioPackage = fs.readJsonSync(path.join(__dirname, '..', 'io-package.json'));
await notificationHandler.addConfig(ioPackage.notifications);
await notificationHandler.addMessage(
'system',
'fileToJsonl',
`Migrated: ${migrated}`,
`system.host.${hostname}`
);
notificationHandler.storeNotifications();
} catch (e) {
console.warn(`Could not add File-to-JSONL notification: ${e.message}`);
}
}
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 = getObjectsConstructor();
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, async () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
getRepository,
processExit: callback,
params
});
try {
await install.installAdapterFromUrl(url, name);
return void callback(EXIT_CODES.NO_ERROR);
} catch (e) {
console.error(`Could not install adapter from url: ${e.message}`);
return void callback(EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
}
});
break;
}
case 'info': {
Objects = getObjectsConstructor();
dbConnect(params, async objects => {
try {
const data = await tools.getHostInfo(objects);
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] || ''
}`
);
}
} catch (err) {
console.error('Cannot read host info: ' + (typeof err === 'object' ? JSON.stringify(err) : err));
return callback(EXIT_CODES.CANNOT_GET_HOST_INFO);
}
return void callback();
});
break;
}
case 'a':
case 'add':
case 'install':
case 'i': {
Objects = getObjectsConstructor();
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,
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 (err) {
console.warn(`Could not check existence of host "${params.host}": ${err.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)) {
try {
const { stoppedList } = await install.downloadPacket(repoUrl, installName);
await install.installAdapter(installName, repoUrl);
await install.enableInstances(stoppedList, true); // even if unlikely make sure to reenable disabled instances
if (command !== 'install' && command !== 'i') {
await install.createInstance(name, params);
}
return void callback();
} catch (err) {
console.error(`adapter "${name}" cannot be installed: ${err.message}`);
return void callback(EXIT_CODES.UNKNOWN_ERROR);
}
} else if (command !== 'install' && command !== 'i') {
try {
await install.createInstance(name, params);
return void callback();
} catch (err) {
console.error(`adapter "${name}" cannot be installed: ${err.message}`);
return void callback(EXIT_CODES.UNKNOWN_ERROR);
}
} 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': {
const options = { debug: process.argv.includes('--debug') };
if (commandOptions.path) {
if (path.isAbsolute(commandOptions.path)) {
options.cwd = commandOptions.path;
} else {
console.log('Path argument needs to be an absolute path!');
return void processExit(EXIT_CODES.INVALID_ARGUMENTS);
}
}
if (commandOptions.module) {
options.module = commandOptions.module;
console.log(
`Rebuilding native module "${commandOptions.module}"${options.cwd ? ` in ${options.cwd}` : ''} ...`
);
} else {
console.log(`Rebuilding native modules${options.cwd ? ` in ${options.cwd}` : ''} ...`);
}
const result = await tools.rebuildNodeModules(options);
if (result.success) {
console.log();
console.log(`Rebuilding native modules done`);
return void callback();
} else {
console.error('Rebuilding native modules failed');
return void processExit(result.exitCode);
}
}
case 'upload':
case 'u': {
Objects = getObjectsConstructor();
const name = args[0];
const subTree = args[1];
if (name) {
dbConnect(params, async () => {
const Upload = require('./setup/setupUpload.js');
const upload = new Upload({ states, objects });
if (name === 'all') {
try {
const objs = await objects.getObjectListAsync({
startkey: 'system.adapter.',
endkey: 'system.adapter.\u9999'
});
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);
}
await upload.uploadAdapterFullAsync(adapters);
callback();
} catch (err) {
console.error(`Cannot upload all adapters: ${err.message}`);
return void callback(EXIT_CODES.CANNOT_UPLOAD_DATA);
}
} else {
// if upload of file
if (name.includes('.')) {
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);
}
try {
const newName = await upload.uploadFile(name, subTree);
console.log(`File "${name}" is successfully saved under ${newName}`);
return void callback();
} catch (err) {
console.error(`Cannot upload file "${name}": ${err.message}`);
return void callback(EXIT_CODES.CANNOT_UPLOAD_DATA);
}
} else {
try {
if (subTree) {
await upload.uploadAdapter(name, false, true, subTree);
} else {
await upload.uploadAdapterFullAsync([name]);
}
return void callback();
} catch (err) {
console.error(`Cannot upload files "${name}": ${err.message}`);
return void callback(EXIT_CODES.CANNOT_UPLOAD_DATA);
}
}
}
});
} 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, async () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
getRepository,
processExit: callback,
params
});
console.log(`Delete instance "${adapter}.${instance}"`);
await install.deleteInstance(adapter, instance);
callback();
});
} else {
dbConnect(params, async () => {
const Install = require('./setup/setupInstall.js');
const install = new Install({
objects,
states,
getRepository,
processExit: callback,
params
});
console.log(`Delete adapter "${adapter}"`);
const resultCode = await install.deleteAdapter(adapter);
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 = getObjectsConstructor();
let adapter = cli.tools.normalizeAdapterName(args[0]);
if (adapter === 'all') {
adapter = null;
}
dbConnect(params, async () => {
const Upgrade = require('./setup/setupUpgrade.js');
const upgrade = new Upgrade({
objects,
states,
getRepository,
params,
processExit: callback,
restartController
});
if (adapter) {
try {
if (adapter === 'self') {
const hostAlive = await states.getStateAsync(`system.host.${tools.getHostName()}.alive`);
await upgrade.upgradeController('', params.force || params.f, hostAlive && hostAlive.val);
} else {
await upgrade.upgradeAdapter(
'',
adapter,
params.force || params.f,
params.y || params.yes,
false
);
}
return void callback();
} catch (err) {
console.error(`Cannot upgrade: ${err.message}`);
return void callback(EXIT_CODES.INVALID_REPO);
}
} else {
// upgrade all
try {