Initial commit

This commit is contained in:
2018-09-17 20:32:19 +08:00
commit 3a9804f020
58 changed files with 31566 additions and 0 deletions

5102
lib/adapter.js Normal file

File diff suppressed because it is too large Load Diff

78
lib/dbdump.js Normal file
View File

@@ -0,0 +1,78 @@
/**
*
* dpdump.js
*
* Utility to get JSON Database Dumps
*
* 7'2014 hobbyquaker <hq@ccu.io>
*
*/
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
'use strict';
var yargs = require('yargs')
.usage('$0 [-d design] [-s search] [-p pattern]')
.example('$0 -s state', 'get all objects with type=state')
.example('$0 -s state -p hue.*', 'get all objects from the adapter hue with type=state')
// Todo .example('$0 -p system.*', 'get all objects with _id=system.*')
.alias('s', 'search')
.alias('d', 'design')
.default('design', 'system')
;
if (!yargs.argv.s && !yargs.argv.p) {
yargs.showHelp();
process.exit(0);
}
var Objects = require(__dirname + '/objects.js');
var db = new Objects({
logger: {
silly: function (msg) { },
debug: function (msg) { },
info: function (msg) { },
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: function () {
if (yargs.argv.search) {
db.getObjectView(yargs.argv.design, yargs.argv.search, {}, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
showResult(res);
});
} else {
db.getObjectList({include_docs: true}, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
showResult(res);
});
}
}
});
function showResult(res) {
var outArr = [];
for (var i = 0; i < res.total_rows; i++) {
var obj = res.rows[i].value;
delete obj._rev;
outArr.push(obj);
}
console.log(JSON.stringify(outArr, null, ' '));
process.exit(0);
}

60
lib/defaultObjs.js Normal file
View File

@@ -0,0 +1,60 @@
'use strict';
function createDefaults(lang, temperature, currency) {
var defaults = {
"level.dimmer": {
"def": 0,
"type": "number",
"read": true,
"write": true,
"min": 0,
"max": 100,
"unit": "%"
},
"indicator.working": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true
},
"indicator.maintenance": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true
},
"indicator.maintenance.lowbat": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true,
"desc": "Low battery"
},
"indicator.maintenance.unreach": {
"def": false,
"type": "boolean",
"read": true,
"write": false,
"min": false,
"max": true,
"desc": "Device unreachable"
},
"switch": {
"def": false,
"type": "boolean",
"read": true,
"write": true
}
};
return defaults;
}
module.exports = createDefaults;

BIN
lib/img/iobroker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

182
lib/letsencrypt.js Normal file
View File

@@ -0,0 +1,182 @@
'use strict';
function createServer(app, settings, certificates, leSettings, log) {
let server;
const leLog = function (debug, arg1, arg2, arg3) {
if (debug) {
const args = Array.prototype.slice.call(arguments);
args.shift(); // remove debug argument
// skip "no match"
if (args[0][0] === 'n' && args[0][1] === 'o') return;
log.info(arg1 + (arg2 || '') + (arg3 || ''));
}
};
if (settings.secure) {
if (leSettings && (!leSettings.email || !leSettings.email) && settings.leEnabled) {
log.error('Please specify the email address and domains to use Let\'s Encrypt certificates!');
}
if (leSettings && leSettings.email && leSettings.domains && settings.leEnabled) {
const tools = require(__dirname + '/tools');
const tls = require('tls');
const fs = require('fs');
const LE = require('greenlock');
let leDir;
let configPath = tools.getConfigFileName().replace(/\\/g, '/');
let parts = configPath.split('/');
parts.pop();
configPath = parts.join('/');
if (leSettings.path && (leSettings.path[0] === '/' || leSettings.path.match(/^[A-Za-z]:/))) {
leDir = leSettings.path;
} else {
leDir = configPath + '/' + (leSettings.path || 'letsencrypt');
}
// for lex outputs
if (!console.debug) console.debug = console.log;
if (!fs.existsSync(leDir)) fs.mkdirSync(leDir);
// prepare domains
if (typeof leSettings.domains === 'string') {
leSettings.domains = leSettings.domains.split(',');
for (let d = leSettings.domains.length - 1; d >= 0; d--) {
leSettings.domains[d] = leSettings.domains[d].trim();
if (!leSettings.domains[d]) leSettings.domainss.splice(d, 1);
}
}
let lex = LE.create({
debug: true,
configDir: leDir,
agreeTos: true,
store: require(__dirname + '/letsencryptStore.js').create({debug: true, log: leLog}),
server: 'https://acme-v01.api.letsencrypt.org/directory', //'staging',
email: leSettings.email,
approvedDomains: leSettings.domains,
log: leLog
});
if (settings.leUpdate) {
settings.lePort = parseInt(settings.lePort, 10) || 80;
// handles acme-challenge and redirects to https
// used for validation of requests like http://example.com/.well-known/acme-challenge/BLABALBAL
require('http').createServer(lex.middleware(function redirectHttps(req, res) {
res.setHeader('Location', 'https://' + req.headers.host + req.url);
res.statusCode = 302;
res.end('<!-- Hello Developer Person! Please use HTTPS instead -->');
})).listen(settings.lePort, function () {
log.info('LetsEncrypt challenge server is started on ' + settings.lePort);
});
}
let options = JSON.parse(JSON.stringify(certificates));
let defaultTls = tls.createSecureContext(certificates);
let hostTls;
let running;
options.SNICallback = function (hostname, cb) {
if (leSettings.domains.indexOf(hostname) !== -1) {
if (settings.leUpdate) {
if (running === true) {
cb(null, hostTls || defaultTls);
} else
if (running) {
running.push(cb);
} else {
running = [cb];
return lex.httpsOptions.SNICallback(hostname, function (err, tls) {
if (tls) log.debug('Got valid certificates from letsencrypt');
if (err) log.error('Cannot get certificates: ' + err);
lex.debug = false;
hostTls = tls;
for (let r = 0; r < running.length; r++) {
running[r](err, tls || defaultTls);
}
running = true;
});
}
} else {
if (!hostTls) {
// validate certificates
lex.check({domains: leSettings.domains}).then(function (certInfo) {
if (certInfo) {
hostTls = tls.createSecureContext({
key: certInfo.privkey || certInfo.key, // privkey.pem
cert: certInfo.fullchain || certInfo.cert, // fullchain.pem (cert.pem + '\n' + chain.pem)
ca: certInfo.ca
});
cb(null, hostTls);
} else {
log.error('No letsencrypt certificates found in "' + leDir + '"');
cb(null, defaultTls);
// do not register domain
/*
if (!running) {
running = [cb];
lex.letsencrypt.register({
domains: leSettings.domains,
email: leSettings.email,
agreeTos: true
}, function (err, certInfo) {
//log.debug("[LEX] '" + hostname + "' register completed", err && err.stack || null, certInfo);
if ((!err || !err.stack) && !certInfo) {
log.error((new Error('[LEX] SANITY FAIL: no error and yet no certs either')).stack);
}
if (certInfo) {
hostTls = tls.createSecureContext({
key: certInfo.privkey || certInfo.key, // privkey.pem
cert: certInfo.fullchain || certInfo.cert, // fullchain.pem (cert.pem + '\n' + chain.pem)
ca: certInfo.ca
});
for (var r = 0; r < running.length; r++) {
running[r](null, hostTls);
}
} else {
for (var r = 0; r < running.length; r++) {
running[r](null, defaultTls);
}
}
running = true;
});
} else {
if (running === true) {
cb(null, defaultTls);
} else {
running.push(cb);
}
}
*/
}
},
function (err) {
if (err) log.error(err);
cb(null, defaultTls);
});
} else {
cb(null, hostTls);
}
}
} else {
cb(null, defaultTls);
}
};
server = require('https').createServer(options, lex.middleware(app));
} else {
server = require('https').createServer(certificates, app);
}
} else {
server = require('http').createServer(app);
}
return server;
}
exports.createServer = createServer;

628
lib/letsencryptStore.js Normal file
View File

@@ -0,0 +1,628 @@
// Initially copied from https://github.com/Daplie/le-store-certbot
// but must be completely rewritten to support DB
'use strict';
var PromiseA = require('bluebird');
var mkdirpAsync = PromiseA.promisify(require('mkdirp'));
var path = require('path');
var fs = PromiseA.promisifyAll(require('fs'));
var sfs = require('safe-replace');
var log = function (debug) {
if (debug) {
var args = Array.prototype.slice.call(arguments);
args.shift();
args.unshift('[le-store-certbot]');
console.log.apply(console, args);
}
};
function writeRenewalConfig(args) {
var pyobj = args.pyobj;
pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0;
var pyconf = PromiseA.promisifyAll(require('pyconf'));
var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem');
var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem');
var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem');
var privkeyPath = args.privkeyPath || pyobj.privkey
//|| args.domainPrivateKeyPath || args.domainKeyPath || pyobj.keyPath
|| path.join(liveDir, 'privkey.pem');
log(args.debug, 'writeRenewalConfig privkeyPath', privkeyPath);
var updates = {
account: args.account.id
, configDir: args.configDir
, domains: args.domains
, email: args.email
, tos: args.agreeTos && true
// yes, it's an array. weird, right?
, webrootPath: args.webrootPath && [args.webrootPath] || []
, server: args.server || args.acmeDiscoveryUrl
, privkey: privkeyPath
, fullchain: fullchainPath
, cert: certPath
, chain: chainPath
, http01Port: args.http01Port
, keyPath: args.domainPrivateKeyPath || args.privkeyPath
, rsaKeySize: args.rsaKeySize
, checkpoints: pyobj.checkpoints
/* // TODO XXX what's the deal with these? they don't make sense
// are they just old junk? or do they have a meaning that I don't know about?
, fullchainPath: path.join(args.configDir, 'chain.pem')
, certPath: path.join(args.configDir, 'cert.pem')
, chainPath: path.join(args.configDir, 'chain.pem')
*/ // TODO XXX end
, workDir: args.workDir
, logsDir: args.logsDir
};
// final section is completely dynamic
// :hostname = :webroot_path
args.domains.forEach(function (hostname) {
updates[hostname] = args.webrootPath;
});
// must write back to the original pyobject or
// annotations will be lost
Object.keys(updates).forEach(function (key) {
pyobj[key] = updates[key];
});
return mkdirpAsync(path.dirname(args.renewalPath)).then(function () {
return pyconf.writeFileAsync(args.renewalPath, pyobj);
}).then(function () {
// NOTE
// writing twice seems to causes a bug,
// so instead we re-read the file from the disk
return pyconf.readFileAsync(args.renewalPath);
});
}
function pyToJson(pyobj) {
if (!pyobj) {
return null;
}
var jsobj = {};
Object.keys(pyobj).forEach(function (key) {
jsobj[key] = pyobj[key];
});
jsobj.__lines = undefined;
jsobj.__keys = undefined;
return jsobj;
}
var defaults = {
configDir: [ '~', 'letsencrypt', 'etc' ].join(path.sep) // /etc/letsencrypt/
, logsDir: [ '~', 'letsencrypt', 'var', 'log' ].join(path.sep) // /var/log/letsencrypt/
, workDir: [ '~', 'letsencrypt', 'var', 'lib' ].join(path.sep) // /var/lib/letsencrypt/
, accountsDir: [ ':configDir', 'accounts', ':serverDir' ].join(path.sep)
, renewalPath: [ ':configDir', 'renewal', ':hostname.conf' ].join(path.sep)
, renewalDir: [ ':configDir', 'renewal', '' ].join(path.sep)
, serverDirGet: function (copy) {
return (copy.server || '').replace('https://', '').replace(/(\/)$/, '').replace(/\//g, path.sep);
}
, privkeyPath: ':configDir/live/:hostname/privkey.pem'.split(/\//).join(path.sep)
, fullchainPath: [ ':configDir', 'live', ':hostname', 'fullchain.pem' ].join(path.sep)
, certPath: [ ':configDir', 'live', ':hostname', 'cert.pem' ].join(path.sep)
, chainPath: [ ':configDir', 'live', ':hostname', 'chain.pem' ].join(path.sep)
, rsaKeySize: 2048
, webrootPath: [ ':workDir', 'acme-challenge' ].join(path.sep)
};
module.exports.create = function (configs) {
var mergedConfigs;
if (configs && typeof configs.log === 'function') log = configs.log;
var store = {
getOptions: function () {
if (mergedConfigs) {
return configs;
}
if (!configs.domainKeyPath) {
configs.domainKeyPath = configs.privkeyPath || defaults.privkeyPath;
}
Object.keys(defaults).forEach(function (key) {
if (!configs[key]) {
configs[key] = defaults[key];
}
});
mergedConfigs = configs;
return configs;
}
, keypairs: {
checkAsync: function (keypath, format) {
if (!keypath) {
return null;
}
return fs.readFileAsync(keypath, 'ascii').then(function (key) {
if ('jwk' === format) {
return { privateKeyJwk: JSON.parse(key) };
}
else {
return { privateKeyPem: key };
}
}, function (err) {
if ('ENOENT' !== err.code) {
throw err;
}
return null;
});
}
, setAsync: function (keypath, keypair, format) {
return mkdirpAsync(path.dirname(keypath)).then(function () {
var key;
if ('jwk' === format) {
key = JSON.stringify(keypair.privateKeyJwk, null, ' ');
}
else {
key = keypair.privateKeyPem;
}
return fs.writeFileAsync(keypath, key, 'ascii').then(function () {
return keypair;
});
});
}
}
//
// Certificates
//
, certificates: {
// Certificates
checkKeypairAsync: function (args) {
if (!args.domainKeyPath) {
return PromiseA.reject(new Error("missing options.domainKeyPath"));
}
return store.keypairs.checkAsync(args.domainKeyPath, 'pem');
}
// Certificates
, setKeypairAsync: function (args, keypair) {
return store.keypairs.setAsync(args.domainKeyPath, keypair, 'pem');
}
// Certificates
, checkAsync: function (args) {
if (!args.fullchainPath || !args.privkeyPath || !args.certPath || !args.chainPath) {
return PromiseA.reject(new Error("missing one or more of privkeyPath, fullchainPath, certPath, chainPath from options"));
}
//, fs.readFileAsync(fullchainPath, 'ascii')
// note: if this ^^ gets added back in, the arrays below must change
return PromiseA.all([
fs.readFileAsync(args.privkeyPath, 'ascii') // 0
, fs.readFileAsync(args.certPath, 'ascii') // 1
, fs.readFileAsync(args.chainPath, 'ascii') // 2
, fs.readFileAsync(args.fullchainPath, 'ascii') // 3
// stat the file, not the link
, fs.statAsync(args.certPath) // 4
]).then(function (arr) {
return {
privkey: arr[0] // privkey.pem
, cert: arr[1] // cert.pem
, chain: arr[2] // chain.pem
, fullchain: arr[3] // fullchain.pem
/*
// TODO populate these values only if they are known
, issuedAt: arr[4].mtime.valueOf()
, expiresAt: arr[4].mtime.valueOf() + (90 * 24 * 60 * 60 * 100)
*/
};
}, function (err) {
if (args.debug) {
console.error("[le-store-certbot] certificates.check");
console.error(err.stack);
}
return null;
});
}
// Certificates
, setAsync: function (args) {
return store.configs.getAsync(args).then(function (pyobj) {
var pems = args.pems;
pyobj.checkpoints = parseInt(pyobj.checkpoints, 10) || 0;
var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]);
var certPath = args.certPath || pyobj.cert || path.join(liveDir, 'cert.pem');
var fullchainPath = args.fullchainPath || pyobj.fullchain || path.join(liveDir, 'fullchain.pem');
var chainPath = args.chainPath || pyobj.chain || path.join(liveDir, 'chain.pem');
var privkeyPath = args.privkeyPath || pyobj.privkey
|| args.domainKeyPath
|| path.join(liveDir, 'privkey.pem');
var archiveDir = args.archiveDir || path.join(args.configDir, 'archive', args.domains[0]);
var checkpoints = pyobj.checkpoints.toString();
var certArchive = path.join(archiveDir, 'cert' + checkpoints + '.pem');
var fullchainArchive = path.join(archiveDir, 'fullchain' + checkpoints + '.pem');
var chainArchive = path.join(archiveDir, 'chain'+ checkpoints + '.pem');
var privkeyArchive = path.join(archiveDir, 'privkey' + checkpoints + '.pem');
return mkdirpAsync(archiveDir).then(function () {
return PromiseA.all([
sfs.writeFileAsync(certArchive, pems.cert, 'ascii')
, sfs.writeFileAsync(chainArchive, pems.chain, 'ascii')
, sfs.writeFileAsync(fullchainArchive, pems.cert + pems.chain, 'ascii')
, sfs.writeFileAsync(privkeyArchive, pems.privkey, 'ascii')
]);
}).then(function () {
return mkdirpAsync(liveDir);
}).then(function () {
return PromiseA.all([
sfs.writeFileAsync(certPath, pems.cert, 'ascii')
, sfs.writeFileAsync(chainPath, pems.chain, 'ascii')
, sfs.writeFileAsync(fullchainPath, pems.cert + pems.chain, 'ascii')
, sfs.writeFileAsync(privkeyPath, pems.privkey, 'ascii')
]);
}).then(function () {
pyobj.checkpoints += 1;
args.checkpoints += 1;
// TODO other than for compatibility this is optional, right?
// or is it actually needful for renewal? (i.e. list of domains)
return writeRenewalConfig(args);
}).then(function () {
return {
privkey: pems.privkey
, cert: pems.cert
, chain: pems.chain
/*
// TODO populate these only if they are actually known
, issuedAt: Date.now()
, expiresAt: Date.now() + (90 * 24 * 60 * 60 * 100)
*/
};
});
});
}
}
//
// Accounts
//
, accounts: {
// Accounts
_getAccountKeyPath: function (args) {
var promise = PromiseA.resolve(args.accountId);
if (args.email && !args.accountKeyPath && !args.accountId) {
promise = store.accounts._getAccountIdByEmail(args);
}
return promise.then(function (accountId) {
if (!accountId) {
return null;
}
return args.accountKeyPath || path.join(args.accountsDir, accountId, 'private_key.json');
});
}
// Accounts
, _getAccountIdByEmail: function (args) {
// If we read 10,000 account directories looking for
// just one email address, that could get crazy.
// We should have a folder per email and list
// each account as a file in the folder
// TODO
var email = args.email;
if ('string' !== typeof email) {
log(args.debug, "No email given");
return PromiseA.resolve(null);
}
return fs.readdirAsync(args.accountsDir).then(function (nodes) {
log(args.debug, "success reading arg.accountsDir");
return PromiseA.all(nodes.map(function (node) {
return fs.readFileAsync(path.join(args.accountsDir, node, 'regr.json'), 'utf8').then(function (text) {
var regr = JSON.parse(text);
regr.__accountId = node;
return regr;
});
})).then(function (regrs) {
var accountId;
log(args.debug, "regrs.length", regrs.length);
regrs.some(function (regr) {
return regr.body.contact.some(function (contact) {
var match = contact.toLowerCase() === 'mailto:' + email.toLowerCase();
if (match) {
accountId = regr.__accountId;
return true;
}
});
});
if (!accountId) {
return null;
}
return accountId;
});
}).then(function (accountId) {
return accountId;
}, function (err) {
if ('ENOENT' === err.code) {
// ignore error
return null;
}
return PromiseA.reject(err);
});
}
// Accounts
, _getAccountIdByPublicKey: function (keypair) {
// we use insecure md5 - even though we know it's bad - because that's how the python client did
return require('crypto').createHash('md5').update(keypair.publicKeyPem).digest('hex');
}
// Accounts
, checkKeypairAsync: function (args) {
if (!(args.accountKeyPath || args.accountsDir)) {
return PromiseA.reject(new Error("must provide one of options.accountKeyPath or options.accountsDir"));
}
return store.accounts._getAccountKeyPath(args).then(function (keypath) {
return store.keypairs.checkAsync(keypath, 'jwk');
});
}
// Accounts
, setKeypairAsync: function (args, keypair) {
var accountId;
if (args.email) {
accountId = store.accounts._getAccountIdByPublicKey(keypair);
}
return store.accounts._getAccountKeyPath({
accountsDir: args.accountsDir
, email: args.email
, accountId: args.accountId || accountId
}).then(function (keypath) {
return store.keypairs.setAsync(keypath, keypair, 'jwk');
});
}
// Accounts
, checkAsync: function (args) {
var promise;
var files = {};
var accountId;
if (args.accountId) {
promise = PromiseA.resolve(args.accountId);
}
else if (args.email) {
promise = store.accounts._getAccountIdByEmail(args);
}
else {
promise = PromiseA.reject(new Error("must provide accountId or email"));
}
return promise.then(function (_accountId) {
log(args.debug, 'accountId:', _accountId);
if (!_accountId) {
return false;
}
accountId = _accountId;
var accountDir = path.join(args.accountsDir, accountId);
var configs = [ 'meta.json', 'private_key.json', 'regr.json' ];
return PromiseA.all(configs.map(function (filename) {
var keyname = filename.slice(0, -5);
return fs.readFileAsync(path.join(accountDir, filename), 'utf8').then(function (text) {
var data;
try {
data = JSON.parse(text);
} catch(e) {
files[keyname] = { error: e };
return;
}
files[keyname] = data;
return true;
}, function (err) {
log(args.debug, 'Error reading account files:', err);
files[keyname] = { error: err };
});
}));
}).then(function (hasAccount) {
if (!hasAccount) {
return null;
}
var err;
if (!Object.keys(files).every(function (key) {
return !files[key].error;
}) || !files.private_key || !files.private_key.n) {
err = new Error("Account '" + accountId + "' was corrupt (had id, but was missing files).");
err.code = 'E_ACCOUNT_CORRUPT';
err.data = files;
return PromiseA.reject(err);
}
//files.private_key;
//files.regr;
//files.meta;
files.accountId = accountId; // preserve current account id
files.id = accountId;
files.keypair = { privateKeyJwk: files.private_key };
return files;
});
}
// Accounts
, setAsync: function (args, reg) {
var os = require("os");
var accountId = store.accounts._getAccountIdByPublicKey(reg.keypair);
var accountDir = path.join(args.accountsDir, accountId);
var accountMeta = {
creation_host: os.hostname()
, creation_dt: new Date().toISOString()
};
return mkdirpAsync(accountDir).then(function () {
// TODO abstract file writing
return PromiseA.all([
// meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"}
fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(accountMeta), 'utf8')
// private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" }
, fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(reg.keypair.privateKeyJwk), 'utf8')
// regr.json:
/*
{ body: { contact: [ 'mailto:coolaj86@gmail.com' ],
agreement: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf',
key: { e: 'AQAB', kty: 'RSA', n: '...' } },
uri: 'https://acme-v01.api.letsencrypt.org/acme/reg/71272',
new_authzr_uri: 'https://acme-v01.api.letsencrypt.org/acme/new-authz',
terms_of_service: 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf' }
*/
, fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify({ body: reg.receipt }), 'utf8')
]);
}).then(function () {
return {
id: accountId
, accountId: accountId
, email: args.email
, keypair: reg.keypair
, receipt: reg.receipt
};
});
}
// Accounts
, getAccountIdAsync: function (args) {
var pyconf = PromiseA.promisifyAll(require('pyconf'));
return pyconf.readFileAsync(args.renewalPath).then(function (renewal) {
var accountId = renewal.account;
renewal = renewal.account;
return accountId;
}, function (err) {
if ("ENOENT" === err.code) {
return store.accounts._getAccountIdByEmail(args);
}
return PromiseA.reject(err);
});
}
}
//
// Configs
//
, configs: {
// Configs
checkAsync: function (copy) {
copy.domains = [];
return store.configs._checkHelperAsync(copy).then(function (pyobj) {
var exists = pyobj.checkpoints >= 0;
if (!exists) {
return null;
}
return pyToJson(pyobj);
});
}
// Configs
, _checkHelperAsync: function (args) {
var pyconf = PromiseA.promisifyAll(require('pyconf'));
return pyconf.readFileAsync(args.renewalPath).then(function (pyobj) {
return pyobj;
}, function () {
return pyconf.readFileAsync(path.join(__dirname, 'renewal.conf.tpl')).then(function (pyobj) {
return pyobj;
});
});
}
// Configs
, getAsync: function (args) {
return store.configs._checkHelperAsync(args).then(function (pyobj) {
var minver = pyobj.checkpoints >= 0;
args.pyobj = pyobj;
if (!minver) {
args.checkpoints = 0;
pyobj.checkpoints = 0;
return writeRenewalConfig(args);
}
// args.account.id = pyobj.account
// args.configDir = args.configDir || pyobj.configDir;
args.checkpoints = pyobj.checkpoints;
args.agreeTos = (args.agreeTos || pyobj.tos) && true;
args.email = args.email || pyobj.email;
args.domains = args.domains || pyobj.domains;
// yes, it's an array. weird, right?
args.webrootPath = args.webrootPath || pyobj.webrootPath[0];
args.server = args.server || args.acmeDiscoveryUrl || pyobj.server;
args.certPath = args.certPath || pyobj.cert;
args.privkeyPath = args.privkeyPath || pyobj.privkey;
args.chainPath = args.chainPath || pyobj.chain;
args.fullchainPath = args.fullchainPath || pyobj.fullchain;
//, workDir: args.workDir
//, logsDir: args.logsDir
args.rsaKeySize = args.rsaKeySize || pyobj.rsaKeySize;
args.http01Port = args.http01Port || pyobj.http01Port;
args.domainKeyPath = args.domainKeyPath || args.keyPath || pyobj.keyPath;
return writeRenewalConfig(args);
});
}
// Configs
, allAsync: function (copy) {
copy.domains = [];
return fs.readdirAsync(copy.renewalDir).then(function (nodes) {
nodes = nodes.filter(function (node) {
return /^[a-z0-9]+.*\.conf$/.test(node);
});
return PromiseA.all(nodes.map(function (node) {
copy.domains = [node.replace(/\.conf$/, '')];
return store.configs.getAsync(copy);
}));
});
}
}
};
return store;
};

251
lib/logger.js Normal file
View File

@@ -0,0 +1,251 @@
/* jshint -W097 */
/* jshint strict: false */
/*jslint node: true */
'use strict';
var winston = require('winston');
var DailyRotateFile = require('winston-daily-rotate-file');
var fs = require('fs');
var path = require('path');
var os = require('os');
var tools = require(__dirname + '/tools.js');
var SysLog;
var hostname = tools.getHostName();
try {
SysLog = require('winston-syslog').Syslog;
} catch (ex) {
//console.log('No syslog support');
}
var logger = function (level, files, noStdout, prefix) {
var userOptions = {};
var options = {
transports: []
};
//var defaultMaxSize;// = 10 * 1024 * 1024;
if (typeof files === 'string') {
files = [files];
}
files = files || [];
var isNpm = (__dirname.replace(/\\/g, '/').toLowerCase().indexOf('node_modules/' + tools.appName.toLowerCase() + '.js-controller') !== -1);
if (typeof level === 'object') {
userOptions = Object.assign({}, level);
level = userOptions.level;
prefix = userOptions.prefix;
noStdout = userOptions.noStdout;
if (userOptions.prefix !== undefined) delete userOptions.prefix;
if (userOptions.transport) {
var fName = 0;
for (var f in userOptions.transport) {
if (!userOptions.transport.hasOwnProperty(f)) continue;
if (userOptions.transport[f].type === 'file' && userOptions.transport[f].enabled !== false) {
userOptions.transport[f].filename = userOptions.transport[f].filename || 'log/' + tools.appName;
if (!userOptions.transport[f].fileext && userOptions.transport[f].filename.indexOf('.log') === -1) {
userOptions.transport[f].fileext = '.log';
}
if (!fName) userOptions.transport[f].systemLog = true;
userOptions.transport[f].handleExceptions = false;
userOptions.transport[f].name = !fName ? tools.appName : 'dailyRotateFile' + fName;
fName++;
userOptions.transport[f].filename = userOptions.transport[f].filename.replace(/\\/g, '/');
if (userOptions.transport[f].filename.match(/^\w:\/|^\//)) {
userOptions.transport[f].filename = path.normalize(userOptions.transport[f].filename);
} else {
userOptions.transport[f].filename = path.normalize(__dirname + (isNpm ? '/../../../' : '/../') + userOptions.transport[f].filename);
}
userOptions.transport[f].label = prefix || '';
userOptions.transport[f].level = userOptions.transport[f].level || level;
userOptions.transport[f].json = (userOptions.transport[f].json !== undefined) ? userOptions.transport[f].json : false;
userOptions.transport[f].silent = (userOptions.transport[f].silent !== undefined) ? userOptions.transport[f].silent : false;
userOptions.transport[f].colorize = (userOptions.transport[f].colorize !== undefined) ? userOptions.transport[f].colorize : ((userOptions.colorize === undefined) ? true : userOptions.colorize);
userOptions.transport[f].localTime = (userOptions.transport[f].localTime !== undefined) ? userOptions.transport[f].localTime : ((userOptions.localTime === undefined) ? true : userOptions.localTime);
// userOptions.transport[f].maxsize = (userOptions.transport[f].maxsize !== undefined) ? userOptions.transport[f].maxsize : defaultMaxSize;
userOptions.transport[f].timestamp = timestamp;
userOptions.transport[f].datePattern = '.yyyy-MM-dd' + (userOptions.transport[f].fileext || '');
/*userOptions.transport[f].logException = function (message, info, next, err) {
console.error(message);
};*/
var _log = new DailyRotateFile(userOptions.transport[f]);
options.transports.push(_log);
} else if (userOptions.transport[f].type === 'syslog' && userOptions.transport[f].enabled !== false) {
if (!SysLog) {
console.error('Syslog configured, but not installed!');
continue;
}
// host: The host running syslogd, defaults to localhost.
// port: The port on the host that syslog is running on, defaults to syslogd's default port.
// protocol: The network protocol to log over (e.g. tcp4, udp4, unix, unix-connect, etc).
// path: The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).
// pid: PID of the process that log messages are coming from (Default process.pid).
// facility: Syslog facility to use (Default: local0).
// localhost: Host to indicate that log messages are coming from (Default: localhost).
// sysLogType: The type of the syslog protocol to use (Default: BSD).
// app_name: The name of the application (Default: process.title).
// eol: The end of line character to be added to the end of the message (Default: Message without modifications).
// replace the used by syslog attribute "type" with own "sysLogType"
// If no name defined, use hostname as name
userOptions.transport[f].localhost = userOptions.transport[f].localhost || hostname;
if (userOptions.transport[f].sysLogType) {
userOptions.transport[f].type = userOptions.transport[f].sysLogType;
delete userOptions.transport[f].sysLogType;
} else {
delete userOptions.transport[f].type;
}
try {
options.transports.push(new SysLog(userOptions.transport[f]));
} catch (err) {
console.log('Cannot activate syslog: ' + err);
}
}
}
}
} else {
for (var i = 0; i < files.length; i++) {
var opt = {
name: !i ? tools.appName : 'dailyRotateFile' + i,
filename: path.normalize(isNpm ? __dirname + '/../../../log/' + files[i] : __dirname + '/../log/' + files[i]),
datePattern: '.yyyy-MM-dd.log',
json: false, // If true, messages will be logged as JSON (default true).
level: level,
silent: false,
localTime: true,
colorize: (userOptions.colorize === undefined) ? true : userOptions.colorize,
timestamp: timestamp,
label: prefix || '',
handleExceptions: false
//maxsize: defaultMaxSize
};
options.transports.push(new DailyRotateFile(opt));
}
}
if (!noStdout) {
options.transports.push(new winston.transports.Console({
level: level,
silent: false,
colorize: (userOptions.colorize === undefined) ? true : userOptions.colorize,
timestamp: timestamp,
label: prefix || ''
}));
}
var log = new winston.Logger(options);
log.getFileName = function () {
if (this.transports && this.transports[tools.appName]) {
if (this.transports[tools.appName].filename) {
return this.transports[tools.appName].dirname + '/' + this.transports[tools.appName].filename;
} else if (this.transports[tools.appName]._getFilename) {
return this.transports[tools.appName].dirname + '/' + this.transports[tools.appName]._getFilename();
} else {
return '';
}
} else {
return '';
}
};
log.activateDateChecker = function (isEnabled, daysCount) {
if (!isEnabled && this._fileChecker) {
clearInterval(this._fileChecker);
} else if (isEnabled && !this._fileChecker) {
if (!daysCount) daysCount = 3;
// Check every hour
this._fileChecker = setInterval(function () {
if (this.transports[tools.appName] && fs.existsSync(this.transports[tools.appName].dirname)) {
var files = fs.readdirSync(this.transports[tools.appName].dirname);
var for3days = new Date();
for3days.setDate(for3days.getDate() - daysCount);
for (var i = 0; i < files.length; i++) {
var match = files[i].match(/.+\.(\d+-\d+-\d+)/);
if (match) {
var date = new Date(match[1]);
if (date < for3days) {
// delete file
try {
this.transports[tools.appName].log('info', 'host.' + hostname + ' Delete log file ' + files[i]);
fs.unlinkSync(this.transports[tools.appName].dirname + '/' + files[i]);
} catch (e) {
// there is a bug under windows, that file stays opened and cannot be deleted
this.log(os.platform().match(/^win/) ? 'info' : 'error', 'host.' + hostname + ' Cannot delete file "' + path.normalize(this.transports[tools.appName].dirname + '/' + files[i]) + '": ' + e);
}
}
}
}
}
}.bind(this), 3600000); // every hour
}
};
winston.unhandleExceptions();
return log;
};
function timestamp() {
var ts = new Date();
var result = ts.getFullYear() + '-';
/** @type {number | string} */
var value = ts.getMonth() + 1;
if (value < 10) value = '0' + value;
result += value + '-';
value = ts.getDate();
if (value < 10) value = '0' + value;
result += value + ' ';
value = ts.getHours();
if (value < 10) value = '0' + value;
result += value + ':';
value = ts.getMinutes();
if (value < 10) value = '0' + value;
result += value + ':';
value = ts.getSeconds();
if (value < 10) value = '0' + value;
result += value + '.';
value = ts.getMilliseconds();
if (value < 10) {
value = '00' + value;
} else
if (value < 100) {
value = '0' + value;
}
result += value + ' ';
return result;
}
module.exports = logger;

215
lib/multihostClient.js Normal file
View File

@@ -0,0 +1,215 @@
'use strict';
var dgram = require('dgram');
var crypto = null;
var port = 50005;
var MULTICAST_ADDR = '239.255.255.250';
function MHClient(hostname, logger, config, info) {
var id = 1;
var server;
var timer;
function getIPs() {
var ifaces = require('os').networkInterfaces();
var ipArr = [];
for (var dev in ifaces) {
if (!ifaces.hasOwnProperty(dev)) continue;
/*jshint loopfunc:true */
ifaces[dev].forEach(function (details) {
//noinspection JSUnresolvedVariable
if (!details.internal) ipArr.push(details.address);
});
}
return ipArr;
}
function stopServer() {
if (server) {
try {
server.close();
} catch (e) {
}
server = null;
}
if (timer) {
clearTimeout(timer);
timer = null;
}
}
function sha(secret, salt, callback) {
// calculate sha256
crypto = crypto || require('crypto');
var hash = crypto.createHash('sha256');
hash.on('readable', function () {
var data = hash.read();
if (data) {
callback(data.toString('hex'));
}
});
hash.write(secret + salt);
hash.end();
}
function startServer(isBroadcast, timeout, onReady, onMessage, onFinished) {
if (server) {
onFinished('Some operation still active');
return;
}
server = dgram.createSocket('udp4');
timeout = parseInt(timeout, 10) || 2000;
timer = setTimeout(function () {
stopServer();
if (onFinished) {
onFinished(null);
onFinished = null;
}
}, timeout);
server.on('error', function (err) {
stopServer();
if (onFinished) {
onFinished(err);
onFinished = null;
}
});
server.on('message', function (msg, rinfo) {
msg = msg.toString();
try {
msg = JSON.parse(msg);
if (onMessage) {
if (onMessage(server, msg, rinfo)) {
stopServer();
onFinished = null;
}
}
} catch (e) {
console.error('Invalid answer: ' + msg);
}
});
server.on('listening', function (msg, rinfo) {
// var address = server.address();
if (isBroadcast) server.setBroadcast(true);
onReady && onReady(server);
});
server.bind();
}
this.browse = function (timeout, isDebug, callback) {
var result = [];
var ownIps = getIPs();
startServer(true, timeout,
function onReady(srv) {
var text = JSON.stringify({
cmd: 'browse',
id: ++id
});
server.send(text, 0, text.length, port, MULTICAST_ADDR);
},
function onMessage (srv, msg, rinfo) {
// ignore own answers
if (isDebug || rinfo.address !== '127.0.0.1' && ownIps.indexOf(rinfo.address) === -1) {
if (msg.result === 'not authenticated') {
result.push({ip: rinfo.address, hostname: rinfo.address, info: 'authentication required', auth: msg.auth});
} else if (msg.result === 'ok') {
result.push(msg);
} else {
console.log('Unknown answer: ' + JSON.stringify(msg));
}
}
if (isDebug) {
console.log(JSON.stringify(msg));
}
},
function onFinished(err) {
callback(err, result);
}
);
};
this.connect = function (ip, password, callback) {
startServer(false, 2000,
function onReady(srv) {
var text = JSON.stringify({
cmd: 'browse',
id: ++id
});
server.send(text, 0, text.length, port, ip);
},
function onMessage (srv, msg, rinfo) {
if (msg.cmd === 'browse' && msg.id === id) {
if (msg.result === 'ok') {
if (callback) {
if (!msg.objects) {
if (callback) {
callback('Invalid configuration received: ' + JSON.stringify(msg));
callback = null;
}
} else if (!msg.states) {
if (callback) {
callback('Invalid configuration received: ' + JSON.stringify(msg));
callback = null;
}
} else {
callback && callback(null, msg.objects, msg.states, rinfo.address);
}
}
} else if (msg.result === 'not authenticated') {
if (!password) {
if (callback) {
callback('not authenticated' + msg);
callback = null;
}
} else {
sha(password, msg.salt, function (shaText) {
// send password
var text = JSON.stringify({
cmd: 'browse',
id: ++id,
password: shaText
});
server.send(text, 0, text.length, port, ip);
});
return false;
}
} else if (msg.result === 'invalid password') {
if (callback) {
callback('invalid password');
callback = null;
}
} else {
console.log(msg.result);
}
return true;
} else {
console.warn('Unexpected message: ' + JSON.stringify(msg));
}
},
function onFinished(err) {
if (callback) {
callback(err);
callback = null;
}
}
);
};
return this;
}
module.exports = MHClient;

275
lib/multihostServer.js Normal file
View File

@@ -0,0 +1,275 @@
'use strict';
var dgram = require('dgram');
var port = 50005;
var MULTICAST_ADDR = '239.255.255.250';
function MHServer(hostname, logger, config, info, ips, secret) {
var server = null;
var count = 0;
var initTimer = null;
var buffer = {};
var lastFrame = {};
var crypto;
var that = this;
var authList = {};
config = Object.assign({}, config); // make a copy
if (config.objects) {
config.objects = {
type: config.objects.type,
host: config.objects.host,
port: config.objects.port,
user: config.objects.user,
pass: config.objects.pass
};
}
if (config.states) {
config.states = {
type: config.states.type,
host: config.states.host,
port: config.states.port,
user: config.states.user,
pass: config.states.pass,
options: config.states.options,
maxQueue: config.states.maxQueue
};
}
function send(msg, rinfo) {
if (server) {
setImmediate(function () {
var text = JSON.stringify(msg);
try {
server.send(text, 0, text.length, rinfo.port, rinfo.address);
} catch (e) {
logger.warn('host.' + hostname + ' cannot send answer to ' + rinfo.address + ':' + rinfo.port + ': ' + e);
}
});
}
}
// delete all old connections
function checkAuthList(ts) {
ts = ts || new Date().getTime();
for (var id in authList) {
if (authList.hasOwnProperty(id)) {
if (!authList[id]) {
delete authList[id];
} else if (ts - authList[id].ts > 31000) {
delete authList[id];
}
}
}
}
function isSlave(oHost, sHots, ownIps) {
return !(oHost === 'localhost' || oHost === '127.0.0.1' || ownIps.indexOf(oHost) !== -1);
}
function sha(secret, salt, callback) {
// calculate sha256
crypto = crypto || require('crypto');
var hash = crypto.createHash('sha256');
hash.on('readable', function () {
var data = hash.read();
if (data) {
callback(data.toString('hex'));
}
});
hash.write(secret + salt);
hash.end();
}
// hello => auth => browse
function process(msg, rinfo) {
if (!msg) return;
var ts = new Date().getTime();
checkAuthList(ts);
var id = rinfo.address + ':' + rinfo.port;
switch (msg.cmd) {
case 'browse':
if (secret && msg.password && authList[id]) {
sha(secret, authList[id].salt, function (shaText) {
if (shaText !== msg.password) {
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
result: 'invalid password'
}, rinfo);
} else {
authList[id].auth = true;
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
objects: config.objects,
states: config.states,
info: info,
hostname: hostname,
slave: isSlave(config.objects.host, config.states.host, ips),
result: 'ok'
}, rinfo);
}
});
return;
}
if (!config.multihostService.secure || (authList[id] && authList[id].auth)) {
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
objects: config.objects,
states: config.states,
info: info,
hostname: hostname,
slave: isSlave(config.objects.host, config.states.host, ips),
result: 'ok'
}, rinfo);
} else {
authList[id] = {
time: ts,
salt: (Math.random() * 1000000 + ts).toString().substring(0, 16),
auth: false
};
// padding
if (authList[id].salt.length < 16) {
authList[id].salt += new Array(16 - authList[id].salt.length).join('_');
}
send({
auth: config.multihostService.secure,
cmd: msg.cmd,
id: msg.id,
result: 'not authenticated',
salt: authList[id].salt
}, rinfo);
}
break;
default:
send({
cmd: msg.cmd,
id: msg.id,
result: 'unknown command'
}, rinfo);
break;
}
}
this.init = function () {
if (initTimer) {
clearTimeout(initTimer);
initTimer = null;
}
if (count > 10) {
logger.warn('host.' + hostname + ' Port ' + port + ' is occupied. Service stopped.');
return;
}
server = dgram.createSocket('udp4');
server.on('error', function (err) {
logger.error('host.' + hostname + ' multihost service error: ' + err.stack);
server.close();
server = null;
if (!initTimer) {
initTimer = setTimeout(function () {
initTimer = null;
that.init();
}, 5000);
}
});
server.on('close', function (err) {
server = null;
if (!initTimer) {
initTimer = setTimeout(function () {
initTimer = null;
that.init();
}, 5000);
}
});
server.on('message', function (msg, rinfo) {
// following messages are allowed
var text = msg.toString();
var now = new Date().getTime();
var id = rinfo.address + ':' + rinfo.port;
for (var ids in buffer) {
if (!lastFrame[ids]) {
delete buffer[ids];
} else if (now - lastFrame[ids] > 1000) {
delete buffer[ids];
delete lastFrame[ids];
}
}
if (lastFrame[id] && now - lastFrame[id] > 1000) {
buffer[id] = '';
}
lastFrame[id] = now;
if (!buffer[id] && text[0] !== '{') {
// ignore message
logger.warn('host.' + hostname + ' Message ignored: ' + text);
} else {
buffer[id] = (buffer[id] || '') + msg.toString();
if (buffer[id] && buffer[id][buffer[id].length - 1] === '}') {
try {
var data = JSON.parse(buffer[id]);
buffer[id] = '';
if (data) {
process(data, rinfo);
}
} catch (e) {
// may be not yet complete.
}
}
}
});
server.on('listening', function () {
server.addMembership(MULTICAST_ADDR);
var address = server.address();
logger.warn('host.' + hostname + ' multihost service started on ' + address.address + ':' + address.port);
});
server.bind(50005);
};
this.close = function (callback) {
if (initTimer) {
clearTimeout(initTimer);
initTimer = null;
}
if (server) {
try {
server.close(callback);
server = null;
} catch (e) {
server = null;
callback && callback()
}
} else if (callback) {
callback();
}
};
this.init();
return this;
}
module.exports = MHServer;

13
lib/objects.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
var getConfigFileName = require(__dirname + '/tools').getConfigFileName;
var config = JSON.parse(require('fs').readFileSync(getConfigFileName(), 'utf8'));
if (!config.objects) config.objects = {type: 'file'};
if (config.objects.type === 'file') {
module.exports = require(__dirname + '/objects/objectsInMemClient');
} else if (config.objects.type === 'redis') {
module.exports = require(__dirname + '/objects/objectsInRedis');
} else {
throw 'Unknown objects type: ' + config.objects.type;
}

View File

@@ -0,0 +1,501 @@
/**
* Object DB in memory - Client
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
*
* MIT License
*
*/
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
/* jshint -W061 */
'use strict';
const io = require('socket.io-client');
const util = require('util');
const stream = require('stream');
const Writable = stream.Writable;
let memStore = {};
/* Writable memory stream */
function WMStrm(key, options) {
// allow use without new operator
if (!(this instanceof WMStrm)) {
return new WMStrm(key, options);
}
Writable.call(this, options); // init super
this.key = key; // save key
memStore[key] = new Buffer(''); // empty
}
util.inherits(WMStrm, Writable);
WMStrm.prototype._write = function (chunk, enc, cb) {
if (chunk) {
// our memory store stores things in buffers
let buffer = (Buffer.isBuffer(chunk)) ?
chunk : // already is Buffer use it
new Buffer(chunk, enc); // string, convert
// concatenate to the buffer already there
if (!memStore[this.key]) {
memStore[this.key] = new Buffer('');
console.log('memstore for ' + this.key + ' is null');
}
memStore[this.key] = Buffer.concat([memStore[this.key], buffer]);
}
if (!cb) throw 'No callback for WMStrm.prototype._write';
cb();
};
function ObjectsInMemClient(settings) {
let client;
let that = this;
let subscribes = [];
let connectionTimeout;
let log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/*console.log(msg);*/},
debug: function (msg) {/*console.log(msg);*/},
info: function (msg) {/*console.log(msg);*/},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
let __construct = (function () {
if (!settings.connection.secure) {
client = io.connect('http://' + (settings.connection.host !=='0.0.0.0' ? settings.connection.host || '127.0.0.1' : '127.0.0.1') + ':' + (settings.connection.port || 9001));
} else {
client = io.connect('https://' + (settings.connection.host !=='0.0.0.0' ? settings.connection.host || '127.0.0.1' : '127.0.0.1') + ':' + (settings.connection.port || 9001));
}
if (typeof settings.change === 'function') {
client.on('message', function (pattern, channel, message) {
log.silly(settings.namespace + ' inMem message ', pattern, channel, message);
try {
settings.change(channel, message);
} catch (e) {
log.error(settings.namespace + ' message ' + channel + ' ' + message + ' ' + e.message);
log.error(settings.namespace + ' ' + e.stack);
}
});
}
client.on('disconnect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
}
});
client.on('error', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.error(settings.namespace + ' ' + error.message);
log.error(settings.namespace + ' ' + error.stack);
}
});
client.on('connect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.connected === 'function') settings.connected('InMemoryDB ' + settings.connection.host + ':' + settings.connection.port);
});
client.on('reconnect', function (error) {
// Re-initialise subscribes
for (let i = 0; i < subscribes.length; i++) {
client.emit('subscribe', subscribes[i]);
}
if (typeof settings.connected === 'function') settings.connected('InMemoryDB ' + settings.connection.host + ':' + settings.connection.port);
});
connectionTimeout = setTimeout(function () {
if (typeof settings.connectTimeout === 'function') settings.connectTimeout('Connection timeout');
connectionTimeout = null;
}, 5000);
})();
settings = settings || {};
this.subscribe = function (pattern, options) {
if (subscribes.indexOf(pattern) === -1) subscribes.push(pattern);
if (!client) return;
client.emit('subscribe', pattern, options);
};
this.unsubscribe = function (pattern) {
let pos = subscribes.indexOf(pattern);
if (pos !== -1) subscribes.splice(pos, 1);
if (!client) return;
client.emit('unsubscribe', pattern);
};
this.destroy = function (callback) {
if (!client) return;
// Client may not close the DB
if (callback) callback();
//client.emit('destroy', callback);
};
this.enableFileCache = function (enabled, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('enableFileCache', enabled, options, callback);
};
this.insert = function (id, attName, ignore, options, obj, callback) {
//return pipe for write into redis
let strm = new WMStrm(id + '/' + attName);
strm.on('finish', function () {
if (!memStore[id + '/' + attName]) log.error(settings.namespace + ' File ' + id + ' / ' + attName + ' is empty');
that.writeFile(id, attName, memStore[id + '/' + attName], options, function () {
if (memStore[id + '/' + attName] !== undefined) delete memStore[id + '/' + attName];
if (callback) callback(null, null);
});
});
return strm;
};
this.writeFile = function (id, name, data, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('writeFile', id, name, data, options, callback);
};
this.readFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('readFile', id, name, options, callback);
};
this.readDir = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('readDir', id, name, options, callback);
};
this.readDirAsZip = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('readDirAsZip', id, name, options, callback);
};
this.writeDirAsZip = function (id, name, options, data, callback) {
if (typeof data === 'function') {
callback = data;
data = options;
options = null;
}
if (!client) return;
client.emit('writeDirAsZip', id, name, options, data, callback);
};
this.unlink = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('unlink', id, name, options, callback);
};
this.delFile = this.unlink;
this.rename = function (id, oldName, newName, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('rename', id, oldName, newName, options, callback);
};
this.mkdir = function (id, dirname, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('mkdir', id, dirname, options, callback);
};
this.chmodFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chmodFile', id, name, options, callback);
};
this.chownFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chownFile', id, name, options, callback);
};
this.touch = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('touch', id, name, options, callback);
};
this.rm = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('rm', id, name, options, callback);
};
this.chmodObject = function (pattern, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chmodObject', pattern, options, callback);
};
this.chownObject = function (pattern, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('chownObject', pattern, options, callback);
};
this.getObjectView = function (design, search, params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjectView', design, search, params, options, callback);
};
this.getObjectList = function (params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjectList', params, options, callback);
};
this.getObjectListAsync = (params, options) => {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.getObjectList(params, options, (err, arr) => {
if (err) {
reject(err);
} else {
resolve(arr);
}
})
});
};
this.extendObject = function (id, obj, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('extendObject', id, obj, options, callback);
};
this.extendObjectAsync = function (id, obj, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.extendObject(id, obj, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.getKeys = function (pattern, options, callback, dontModify) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getKeys', pattern, options, callback, dontModify);
};
this.getConfigKeys = this.getKeys;
this.getObjects = function (keys, options, callback, dontModify) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjects', keys, options, callback, dontModify);
};
this.getObjectsByPattern = (pattern, options, callback) => {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObjectsByPattern', pattern, options, callback);
};
this.getObjectsByPatternAsync = (pattern, options) => {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.getObjectsByPattern(pattern, options, (err, objs) => {
if (err) {
reject(err);
} else {
resolve(objs);
}
});
});
};
this.setObject = function (id, obj, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('setObject', id, obj, options, callback);
};
this.setObjectAsync = function (id, obj, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.setObject(id, obj, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.delObject = function (id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('delObject', id, options, callback);
};
this.delObjectAsync = function (id, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.delObject(id, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.getObject = function (id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('getObject', id, options, callback);
};
this.getObjectAsync = function (id, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.getObject(id, options, (err, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
};
this.findObject = function (idOrName, type, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('findObject', idOrName, type, options, callback);
};
this.findObjectAsync = function (idOrName, type, options) {
if (!client) return Promise.reject('No client');
return new Promise((resolve, reject) => {
this.findObject(idOrName, type, options, (err, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
};
this.destroyDB = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!client) return;
client.emit('destroyDB', options, callback);
};
}
module.exports = ObjectsInMemClient;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

87
lib/password.js Normal file
View File

@@ -0,0 +1,87 @@
/**
*
* password hash and check
*
* 7'2014-2016 Bluefox <dogafox@gmail.com>
* 2014 hobbyquaker <hq@ccu.io>
*
* derived from https://github.com/florianheinemann/password-hash-and-salt/ (MIT License)
*
* The created hash is of the following format: <algorithm>$<iterations>$<hash>$<salt>
*
* Usage Example:
var password = require('./lib/password.js');
password('test').hash(null, null, function (err, res) {
console.log(res);
password('test').check(res, function (err, res) {
console.log('test: ' + res);
});
password('muh').check(res, function (err, res) {
console.log('muh: ' + res);
});
});
*
*/
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const crypto = require('crypto');
let version = null;
const password = pw => {
return {
hash: (salt, iterations, callback) => {
salt = salt || crypto.randomBytes(16).toString('hex');
iterations = iterations || 10000;
// version 0.10 has no 'sha256' and this option must be ignored
if (version === null) {
version = process.version.replace('v', '');
version = !version.match(/^0\.10\./);
}
if (version) {
crypto.pbkdf2(pw, salt, iterations, 256, 'sha256', (err, key) => {
if (err) return callback(err);
callback(null, `pbkdf2$${iterations}$${key.toString('hex')}$${salt}`);
});
} else {
crypto.pbkdf2(pw, salt, iterations, 64, (err, key) => {
if (err) return callback(err);
callback(null, `pbkdf2$${iterations}$${key.toString('hex')}$${salt}`);
});
}
},
check: function (hashedPassword, callback) {
if (!hashedPassword || !password) return callback(null, false);
let key = hashedPassword.split('$');
if (key.length !== 4 || !key[2] || !key[3]) return callback('Hash not formatted correctly');
if (key[0] !== 'pbkdf2') return callback('Unknown');
this.hash(key[3], parseInt(key[1], 10), function (error, newHash) {
if (error) return callback(error);
callback(null, newHash === hashedPassword);
});
},
complexity: callback => {
// Todo: Check for password complexity
return true;
}
};
};
module.exports = password;

159
lib/preinstall_check.js Normal file
View File

@@ -0,0 +1,159 @@
'use strict';
// we cannot use semver here, because dependencies are not installed yet
// so the version checks get a bit messy
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const os = require('os');
// where yunkong2 is installed
const rootDir = __dirname.substr(0, __dirname.lastIndexOf('node_modules'));
function checkNpmVersion() {
// Get npm version
try {
// remove local node_modules\.bin dir from path
// or we potentially get a wrong npm version
let newEnv = Object.assign({}, process.env);
newEnv.PATH = (newEnv.PATH || newEnv.Path || newEnv.path)
.split(path.delimiter)
.filter(dir => {
dir = dir.toLowerCase();
return !(dir.indexOf('yunkong2') > -1 && dir.indexOf(path.join('node_modules', '.bin')) > -1);
})
.join(path.delimiter);
let npmVersion = child_process.execSync('npm -v', {encoding: 'utf8', env: newEnv});
npmVersion = npmVersion.trim();
console.log('NPM version: ' + npmVersion);
if (gte(npmVersion, '5.0.0') && lt(npmVersion, '5.7.1')) {
console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
console.warn('WARNING:');
console.warn('You are using an unsupported npm version!');
console.warn('This can lead to problems when installing further packages');
console.warn();
console.warn('Please use "npm install -g npm@4" to downgrade npm to 4.x or ');
console.warn('use "npm install -g npm@latest" to install a supported version of npm 5!');
console.warn('You need to make sure to repeat this step after installing an update to NodeJS and/or npm.');
console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
return npmVersion;
} catch (e) {
console.error('Could not check npm version: ' + e);
console.error('Assuming that correct version is installed.');
process.exit(0);
}
}
if (gte(checkNpmVersion(), '5.0.0')) {
// disables NPM's package-lock.json on NPM >= 5 because that creates heaps of problems
console.log('npm version >= 5: disabling package-lock');
fs.writeFileSync(path.join(rootDir, '.npmrc'), 'package-lock=false' + os.EOL, 'utf8');
}
process.exit(0);
// ======================================
// all the functions to replace `semver`:
/**
* @typedef {{major: number, minor: number, build: number}} Version
*/
/**
* Parses a version string
* @param {string} version The version string to parse
* @returns {Version | null} The parsed version
*/
function parseVersion(version) {
const versionRegExp = /^v?(\d+)\.(\d+)\.(\d+).*?/;
const parsed = versionRegExp.exec(version);
if (!parsed) return null;
return {
major: +parsed[1],
minor: +parsed[2],
build: +parsed[3]
};
}
/**
* Checks if v1 > v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function gt(v1, v2) {
if (typeof v1 === "string") v1 = parseVersion(v1);
if (typeof v2 === "string") v2 = parseVersion(v2);
if (v1.major > v2.major) return true;
else if (v1.major < v2.major) return false;
if (v1.minor > v2.minor) return true;
else if (v1.minor < v2.minor) return false;
return (v1.build > v2.build);
}
/**
* Checks if v1 < v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function lt(v1, v2) {
if (typeof v1 === "string") v1 = parseVersion(v1);
if (typeof v2 === "string") v2 = parseVersion(v2);
if (v1.major < v2.major) return true;
else if (v1.major > v2.major) return false;
if (v1.minor < v2.minor) return true;
else if (v1.minor > v2.minor) return false;
return (v1.build < v2.build);
}
/**
* Checks if v1 == v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function eq(v1, v2) {
if (typeof v1 === "string") v1 = parseVersion(v1);
if (typeof v2 === "string") v2 = parseVersion(v2);
if (v1.major !== v2.major) return false;
if (v1.minor !== v2.minor) return false;
return v1.build === v2.build;
}
/**
* Checks if v1 != v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function ne(v1, v2) {
return !eq(v1, v2);
}
/**
* Checks if v1 >= v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function gte(v1, v2) {
return gt(v1, v2) || eq(v1, v2);
}
/**
* Checks if v1 <= v2
* @param {Version | string} v1
* @param {Version | string} v2
*/
function lte(v1, v2) {
return lt(v1, v2) || eq(v1, v2);
}

68
lib/renewal.conf.tpl Normal file
View File

@@ -0,0 +1,68 @@
#cert = :configDir/live/:hostname/cert.pem
cert = :cert_path
privkey = :privkey_path
chain = :chain_path
fullchain = :fullchain_path
# Options and defaults used in the renewal process
[renewalparams]
apache_enmod = a2enmod
no_verify_ssl = False
ifaces = None
apache_dismod = a2dismod
register_unsafely_without_email = False
uir = None
installer = none
config_dir = :configDir
text_mode = True
# junk?
# https://github.com/letsencrypt/letsencrypt/issues/1955
func = <function obtain_cert at 0x30c9500>
prepare = False
work_dir = :work_dir
tos = :agree_tos
init = False
http01_port = :http_01_port
duplicate = False
# this is for the domain
key_path = :privkey_path
nginx = False
fullchain_path = :fullchain_path
email = :email
csr = None
agree_dev_preview = None
redirect = None
verbose_count = -3
config_file = None
renew_by_default = True
hsts = False
authenticator = webroot
domains = :hostnames #comma,delimited,list
rsa_key_size = :rsa_key_size
# starts at 0 and increments at every renewal
checkpoints = -1
manual_test_mode = False
apache = False
cert_path = :cert_path
webroot_path = :webroot_paths # comma,delimited,list
strict_permissions = False
apache_server_root = /etc/apache2
# https://github.com/letsencrypt/letsencrypt/issues/1948
account = :account_id
manual_public_ip_logging_ok = False
chain_path = :chain_path
standalone = False
manual = False
server = :acme_discovery_url
standalone_supported_challenges = "http-01,tls-sni-01"
webroot = True
apache_init_script = None
user_agent = None
apache_ctl = apache2ctl
apache_le_vhost_ext = -le-ssl.conf
debug = False
tls_sni_01_port = 443
logs_dir = :logs_dir
configurator = None
[[webroot_map]]
# :hostname = :webroot_path

181
lib/restart.js Normal file
View File

@@ -0,0 +1,181 @@
/**
* @fileOverview restart the controller
* @author bluefox
* @version 0.1
*/
'use strict';
/** @module restart */
var fs = require('fs');
var tools = require(__dirname + '/tools.js');
var logFile;
try {
var dir = __dirname + '/../' + tools.getDefaultDataDir() + '../log';
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
logFile = dir + '/restart.log';
console.log(require('path').normalize(logFile));
if (fs.existsSync(logFile)) fs.unlinkSync(logFile);
fs.writeFileSync(logFile, 'started\n');
} catch (err) {
console.error(err);
}
function log(text) {
var t = (new Date()).toString() + text;
console.log(t);
fs.appendFileSync(logFile, t + '\n');
}
function checkRoot(callback) {
var data = '';
var child = require('child_process').spawn('whoami', []);
child.stdout.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.stderr.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.on('exit', function (exitCode) {
callback(data.trim() == 'root');
});
}
function killPidsScript(callback) {
checkRoot(function (isRoot) {
var data = '';
try {
fs.chmodSync(__dirname + '/../killall.sh', '777');
if (isRoot) {
fs.writeFileSync(__dirname + '/../killall.sh', "sudo pgrep -f '^io.*' | sudo xargs kill -9\nsudo pgrep -f '^node-red*' | sudo xargs kill -9");
} else {
fs.writeFileSync(__dirname + '/../killall.sh', "pgrep -f '^io.*' | xargs kill -9\pgrep -f '^node-red*' | xargs kill -9");
}
var child = require('child_process').spawn(__dirname + '/../killall.sh', []);
child.stdout.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.stderr.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.on('exit', function (exitCode) {
if (exitCode) log('Exit code for "killall.sh": ' + exitCode);
callback(exitCode, data);
});
} catch (e) {
log('Cannot create "' + __dirname + '/../killall.sh"');
callback(-1);
}
});
}
function killPid(pid, callback) {
var data = '';
var child = require('child_process').spawn('kill', ['-KILL', pid]);
child.stdout.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.stderr.on('data', function (text) {
data += text.toString().replace('\n', '');
});
child.on('exit', function (exitCode) {
if (exitCode) log('Exit code for "kill -KILL ' + pid + '": ' + exitCode);
callback(exitCode, data);
});
}
function killPids(pids, callback) {
if (pids && pids.length) {
killPid(pids.pop(), function () {
killPids(pids, callback);
});
} else {
callback();
}
}
if (require('os').platform().match(/^win/) && fs.existsSync(__dirname + '/../_service_' + tools.appName + '.bat')) {
log('Restarting service ' + tools.appName + '...');
var spawn = require('child_process').spawn;
var out;
var err;
var stat;
var fileName;
if (fs.existsSync(__dirname + '/../../../log')) {
fileName = __dirname + '/../../../log/restart.log';
} else {
fileName = __dirname + '/../log/restart.log';
}
stat = fs.statSync(fileName);
if (stat.size > 1024 * 1024) {
try {
fs.unlinkSync(fileName);
} catch (e) {
console.log('File is too big, but cannot delete restart.log: ' + e.toString());
}
}
out = fs.openSync(fileName, 'a');
err = out;
log('Starting ' + __dirname + '/../_service_' + tools.appName + '.bat');
var child = spawn('cmd.exe', ['/c', __dirname + '/../_service_' + tools.appName + '.bat'], {
detached: true,
stdio: ['ignore', out, err]
});
child.unref();
process.exit();
} else if (!fs.existsSync(__dirname + '/' + tools.appName + '.pid')) {
log(tools.appName + ' was started manually or was not running. Please restart it manually.');
} else {
log('Restarting ' + tools.appName + '...');
var daemon = require('daemonize2').setup({
main: '../controller.js',
name: tools.appName + ' controller',
pidfile: __dirname + '/' + tools.appName + '.pid',
stopTimeout: 5000
});
log('Stopping daemon ' + tools.appName + '...');
daemon.stop(function (err, pid) {
// force to stop all adapters
if (fs.existsSync(__dirname + '/../pids.txt')) {
try {
var pids = JSON.parse(fs.readFileSync(__dirname + '/../pids.txt').toString());
killPids(pids, function () {
killPidsScript(function () {
log('Starting daemon ' + tools.appName + '...');
daemon.start(function (err, pid) {
log('Daemon ' + tools.appName + ' started');
process.exit();
});
});
});
} catch (e) {
log('Error by pids.txt: ' + e);
log('Starting daemon ' + tools.appName + '...');
daemon.start(function (err, pid) {
log('Daemon ' + tools.appName + ' started');
process.exit();
});
}
} else {
log('Starting daemon ' + tools.appName + '...');
daemon.start(function (err, pid) {
log('Daemon ' + tools.appName + ' started');
process.exit();
});
}
});
}

112
lib/scripts/scripts.js Normal file
View File

@@ -0,0 +1,112 @@
var https = require('https');
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
function httpsGet(link, callback) {
https.get(link, function (res) {
var statusCode = res.statusCode;
if (statusCode !== 200) {
// consume response data to free up memory
res.resume();
callback(statusCode, null, link);
}
res.setEncoding('utf8');
var rawData = '';
res.on('data', function (chunk) {
rawData += chunk;
});
res.on('end', function () {
callback(null, rawData ? rawData.toString() : null, link);
});
}).on('error', function (e) {
callback(e.message, null, link);
});
}
var stableURL = 'https://raw.githubusercontent.com/' + tools.appName + '/' + tools.appName + '.repositories/master/sources-dist-stable.json';
function updateVersion(name, callback, _sources) {
if (!_sources) {
httpsGet(stableURL, function (err, body) {
updateVersion(name, callback, JSON.parse(body));
});
}
var cmd = 'npm show ' + tools.appName + '.' + name + ' version';
var exec = require('child_process').exec;
var result = '';
var child = exec(cmd, function(error, stdout, stderr){
result = stdout;
});
child.stderr.pipe(process.stdout);
child.on('exit', function (code /* , signal */) {
if (code) {
console.error('host.' + tools.getHostName() + ' Cannot get version of ' + tools.appName + '.' + name + ': ' + code);
callback(code, _sources, name, null);
} else {
_sources[name].version = result;
callback(null, _sources, name, result);
}
});
}
function updateVersions(callback) {
httpsGet(stableURL, function (err, body) {
var sources = JSON.parse(body);
var count = 0;
for (var name in sources) {
if (!sources.hasOwnProperty(name)) continue;
if (!sources[name].version) {
count++;
updateVersion(name, function () {
if (!--count) callback(sources);
}, sources);
}
}
if (!count) callback(sources);
});
}
// get the sources-dist.json
if (process.argv.indexOf('--prepublish') !== -1) {
httpsGet(stableURL, function (err, body) {
if (err || !body) {
console.error('Cannot read sources file "' + stableURL + '": ' + err);
process.exit(2);
} else {
fs.writeFileSync(__dirname + '/../../conf/sources-dist.json', body);
process.exit();
}
});
}
// update versions for all adapter, which do not have the version
if (process.argv.indexOf('--init') !== -1) {
updateVersions(function (sources) {
var file = process.argv.indexOf('--file');
if (file !== -1 && process.argv[file + 1]) {
fs.writeFileSync(file, JSON.stringify(sources, null, 2));
} else {
console.log(JSON.stringify(sources, null, 2));
}
});
}
// update version for one adapter
if (process.argv.indexOf('--update') !== -1) {
var pos = process.argv.indexOf('--update');
if (process.argv[pos + 1]) {
updateVersion(process.argv[pos + 1], function (sources) {
var file = process.argv.indexOf('--file');
if (file !== -1 && process.argv[file + 1]) {
fs.writeFileSync(file, JSON.stringify(sources, null, 2));
} else {
console.log(JSON.stringify(sources, null, 2));
}
});
} else {
console.warn('Pleas specify name of adapter to update: script.js --update admin');
process.exit(1);
}
}

73
lib/session.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
module.exports = function (session, defaultTtl) {
var Store = session.Store;
defaultTtl = defaultTtl || 3600;
function AdapterStore(options) {
var that = this;
this.adapter = options.adapter;
options = options || {};
Store.call(this, options);
}
// Object.getPrototypeOf(AdapterStore.prototype) ?
// AdapterStore.prototype.__proto__ = Store.prototype;
AdapterStore.prototype = Object.create(Store.prototype);
/**
* Attempt to fetch session by the given `sid`.
*
* @param {String} sid
* @param {Function} fn
* @api public
*/
AdapterStore.prototype.get = function (sid, fn) {
this.adapter.getSession(sid, function (obj) {
if (obj) {
if (fn) return fn(null, obj);
} else {
if (fn) return fn();
}
});
};
/**
* Commit the given `sess` object associated with the given `sid`.
*
* @param {String} sid
* @param {Session} sess
* @param {Function} fn
* @api public
*/
AdapterStore.prototype.set = function (sid, ttl, sess, fn) {
if (typeof ttl === 'object') {
fn = sess;
sess = ttl;
ttl = defaultTtl;
}
ttl = ttl || defaultTtl;
this.adapter.setSession(sid, ttl, sess, function () {
if (fn) fn.apply(this, arguments);
});
};
/**
* Destroy the session associated with the given `sid`.
*
* @param {String} sid
* @api public
*/
AdapterStore.prototype.destroy = function (sid, fn) {
this.adapter.destroySession(sid, fn);
};
return AdapterStore;
};

3167
lib/setup.js Normal file

File diff suppressed because it is too large Load Diff

602
lib/setup/setupBackup.js Normal file
View File

@@ -0,0 +1,602 @@
'use strict';
function BackupRestore(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var pathLib = require('path');
var tmpDir = pathLib.normalize(__dirname + '/../../tmp');
var bkpDir = pathLib.normalize(__dirname + '/../../backups');
var hostname = tools.getHostName();
// allow use without new operator
if (!(this instanceof BackupRestore)) return new BackupRestore(options);
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
if (!options.cleanDatabase) throw 'Invalid arguments: cleanDatabase is missing';
if (!options.restartController) throw 'Invalid arguments: restartController is missing';
var objects = options.objects;
var states = options.states;
var processExit = options.processExit;
var cleanDatabase = options.cleanDatabase;
var restartController = options.restartController;
var mime;
var Upload = require(__dirname + '/setupUpload.js');
var upload = new Upload(options);
// --------------------------------------- BACKUP ---------------------------------------------------
function _copyFile(id, srcPath, destPath, callback) {
objects.readFile(id, srcPath, "", function (err, data) {
if (data) fs.writeFileSync(destPath, data);
callback();
});
}
function copyDir(id, srcPath, destPath, callback) {
var count = 0;
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath);
objects.readDir(id, srcPath, function (err, res) {
if (res) {
for (var t = 0; t < res.length; t++) {
if (res[t].isDir) {
count++;
copyDir(id, srcPath + '/' + res[t].file, destPath + '/' + res[t].file, function () {
count--;
if (!count) callback();
});
} else {
if (!fs.existsSync(destPath)) fs.mkdirSync(destPath);
count++;
_copyFile(id, srcPath + '/' + res[t].file, destPath + '/' + res[t].file, function () {
count--;
if (!count) callback();
});
}
}
}
if (!count) callback();
});
}
function getBackupDir() {
var dataDir = tools.getDefaultDataDir();
// All pathes are returned always relative to /node_modules/appName.js-controller
if (dataDir) {
if (dataDir[0] === '.' && dataDir[1] === '.') {
dataDir = __dirname + '/../../' + dataDir;
} else if (dataDir[0] === '.' && dataDir[1] === '/') {
dataDir = __dirname + '/../../' + dataDir.substring(2);
}
}
dataDir = dataDir.replace(/\\/g, '/');
if (dataDir[dataDir.length - 1] !== '/') dataDir += '/';
var parts = dataDir.split('/');
parts.pop();// remove data or appName-data
parts.pop();
return parts.join('/') + '/backups/';
}
function copyFileSync(source, target) {
var targetFile = target;
// if target is a directory a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = pathLib.join(target, pathLib.basename(source));
}
}
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
function copyFolderRecursiveSync(source, target) {
var files = [];
if (!fs.existsSync(target)) fs.mkdirSync(target);
// check if folder needs to be created or integrated
var targetFolder = pathLib.join(target, pathLib.basename(source));
if (!fs.existsSync(targetFolder)) fs.mkdirSync(targetFolder);
// copy
if (fs.lstatSync(source).isDirectory() ) {
files = fs.readdirSync(source);
files.forEach(function (file) {
var curSource = pathLib.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
this.createBackup = function (name, callback) {
var count = 0;
if (!name) {
var d = new Date();
name = d.getFullYear() + '_' +
('0' + (d.getMonth() + 1)).slice(-2) + '_' +
('0' + d.getDate()).slice(-2) + '-' +
('0' + d.getHours()).slice(-2) + '_' +
('0' + d.getMinutes()).slice(-2) + '_' +
('0' + d.getSeconds()).slice(-2) + '_backup' + tools.appName;
}
name = name.replace(/\\/g, '/');
if (name.indexOf('/') === -1) {
var path = getBackupDir();
// create directory if not exists
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
if (name.indexOf('.tar.gz') === -1) {
name = path + name + '.tar.gz';
} else {
name = path + name;
}
}
objects.getObjectList({include_docs: true}, function (err, res) {
var result = {objects: null, states: {}, config: null};
if (err) {
console.error('host.' + hostname + ' Cannot get objects: ' + err);
} else {
result.objects = res.rows;
}
if (fs.existsSync(tools.getConfigFileName())) result.config = JSON.parse(fs.readFileSync(tools.getConfigFileName(), 'utf8'));
states.getKeys('io.*', function (err, keys) {
/*for (var i = keys.length - 1; i >= 0; i--) {
if (keys[i].match(/^messagebox\./) || keys[i].match(/^log\./)) {
keys.splice(i, 1);
}
}*/
states.getStates(keys, function (err, obj) {
var hostname = tools.getHostName();
var r = new RegExp('^system\\.host\\.' + hostname + '\\.(\\w+)$');
for (var i = 0; i < keys.length; i++) {
if (obj[i].from === 'system.host.' + hostname) {
obj[i].from = 'system.host.$$__hostname__$$';
}
if (r.test(keys[i])) {
keys[i] = keys[i].replace(hostname, '$$__hostname__$$');
}
result.states[keys[i]] = obj[i];
}
if (!fs.existsSync(bkpDir)) fs.mkdirSync(bkpDir);
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
if (!fs.existsSync(tmpDir + '/backup')) fs.mkdirSync(tmpDir + '/backup');
if (!fs.existsSync(tmpDir + '/backup/files')) fs.mkdirSync(tmpDir + '/backup/files');
// try to find user files
for (var j = 0; j < result.objects.length; j++) {
if (!result.objects[j].value || !result.objects[j].value._id) continue;
//if (result.objects[j].doc) delete result.objects[j].doc;
if (result.objects[j].value._id.match(/^system\.adapter\.([\w\d_-]+).(\d+)$/) &&
result.objects[j].value.common.host === hostname) {
result.objects[j].value.common.host = '$$__hostname__$$';
if (result.objects[j].doc) {
result.objects[j].doc.common.host = '$$__hostname__$$';
}
} else
if (r.test(result.objects[j].value._id)) {
result.objects[j].value._id = result.objects[j].value._id.replace(hostname, '$$__hostname__$$');
result.objects[j].id = result.objects[j].value._id;
if (result.objects[j].doc) {
result.objects[j].doc._id = result.objects[j].value._id;
}
} else if (result.objects[j].value._id === 'system.host.' + hostname) {
result.objects[j].value._id = 'system.host.$$__hostname__$$';
result.objects[j].value.common.name = result.objects[j].value._id;
result.objects[j].value.common.hostname = '$$__hostname__$$';
if (result.objects[j].value.native && result.objects[j].value.native.os) {
result.objects[j].value.native.os.hostname = '$$__hostname__$$';
}
result.objects[j].id = result.objects[j].value._id;
if (result.objects[j].doc) {
result.objects[j].doc._id = result.objects[j].value._id;
result.objects[j].doc.common.name = result.objects[j].value._id;
result.objects[j].doc.common.hostname = '$$__hostname__$$';
if (result.objects[j].doc.native && result.objects[j].value.native.os) {
result.objects[j].doc.native.os.hostname = '$$__hostname__$$';
}
}
}
// Read all files
if (result.objects[j].value.type === 'meta' &&
result.objects[j].value.common &&
result.objects[j].value.common.type === 'meta.user') {
count++;
copyDir(result.objects[j].id, '', tmpDir + '/backup/files/' + result.objects[j].id, function () {
count--;
if (!count) {
// todo: store letsencrypt files too => change it as letitbit will be better integrated
var configDir = tools.getConfigFileName().split('/');
configDir.pop();
configDir.push('letsencrypt');
var letsEncrypt = configDir.join('/');
if (fs.existsSync(letsEncrypt)) {
copyFolderRecursiveSync(letsEncrypt, tmpDir + '/backup');
}
var tar = require('tar');
var f = fs.createWriteStream(name);
f.on('finish', function () {
tools.rmdirRecursiveSync(tmpDir + '/backup');
if (callback) callback(pathLib.normalize(name));
});
f.on('error', function (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
});
try {
tar.create({gzip: true, cwd: tmpDir + '/'}, ['backup']).pipe(f);
} catch (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
}
}
});
}
}
fs.writeFileSync(tmpDir + '/backup/backup.json', JSON.stringify(result, null, 2));
if (!count) {
var tar = require('tar');
var f = fs.createWriteStream(name);
f.on('finish', function () {
tools.rmdirRecursiveSync(tmpDir + '/backup');
if (callback) callback(pathLib.normalize(name));
});
f.on('error', function (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
});
try {
tar.create({gzip: true, cwd: tmpDir + '/'}, ['backup']).pipe(f);
} catch (err) {
console.error('host.' + hostname + ' Cannot pack directory ' + pathLib.normalize(tmpDir + '/backup') + ': ' + err);
processExit(9);
}
}
});
});
});
};
//--------------------------------------- RESTORE ---------------------------------------------------
function _setStateHelper(_index, statesList, stateObjects, callback) {
states.setRawState(statesList[_index], stateObjects[statesList[_index]], function () {
if ((_index % 200) === 0) console.log('host.' + hostname + ' Processed ' + _index + '/' + statesList.length + ' states');
_index++;
if (_index < statesList.length) {
setImmediate(_setStateHelper, _index, statesList, stateObjects, callback);
} else {
if (callback) callback();
}
});
}
function _setObjHelper(_index, _objects, callback) {
// Disable all adapters.
if (_objects[_index].id.match(/^system\.adapter\./) && !_objects[_index].id.match(/^system\.adapter\.admin/)) {
if (_objects[_index].doc.common && _objects[_index].doc.common.enabled) {
_objects[_index].doc.common.enabled = false;
}
}
if (_objects[_index].doc && _objects[_index].doc._rev) delete _objects[_index].doc._rev;
objects.setObject(_objects[_index].id, _objects[_index].doc, function (err /* , obj */) {
if (err) {
console.warn('host.' + hostname + ' Cannot restore ' + _objects[_index].id + ': ' + err);
}
if ((_index % 200) === 0) console.log('host.' + hostname + ' Processed ' + _index + '/' + _objects.length + ' objects');
_index++;
if (_index < _objects.length) {
setImmediate(_setObjHelper, _index, _objects, callback);
} else {
if (callback) callback();
}
});
}
function reloadAdapterObject(index, objectList, callback) {
if (objectList && index < objectList.length) {
objects.getObject(objectList[index]._id, function (err, obj) {
if (err || !obj) {
objects.setObject(objectList[index]._id, objectList[index], function () {
console.log('host.' + hostname + ' object ' + objectList[index]._id + ' created');
index++;
setImmediate(reloadAdapterObject, index, objectList, callback);
});
} else {
index++;
setImmediate(reloadAdapterObject, index, objectList, callback);
}
});
} else {
if (callback) callback();
}
}
function reloadAdaptersObjects(callback, dirs, index) {
if (!dirs) {
dirs = [];
var _modules;
if (fs.existsSync(__dirname + '/../../node_modules')) {
_modules = fs.readdirSync(__dirname + '/../../node_modules');
if (_modules) {
var regEx = new RegExp('^' + tools.appName + '\\.', 'i');
for (var i = 0; i < _modules.length; i++) {
if (regEx.test(_modules[i]) &&
dirs.indexOf(_modules[i].substring(tools.appName.length + 1)) === -1) {
dirs.push(_modules[i]);
}
}
}
}
// if installed as npm
if (fs.existsSync(__dirname + '/../../../../node_modules/' + tools.appName + '.js-controller')) {
_modules = fs.readdirSync(__dirname + '/../../..');
var regEx_ = new RegExp('^' + tools.appName + '\\.', 'i');
for (var j = 0; j < _modules.length; j++) {
// if starting from application name + '.'
if (regEx_.test(_modules[j]) &&
// If not js-controller
(_modules[j].substring(tools.appName.length + 1) !== 'js-controller') &&
dirs.indexOf(_modules[j].substring(tools.appName.length + 1)) === -1) dirs.push(_modules[j]);
}
}
if (dirs.length) {
reloadAdaptersObjects(callback, dirs, 0);
} else {
if (callback) callback();
}
} else {
if (index < dirs.length) {
upload.uploadAdapter(dirs[index], false, true, function () {
upload.uploadAdapter(dirs[index], true, true, function () {
var pkg = null;
if (!dirs[index]) {
console.error('Wrong');
}
var adapterDir = tools.getAdapterDir(dirs[index]);
if (fs.existsSync(adapterDir + '/io-package.json')) {
pkg = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
}
if (pkg && pkg.objects && pkg.objects.length) {
console.log('host.' + hostname + ' Setup "' + dirs[index] + '" adapter');
reloadAdapterObject(0, pkg.objects, function () {
index++;
setImmediate(reloadAdaptersObjects, callback, dirs, index);
});
} else {
index++;
reloadAdaptersObjects(callback, dirs, index);
}
});
});
} else {
if (callback) callback();
}
}
}
function uploadUserFiles(root, path, callback) {
if (typeof path === 'function') {
callback = path;
path = '';
}
var called = false;
if (!fs.existsSync(root)) {
callback();
return;
}
var files = fs.readdirSync(root + path);
var count = files.length;
for (var i = 0; i < files.length; i++) {
var stat = fs.statSync(root + path + '/' + files[i]);
if (stat.isDirectory()) {
called = true;
uploadUserFiles(root, path + '/' + files[i], function (err) {
if (err) console.error('Error: ' + err);
if (!--count) setImmediate(callback);
});
} else {
var parts = path.split('/');
var adapter = parts.splice(0, 2);
adapter = adapter[1];
var _path = parts.join('/') + '/' + files[i];
console.log('host.' + hostname + ' Upload user file "' + adapter + "/" + _path);
called = true;
objects.writeFile(adapter, _path, fs.readFileSync(root + path + '/' + files[i]), null, function (err) {
if (err) console.error('Error: ' + err);
if (!--count) setImmediate(callback);
});
}
}
if (!called) callback();
}
function restoreAfterStop(restartOnFinish, callback) {
// Open file
var data = fs.readFileSync(tmpDir + '/backup/backup.json').toString();
var hostname = tools.getHostName();
data = data.replace(/\$\$__hostname__\$\$/g, hostname);
fs.writeFileSync(tmpDir + '/backup/backup_.json', data);
var restore;
try {
restore = JSON.parse(data);
} catch (e) {
console.error('Cannot parse "' + tmpDir + '/backup/backup_.json": ' + e);
if (callback) callback(31);
}
// stop all adapters
console.log('host.' + hostname + ' Clear all objects and states...');
cleanDatabase(false, function () {
console.log('host.' + hostname + ' done.');
// upload all data into DB
// restore ioBorker.json
if (restore.config) fs.writeFileSync(tools.getConfigFileName(), JSON.stringify(restore.config, null, 2));
var sList = [];
for (var state in restore.states) {
if (restore.states.hasOwnProperty(state)) {
sList.push(state);
}
}
_setStateHelper(0, sList, restore.states, function () {
console.log(sList.length + ' states restored.');
_setObjHelper(0, restore.objects, function () {
console.log(restore.objects.length + ' objects restored.');
// Required for upload adapter
mime = require('mime');
// Load user files into DB
uploadUserFiles(tmpDir + '/backup/files', function () {
// reload objects of adapters
reloadAdaptersObjects(function () {
// Reload host objects
var pckgio = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
reloadAdapterObject(0, pckgio ? pckgio.objects : null, function () {
if (restartOnFinish) {
restartController(callback);
} else {
if (callback) callback();
}
});
});
});
});
});
});
}
this.listBackups = function () {
var dir = getBackupDir();
var result = [];
if (fs.existsSync(dir)) {
var files = fs.readdirSync(dir);
for (var i = 0; i < files.length; i++) {
if (files[i].match(/\.tar\.gz$/i)) {
result.push(files[i]);
}
}
return result;
} else {
return result;
}
};
this.restoreBackup = function (name, callback) {
var backups;
if (!name && name !== 0) {
// List all available backups
console.log('Please specify one of the backup names:');
backups = this.listBackups();
backups.sort(function (a, b) {
return b > a;
});
if (backups.length) {
for (var t = 0; t < backups.length; t++){
console.log(backups[t] + ' or ' + backups[t].replace('_backup' + tools.appName + '.tar.gz', '') + ' or ' + t);
}
} else {
console.warn('No backups found');
}
processExit(10);
}
if (!options.cleanDatabase) throw "Invalid arguments: cleanDatabase is missing";
if (!options.restartController) throw "Invalid arguments: restartController is missing";
if (parseInt(name, 10).toString() === name.toString()) {
backups = this.listBackups();
backups.sort(function (a, b) {
return b > a;
});
name = backups[parseInt(name, 10)];
console.log('host.' + hostname + ' Using backup file ' + name);
}
name = (name || '').toString().replace(/\\/g, '/');
if (name.indexOf('/') === -1) {
name = getBackupDir() + name;
var regEx = new RegExp('_backup' + tools.appName, 'i');
if (!regEx.test(name)) name += '_backup' + tools.appName;
if (!name.match(/\.tar\.gz$/i)) name += '.tar.gz';
}
if (!fs.existsSync(name)) {
console.error('host.' + hostname + ' Cannot find ' + name);
processExit(11);
}
var tar = require('tar');
if (fs.existsSync(tmpDir + '/backup/backup.json')) {
fs.unlinkSync(tmpDir + '/backup/backup.json');
}
tar.extract({
file: name,
cwd: tmpDir
}, function (err) {
if (err) {
console.error('host.' + hostname + ' Cannot extract from file "' + name + '"');
processExit(9);
}
if (!fs.existsSync(tmpDir + '/backup/backup.json')) {
console.error('host.' + hostname + ' Cannot find extracted file from file "' + tmpDir + '/backup/backup.json"');
processExit(9);
}
// Stop controller
var daemon = require('daemonize2').setup({
main: '../../controller.js',
name: tools.appName + ' controller',
pidfile: __dirname + '/../' + tools.appName + '.pid',
cwd: '../../',
stopTimeout: 1000
});
daemon.on('error', function (/* error */) {
restoreAfterStop(false, callback);
});
daemon.on('stopped', function () {
restoreAfterStop(true, callback);
});
daemon.on('notrunning', function () {
console.log('host.' + hostname + ' OK.');
restoreAfterStop(false, callback);
});
daemon.stop();
});
}
}
module.exports = BackupRestore;

1397
lib/setup/setupInstall.js Normal file

File diff suppressed because it is too large Load Diff

76
lib/setup/setupLicense.js Normal file
View File

@@ -0,0 +1,76 @@
'use strict';
function License(options) {
const fs = require('fs');
const jwt = require('jsonwebtoken');
options = options || {};
let objects = options.objects;
// read info from '/etc/iob_vendor.json' and executes instructions stored there
this.setLicense = file => {
if (fs.existsSync(file)) {
try {
file = fs.readFileSync(file).toString('utf8');
} catch (e) {
return Promise.reject(e);
}
}
// try to encode license
let license = jwt.decode(file);
if (!license) {
return Promise.reject('License cannot be decoded');
}
if (!license.name) {
return Promise.reject('Name not found in the license');
}
let adapter = license.name.split('.')[1];
if (!adapter) {
return Promise.reject(`Invalid license name ${license.name}`);
}
// read all instances of this adapter
return objects.getObjectListAsync({
startkey: 'system.adapter.' + adapter + '.',
endkey: 'system.adapter.' + adapter + '.\u9999'
}, {checked: true}).then(arr => {
let promises = [];
if (arr && arr.rows && arr.rows.length) {
for (let g = 0; g < arr.rows.length; g++) {
let obj = arr.rows[g].value;
if (obj && obj.type === 'instance') {
obj.native = obj.native || {};
obj.native.license = file;
promises.push(objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`Instance "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
}));
}
}
}
if (!promises.length) {
console.warn(`no instances of ${adapter} found`);
if (arr && arr.rows && arr.rows.length) {
for (let g = 0; g < arr.rows.length; g++) {
let obj = arr.rows[g].value;
if (obj && obj.type === 'adapter') {
obj.native = obj.native || {};
obj.native.license = file;
promises.push(objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`Adapter "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
}));
}
}
}
}
if (!promises.length) {
console.error(`no installations of ${adapter} found`);
}
return Promise.all(promises);
});
};
return this;
}
module.exports = License;

627
lib/setup/setupList.js Normal file
View File

@@ -0,0 +1,627 @@
'use strict';
function List(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var that = this;
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
var objects = options.objects;
var states = options.states;
var processExit = options.processExit;
function perm2str(perm) {
var result = '';
// user
result += (perm & 0x400) ? 'r' : '-';
result += (perm & 0x200) ? 'w' : '-';
result += (perm & 0x100) ? 'x' : '-';
// group
result += (perm & 0x040) ? 'r' : '-';
result += (perm & 0x020) ? 'w' : '-';
result += (perm & 0x010) ? 'x' : '-';
// any
result += (perm & 0x004) ? 'r' : '-';
result += (perm & 0x002) ? 'w' : '-';
result += (perm & 0x001) ? 'x' : '-';
return result;
}
this.showFileHeader = function () {
console.log(' Modified at |Permission| User | Group | Size | Name');
console.log('----------------+----------+--------------+--------------+------+---------');
};
this.showFile = function (adapter, path, file) {
//drwxr-xr-x 1 odroid odroid 43 Oct 3 2013 .xsessionrc
var text = '';
var time;
if (file.modifiedAt) {
var ts = new Date(file.modifiedAt);
time = ts.toISOString();
time = time.replace('T', ' ');
time = time.substring(0, 16) + ' ';
} else {
time = Array(18).join(' ');
}
text += time;
if (file.acl){
text += (file.isDir ? 'd' : '-') + perm2str(file.acl.permissions || 0);
var owner = file.acl.owner;
if (owner) {
owner = owner.substring(12);
if (owner.length < 15) owner = Array(15 - owner.length).join(' ') + owner;
} else {
owner = Array(15).join(' ');
}
text += ' ' + owner;
var group = file.acl.ownerGroup;
if (group) {
group = group.substring(13);
if (group.length < 15) group = Array(15 - group.length).join(' ') + group;
} else {
group = Array(15).join(' ');
}
text += ' ' + group;
} else {
text += (file.isDir ? 'd' : '-') + '?????????' + Array(31).join(' ');
}
var size = (file.stats) ? file.stats.size.toString() : '';
if (size.length < 7) size = Array(7 - size.length).join(' ') + size;
text += ' ' + size + ' ' + adapter + ((!path || path[0] === '/') ? '' : '/') + path + '/' + file.file;
if (file.isDir) {
text += '/';
console.log(text);
} else {
console.log(text);
}
};
this.showObjectHeader = function () {
console.log('ObjectAC | StateAC | User | Group | ID');
console.log('---------+---------+--------------+--------------+--------------');
};
this.showObject = function (obj) {
//drwxr-xr-x 1 odroid odroid 43 Oct 3 2013 .xsessionrc
var text = '';
if (obj.acl){
text += perm2str(obj.acl.object || 0) + ' ' + ((obj.type === 'state') ? perm2str(obj.acl.state || 0) : ' ');
var owner = obj.acl.owner;
if (owner) {
owner = owner.substring(12);
if (owner.length < 15) owner = Array(15 - owner.length).join(' ') + owner;
} else {
owner = Array(15).join(' ');
}
text += ' ' + owner;
var group = obj.acl.ownerGroup;
if (group) {
group = group.substring(13);
if (group.length < 15) group = Array(15 - group.length).join(' ') + group;
} else {
group = Array(15).join(' ');
}
text += ' ' + group;
} else {
text += '?????????' + ((obj.type === 'state') ? ' ?????????' : ' ') + Array(31).join(' ');
}
text += ' ' + obj._id;
console.log(text);
};
this.listDirectory = function (adapter, path, allFiles, callback) {
if (typeof path === 'function') {
callback = path;
path = '';
allFiles = [];
}
if (typeof allFiles === 'function') {
callback = allFiles;
allFiles = [];
}
allFiles = allFiles || [];
path = path || '';
objects.readDir(adapter, path, null, function (err, files) {
if (err && err.code === 'ENOTDIR') {
var pos = path.lastIndexOf('/');
if (pos !== -1) {
var dir = path.substring(0, pos);
var fname = path.substring(pos + 1);
objects.readDir(adapter, dir, null, function (err, files) {
if (err) {
console.log('Cannot read "' + path + '": ' + err);
callback(allFiles);
} else {
for (var f = 0; f < files.length; f++) {
if (files[f].file === '.' || files[f].file === '..') continue;
if (files[f].file === fname) {
allFiles.push({adapter: adapter, path: path, file: files[f]});
break;
}
}
callback(allFiles);
}
});
} else {
callback(allFiles);
}
} else {
var count = 0;
for (var f = 0; f < files.length; f++) {
if (files[f].file === '.' || files[f].file === '..') continue;
allFiles.push({adapter: adapter, path: path, file: files[f]});
if (files[f].isDir) {
count++;
that.listDirectory(adapter, path + '/' + files[f].file, allFiles, function () {
if (!--count && callback) {
callback(allFiles);
}
});
}
}
if (!count && callback) callback(allFiles);
}
});
};
function pattern2RegEx(pattern) {
if (pattern !== '*') {
if (pattern[0] === '*' && pattern[pattern.length - 1] !== '*') pattern += '$';
if (pattern[0] !== '*' && pattern[pattern.length - 1] === '*') pattern = '^' + pattern;
}
pattern = pattern.replace(/\./g, '\\.');
pattern = pattern.replace(/\*/g, '.*');
return pattern;
}
function sortFiles (a, b) {
var a1 = a.path + a.file.file;
if (a1[0] !== '/') a1 = '/' + a1;
var b1 = b.path + b.file.file;
if (b1[0] !== '/') b1 = '/' + b1;
return a1.localeCompare(b1);
}
this.listAdaptersFiles = function (adapters, filter, callback) {
if (typeof filter === 'function') {
callback = filter;
filter = null;
}
if (filter) {
filter = filter.replace('*', '');
if (filter[filter.length - 1] === '/') filter = filter.substring(0, filter.length - 1);
}
if (adapters && adapters.length) {
var adapter = adapters.pop();
this.listDirectory(adapter, filter, function (files) {
files.sort(sortFiles);
that.showFileHeader();
for (var k = 0; k < files.length; k++) {
if (filter && filter !== (files[k].path + '/'+ files[k].file.file).substring(0, filter.length)) continue;
that.showFile(files[k].adapter, files[k].path, files[k].file);
}
that.listDirectory(adapter + '.admin', filter, function (files) {
files.sort(sortFiles);
for (var k = 0; k < files.length; k++) {
if (filter && filter !== (files[k].path + '/'+ files[k].file.file).substring(0, filter.length)) continue;
that.showFile(files[k].adapter, files[k].path, files[k].file);
}
that.listAdaptersFiles(adapters, callback);
});
});
} else {
if (callback) callback ();
}
};
this.list = function (type, filter, flags) {
switch (type) {
case 'objects':
case 'o':
objects.getObjectList(null, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx(filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
if (objs.rows[i].value.type) {
var id = objs.rows[i].value._id;
var type = objs.rows[i].value.type;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (type.length < 10) type += Array(10 - type.length).join(' ');
console.log(id + ': ' + type + ' - ' + (objs.rows[i].value.common.name || ''));
} else {
console.log(objs.rows[i].value._id);
}
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'states':
case 's':
states.getKeys(filter || '*', function (err, keys) {
if (err) {
console.error(err);
processExit(23);
}
states.getStates(keys, function (err, states) {
if (err) {
console.error(err);
processExit(23);
}
for (var i = 0; i < states.length; i++) {
var id = keys[i];
var from = states[i].from || '';
var type = typeof states[i].val;
if (type.length < 10) type += Array(10 - type.length).join(' ');
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (from.length < 30) from += Array(30 - from.length).join(' ');
console.log(id + ': from [' + from + '] (' + type + ') ' + (states[i].ack ? ' ack': 'not ack') + ' ' + JSON.stringify(states[i].val));
}
setTimeout(function () {
processExit();
}, 1000);
});
});
break;
case 'adapters':
case 'a':
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('system.adapter.' + filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'adapter') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id;
var name = objs.rows[i].value.common.name;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (name.length < 12) name += Array(12 - name.length).join(' ');
var text = id + ': ' + name + ' - ' +
objs.rows[i].value.common.version;
console.log(text);
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'instances':
case 'i':
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('system.adapter.' + filter)) : null;
objs.rows.sort(function (a, b) {
if (a.id > b.id) return 1;
if (a.id < b.id) return -1;
return 0;
});
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'instance') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
if (flags.enabled && !objs.rows[i].value.common.enabled) continue;
if (flags.disabled && objs.rows[i].value.common.enabled) continue;
if (flags.port && objs.rows[i].value.native.port === undefined) continue;
if (flags.ssl && objs.rows[i].value.native.secure === undefined) continue;
if (flags.ip && objs.rows[i].value.native.bind === undefined) continue;
var id = objs.rows[i].value._id;
var name = objs.rows[i].value.common.name;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (name && name.length < 12) name += Array(12 - name.length).join(' ');
var text = id + ': ' + (name || '') + ' - ' +
(objs.rows[i].value.common.enabled ? ' enabled' : 'disabled');
if (objs.rows[i].value.native && objs.rows[i].value.native.port) {
text += ', port: ' + objs.rows[i].value.native.port;
}
if (objs.rows[i].value.native && objs.rows[i].value.native.bind) {
text += ', bind: ' + objs.rows[i].value.native.bind;
}
if (objs.rows[i].value.native && objs.rows[i].value.native.secure) {
text += ' (SSL)';
}
if (objs.rows[i].value.native && objs.rows[i].value.native.defaultUser) {
text += ', run as: ' + objs.rows[i].value.native.defaultUser;
}
console.log(text);
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'users':
case 'u':
objects.getObjectList({startkey: 'system.user.', endkey: 'system.user.\u9999'}, function (err, objs) {
objects.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, groups) {
var reg = filter ? new RegExp(pattern2RegEx('system.user.' + filter)) : null;
console.log(' ID | Name | Active | Groups');
console.log('---------------------------------------+-------------+----------+--------------');
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'user') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id;
var name = objs.rows[i].value.common.name;
if (id.length < 40) id += Array(40 - id.length).join(' ');
if (name.length < 12) name += Array(12 - name.length).join(' ');
var text = id + '| ' + name + ' | ' +
(objs.rows[i].value.common.enabled ? ' enabled' : 'disabled') + ' |';
var gs = [];
// find all groups
for (var g = 0; g < groups.rows.length; g++) {
if (groups.rows[g].value.common.members && groups.rows[g].value.common.members.indexOf(objs.rows[i].value._id) !== -1) {
gs.push(groups.rows[g].value._id.substring(13));
}
}
console.log(text + ' ' + gs.join(', '));
}
}
setTimeout(function () {
processExit();
}, 1000);
});
});
break;
case 'groups':
case 'g':
objects.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('system.group.' + filter)) : null;
console.log('');
console.log(' system.group | object | state | file | user | others | users');
console.log(' | l r w d | l r w d | l r w c d | w c d | |');
console.log('--------------------+---------+---------+-----------+-------+------------------------+---------');
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'group') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id.substring(13);
//var name = objs.rows[i].value.common.name;
if (id === 'administrator') {
objs.rows[i].value.common.acl = {
file: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},
object: {
read: true,
write: true,
'delete': true,
list: true
},
state: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},
user: {
write: true,
create: true,
'delete': true
},
other: {
execute: true,
http: true,
sendto: true
}
};
}
if (id.length < 20) id += Array(20 - id.length).join(' ');
var text = id;
text += ' | ';
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.object) {
text += (objs.rows[i].value.common.acl.object.list ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.object.read ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.object.write ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.object.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.state) {
text += ' ';
text +=(objs.rows[i].value.common.acl.state.list ? '+': '-') + ' ';
text +=(objs.rows[i].value.common.acl.state.read ? '+': '-') + ' ';
text +=(objs.rows[i].value.common.acl.state.write ? '+': '-') + ' ';
text +=(objs.rows[i].value.common.acl.state.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.file) {
text += ' ';
text += (objs.rows[i].value.common.acl.file.list ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.read ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.write ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.create ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.file.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.users) {
text += ' ';
text += (objs.rows[i].value.common.acl.users.write ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.users.create ? '+': '-') + ' ';
text += (objs.rows[i].value.common.acl.users.delete ? '+': '-') + ' ';
text += '|';
} else {
text += ' |';
}
if (objs.rows[i].value.common.acl && objs.rows[i].value.common.acl.other) {
text += ' ';
var others = '';
for (var r in objs.rows[i].value.common.acl.other) {
others += r + (objs.rows[i].value.common.acl.other[r] ? '+' : '-') + ' ';
}
if (others.length < 23) others += Array(23 - others.length).join(' ');
text += others + '|';
} else {
text += Array(25).join(' ') + '|';
}
//if (name.length < 30) name += Array(30 - name.length).join(' ');
if ( objs.rows[i].value.common.members) {
for (var m = 0; m < objs.rows[i].value.common.members.length; m++) {
objs.rows[i].value.common.members[m] = objs.rows[i].value.common.members[m].substring(12);
}
text += ' ' + objs.rows[i].value.common.members.join(', ');
}
//text += '| (' + name + ')';
console.log(text);
}
}
console.log('--------------------+---------+---------+-----------+-------+------------------------+---------');
console.log('Legend: (l)ist, (r)ead, (w)rite, (c)reate, (d)elete');
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'h':
case 'hosts':
objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.\u9999'}, function (err, objs) {
states.getKeys('system.host.*', function (err, keys) {
states.getStates(keys, function (err, states) {
var reg = filter ? new RegExp(pattern2RegEx('system.host.' + filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'host') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
var id = objs.rows[i].value._id.substring(12);
var name = objs.rows[i].value.common.name;
if (id.length < 20) id += Array(20 - id.length).join(' ');
var hostname = objs.rows[i].value.common.hostname;
if (hostname.length < 15) hostname += Array(15 - hostname.length).join(' ');
var version = objs.rows[i].value.common.installedVersion;
var alive = '';
var uptime = '';
for (var k = 0; k < keys.length; k++) {
if (keys[k] === objs.rows[i].value._id + '.alive') {
alive = states[k].val;
}
if (keys[k] === objs.rows[i].value._id + '.uptime') {
uptime = states[k].val;
}
}
alive = alive ? 'alive' : ' dead';
//if (uptime.toString().length < 10) uptime = Array(10 - uptime.toString().length).join(' ') + uptime.toString();
if (!uptime) uptime = '-';
var text = id + ' ' + name + ' (version: ' + version + ', hostname: ' + hostname + ', ' + alive + ', uptime: ' + uptime + ')';
// todo
console.log(text);
}
}
setTimeout(function () {
processExit();
}, 1000);
});
});
});
break;
case 'enums':
case 'e':
objects.getObjectList({startkey: 'enum.', endkey: 'enum.\u9999'}, function (err, objs) {
var reg = filter ? new RegExp(pattern2RegEx('enum.' + filter)) : null;
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type !== 'enum') continue;
if (!reg || reg.test(objs.rows[i].value._id) || (objs.rows[i].value.common && reg.test(objs.rows[i].value.common.name))) {
console.log('\n=====================================================================================');
var id = objs.rows[i].value._id.substring(5);
var name = objs.rows[i].value.common.name;
if (id.length < 20) id += Array(20 - id.length).join(' ');
console.log(id + '(' + name + ')');
console.log('-------------------------------------------------------------------------------------');
if (objs.rows[i].value.common.members) {
console.log(objs.rows[i].value.common.members.join(', '));
}
}
}
setTimeout(function () {
processExit();
}, 1000);
});
break;
case 'files':
case 'f':
objects.getObjectList({startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, function (err, objs) {
var adapter = filter || null;
var names = filter ? filter.split('/') : null;
if (names && !names[0]) names.splice(0, 1);
var adapters = [];
for (var i = 0; i < objs.rows.length; i++) {
if (objs.rows[i].value.type === 'adapter') {
if (adapter && objs.rows[i].value.common.name !== names[0]) continue;
adapters.push(objs.rows[i].value.common.name);
} else if (objs.rows[i].value.type === 'instance') {
if (adapter && objs.rows[i].value._id.substring(15) !== names[0]) continue;
adapters.push(objs.rows[i].value._id.substring(15));
}
}
if (names) names.shift();
that.listAdaptersFiles(adapters, names ? names.join('/') : null, function () {
setTimeout(function () {
processExit();
}, 1000);
});
});
break;
default:
if (type) {
console.error('Unknown type: ' + type);
processExit(23);
} else {
console.log('Please specify type: objects, states, instances, adapters, users, groups, enums, files');
processExit();
}
break;
}
};
}
module.exports = List;

294
lib/setup/setupMultihost.js Normal file
View File

@@ -0,0 +1,294 @@
'use strict';
function Multihost(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var configName = tools.getConfigFileName();
var that = this;
options = options || {};
var params = options.params || {};
var objects = options.objects;
function getConfig() {
var config;
// read actual configuration
try {
if (fs.existsSync(configName)) {
config = JSON.parse(fs.readFileSync(configName, 'utf8'));
} else {
config = require(__dirname + '/../../conf/' + tools.appName + '-dist.json');
}
} catch (e) {
config = require(__dirname + '/../../conf/' + tools.appName + '-dist.json');
}
return config;
}
function leftPad(text, len) {
text = text || '';
if (text.length >= len) return len;
return new Array(len - text.length).join(' ') + text;
}
this.showHosts = function (list) {
if (!list || !list.length) {
console.info('Nothing found');
} else {
for (var i = 0; i < list.length; i++) {
console.log((i + 1) + ' | ' + leftPad(list[i].hostname, 20) + ' | ' + (list[i].slave ? 'slave' : ' host') + ' | ' + leftPad(list[i].ip, 20) + ' | ' + JSON.stringify(list[i].info));
}
}
};
this.browse = function (callback) {
var MHClient = require(__dirname + '/../multihostClient');
var mhClient = new MHClient();
mhClient.browse(2000, params.debug, function (err, list) {
if (err) {
callback('Cannot browse: ' + err);
} else {
callback(null, list);
}
});
};
function showMHState(config, changed, callback) {
if (config.multihostService.enabled) {
if (config.objects.type === 'file' && (config.objects.host === '127.0.0.1' || config.objects.host === 'localhost')) {
console.log('Server accept connections for objects on all IP addresses.');
config.objects.host = '0.0.0.0';
changed = true;
} else if (config.objects.type === 'redis') {
console.log('Please check the binding of redis service. By default it is only local: http://download.redis.io/redis-stable/redis.conf\nChange "bind 127.0.0.1" to "bind 0.0.0.0" or to others.')
}
if (config.states.type === 'file' && (config.states.host === '127.0.0.1' || config.states.host === 'localhost')) {
console.log('Server accept connections for states on all IP addresses.');
config.states.host = '0.0.0.0';
changed = true;
} else if (config.states.type === 'redis') {
console.log('Please check the binding of redis service. By default it is only local: http://download.redis.io/redis-stable/redis.conf\nChange "bind 127.0.0.1" to "bind 0.0.0.0" or to others.')
}
}
if (!changed) {
console.log('Nothing changed.');
} else {
fs.writeFileSync(configName, JSON.stringify(config, null, 2));
console.log('Please restart yunkong2: "yunkong2 restart"');
}
console.log('\n');
console.log('Miltihost: ' + (config.multihostService.enabled ? 'enabled' : 'disabled'));
console.log('Authentication: ' + (config.multihostService.secure ? 'enabled' : 'disabled'));
console.log('Objects: ' + config.objects.type + ' on ' + config.objects.host);
console.log('States: ' + config.states.type + ' on ' + config.states.host);
callback();
}
this.enable = function (isEnable, callback) {
var changed = false;
var config = getConfig();
config.multihostService = config.multihostService || {enabled: false, secure: true};
if (isEnable && !config.multihostService.enabled) {
changed = true;
config.multihostService.enabled = true;
config.multihostService.password = '';
console.log('Miltihost activated.')
} else if (!isEnable && config.multihostService.enabled) {
changed = true;
config.multihostService.enabled = false;
config.multihostService.password = '';
console.log('Miltihost deactivated.')
}
if (params.secure === undefined) {
params.secure = true;
}
if (isEnable && (config.multihostService.secure !== params.secure || (config.multihostService.secure && !config.multihostService.password))) {
changed = true;
config.multihostService.secure = params.secure;
console.log('Authentication ' + (params.secure ? 'activated' : 'deactivated') + '.');
if (config.multihostService.secure) {
var prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
var schema = {
properties: {
password: {
description: 'Enter secret phrase for connection:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
},
passwordRepeat: {
description: 'Repeat secret phrase for connection:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, password) {
if (password && password.password) {
if (password.password !== password.passwordRepeat) {
callback('Secret phrases are not equal!');
} else {
objects.getObject('system.config', function (err, obj) {
tools.encryptPhrase(obj.native.secret, password.password, function (encoded) {
config.multihostService.password = encoded;
showMHState(config, changed, callback);
})
});
}
} else {
callback('No secret phrase entered!');
}
});
} else {
showMHState(config, changed, callback);
}
} else {
showMHState(config, changed, callback);
}
};
this.status = function (callback) {
var config = getConfig();
config.multihostService = config.multihostService || {enabled: false, secure: true};
showMHState(config, false, callback);
};
function readPassword(callback) {
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function hidden(query, callback) {
var stdin = process.openStdin();
process.stdin.on('data', function (char) {
char = char + '';
switch (char) {
case '\n':
case '\r':
case '\u0004':
stdin.pause();
break;
default:
process.stdout.write('\x1B[2K\x1B[200D' + query + new Array(rl.line.length + 1).join('*'));
break;
}
});
rl.question(query, function (value) {
rl.history = rl.history.slice(1);
callback(value);
});
}
hidden('Enter secret phrase for connection: ', function(password) {
callback(password);
});
}
function connect(mhClient, ip, pass, callback) {
mhClient.connect(ip, pass, function (err, oObjects, oStates, ipHost) {
if (err) {
callback('Cannot connect to "' + ip + '": ' + err);
} else if (oObjects && oStates) {
var config = getConfig();
config.objects = oObjects;
config.states = oStates;
if (config.objects.host === '127.0.0.1' || config.objects.host === 'localhost' || config.states.host === '127.0.0.1' || config.states.host === 'localhost') {
callback('IP Address of the host is 127.0.0.1. It accepts no connections. Please change.');
} else {
if (config.states.host === '0.0.0.0') {
config.states.host = ipHost;
}
if (config.objects.host === '0.0.0.0') {
config.objects.host = ipHost;
}
fs.writeFileSync(configName, JSON.stringify(config, null, 2));
console.log('Config ok. Please restart yunkong2: "yunkong2 restart"');
callback();
}
} else {
callback('No configuration received!');
}
});
}
this.connect = function (number, pass, callback) {
if (typeof pass === 'function') {
callback = pass;
pass = null;
}
if (typeof number === 'function') {
callback = number;
number = null;
}
var MHClient = require(__dirname + '/../multihostClient');
var mhClient = new MHClient();
mhClient.browse(2000, params.debug, function (err, list) {
if (err) {
callback('Cannot browse: ' + err);
} else {
that.showHosts(list);
if (number !== null && number !== undefined && parseInt(number, 10) > 0) {
number = parseInt(number, 10);
if (list && number < list.length + 1) {
if (!pass) {
callback('No password defined: please use "multihost connect <NUMBER> <PASSWORD>"');
} else {
connect(mhClient, list[number - 1].ip, pass, callback);
}
} else {
callback('Invalid index: ' + number);
}
} else
if (list && list.length) {
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Please select host [1]: ', function (answer) {
if (answer === '' || answer === null || answer === undefined) {
answer = 1;
}
answer = parseInt(answer, 10) - 1;
if (!list[answer]) {
rl.close();
callback('Invalid index: ' + answer);
} else {
if (list[answer].auth) {
readPassword(function (password) {
if (password) {
connect(mhClient, list[answer].ip, password, callback);
} else {
callback('No password entered!');
}
});
} else {
connect(mhClient, list[answer].ip, null, callback);
}
}
});
} else {
callback(null, list);
}
}
});
};
}
module.exports = Multihost;

279
lib/setup/setupRepo.js Normal file
View File

@@ -0,0 +1,279 @@
'use strict';
function Repo(options) {
const tools = require(__dirname + '/../tools.js');
const extend = require('node.extend');
let defaultSystemRepo = {
"common": {
"name": "System repositories",
"dontDelete": true
},
"native": {
"repositories": {
"default": {
"link": "http://download.yunkong2.net/sources-dist.json",
"json": null
},
"latest": {
"link": "http://download.yunkong2.net/sources-dist-latest.json",
"json": null
}
}
},
"_id": "system.repositories",
"type": "config"
};
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
var objects = options.objects;
function updateRepo(repoUrl, callback) {
var result = {};
if (!repoUrl || typeof repoUrl !== 'object') {
tools.getRepositoryFile(repoUrl, function (err, sources) {
updateRepo(sources, callback);
});
return;
}
var sources = repoUrl;
var downloads = [];
function download() {
if (downloads.length < 1) {
console.log('update done');
if (callback) callback(result);
} else {
var name = downloads.pop();
if (sources[name].version) {
result[name] = sources[name];
} else if (sources[name].meta) {
tools.getJson(sources[name].meta, function (ioPack) {
if (ioPack && ioPack.common) {
result[name] = extend(true, sources[name], ioPack.common);
}
setImmediate(download);
});
return;
} else if (sources[name].url) {
console.log('Cannot get version of "' + name + '".');
result[name] = sources[name];
} else {
console.log('Cannot get any information of "' + name + '". Ignored.');
}
setImmediate(download);
}
}
// Read repository file, local or by url
for (var name in sources) {
if (!sources.hasOwnProperty(name)) continue;
downloads.push(name);
}
download(/*sources*/);
}
this.showRepo = function (repoUrl, flags, callback) {
if (typeof flags === 'function') {
callback = flags;
flags = {};
}
function showRepoResult(_name, sources) {
var installed = tools.getInstalledInfo();
var updatable;
var keys = [];
for (var key in sources) {
if (!sources.hasOwnProperty(key)) continue;
keys.push(key);
}
keys.sort();
for (var i = 0; i < keys.length; i++) {
var name = keys[i];
if (!sources.hasOwnProperty(name)) continue;
updatable = false;
var text = (sources[name].controller ? 'Controller ' : 'Adapter ');
text += '"' + name + '"' + ((name.length < 15) ? new Array(15 - name.length).join(' ') : '');
var tLen = 10;
if (name.length >= 15) tLen -= (name.length > tLen ? 1 : 0);
if (tLen < 0) tLen = 0;
if (sources[name].version) {
text += ': ' + sources[name].version + ((sources[name].version.length < tLen) ? new Array(tLen - sources[name].version.length).join(' ') : '');
} else {
text += new Array(tLen).join(' ')
}
if ((flags.installed || flags.i) && !installed[name]) {
continue;
}
if (installed[name] && installed[name].version) {
text += ', installed ' + installed[name].version;
if (sources[name].version !== installed[name].version &&
sources[name].version &&
!tools.upToDate(sources[name].version, installed[name].version)) {
updatable = true;
text += ' [Updateable]';
}
}
if ((flags.updatable || flags.u) && !updatable) {
continue;
}
console.log(text);
}
}
// Get the repositories
objects.getObject('system.config', function (err, sysConfig) {
objects.getObject('system.repositories', function (err, obj) {
if (err || !obj) {
console.log('Error: Object "system.config" not found');
} else {
if (!obj.native || !obj.native.repositories) {
console.log('Error: no repositories found in the "system.config');
} else {
repoUrl = repoUrl || sysConfig.common.activeRepo;
console.log('Used repository: ' + repoUrl);
// If known repository
if (obj.native.repositories[repoUrl]) {
if (typeof obj.native.repositories[repoUrl] === 'string') {
obj.native.repositories[repoUrl] = {
link: obj.native.repositories[repoUrl],
json: null
};
}
updateRepo(obj.native.repositories[repoUrl].link, function (sources) {
obj.native.repositories[repoUrl].json = sources;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function () {
showRepoResult(repoUrl, sources);
if (callback) callback();
});
});
} else {
updateRepo(repoUrl, function (sources) {
showRepoResult(null, sources);
if (callback) callback();
});
}
}
}
});
});
};
this.showRepoStatus = function (callback) {
objects.getObject('system.repositories', function (err, obj) {
if (err || !obj) {
console.error('Cannot get list: ' + err);
callback(102);
} else {
if (obj.native.repositories) {
for (var r in obj.native.repositories) {
if (obj.native.repositories.hasOwnProperty(r)){
console.log(r + (r.length < 12 ? new Array(12 - r.length).join(' ') : '') + ': ' + obj.native.repositories[r].link);
}
}
objects.getObject('system.config', function (err, obj) {
if (obj) {
console.log('\nActive repo: ' + obj.common.activeRepo);
}
callback();
});
} else {
console.error('Cannot get list: ' + err);
callback(102);
}
}
});
};
this.add = function (repoName, repoUrl, callback) {
objects.getObject('system.repositories', function (err, obj) {
if (err) {
callback && callback(err);
} else
if (!obj) {
obj = defaultSystemRepo;
}
if (obj.native.repositories[repoName]) {
callback && callback('Repository "' + repoName + '" yet exists: ' + obj.native.repositories[repoName].link);
} else {
obj.native.repositories[repoName] = {
link: repoUrl,
json: null
};
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.repositories', obj, callback);
}
});
};
this.del = function (repoName, callback) {
objects.getObject('system.config', function (err, obj) {
if (err) {
callback && callback(err);
} else {
if (obj.common.activeRepo === repoName) {
callback && callback('Cannot delete active repository: ' + repoName);
} else {
objects.getObject('system.repositories', function (err, obj) {
if (err) {
callback && callback(err);
} else if (!obj) {
callback && callback();
} else {
if (!obj.native.repositories[repoName]) {
callback && callback('Repository "' + repoName + '" not found.');
} else {
delete obj.native.repositories[repoName];
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.repositories', obj, callback);
}
}
});
}
}
});
};
this.setActive = function (repoName, callback) {
objects.getObject('system.repositories', function (err, obj) {
if (err) {
callback && callback(err);
} else
if (!obj) {
obj = defaultSystemRepo;
}
if (!obj.native.repositories[repoName]) {
callback && callback('Repository "' + repoName + '" not found.');
} else {
objects.getObject('system.config', function (err, obj) {
if (err) {
callback && callback(err);
} else {
obj.common.activeRepo = repoName;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.config', obj, callback);
}
});
}
})
};
}
module.exports = Repo;

199
lib/setup/setupSetup.js Normal file
View File

@@ -0,0 +1,199 @@
'use strict';
function Setup(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
options = options || {};
var processExit = options.processExit;
var dbConnect = options.dbConnect;
var params = options.params;
var password;
var objects;
function mkpathSync(rootpath, dirpath) {
// Remove filename
dirpath = dirpath.split('/');
dirpath.pop();
if (!dirpath.length) return;
for (var i = 0; i < dirpath.length; i++) {
rootpath += dirpath[i] + '/';
if (!fs.existsSync(rootpath)) {
if (dirpath[i] !== '..') {
fs.mkdirSync(rootpath);
} else {
throw 'Cannot create ' + rootpath + dirpath.join('/');
}
}
}
}
function setupReady(callback) {
if (!callback) {
console.log('database setup done. you can add adapters and start ' + tools.appName + ' now');
processExit(0);
} else {
callback();
}
}
function dbSetup(iopkg, callback) {
if (iopkg.objects && iopkg.objects.length > 0) {
var obj = iopkg.objects.pop();
objects.getObject(obj._id, function (err, _obj) {
if (err || !_obj) {
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function () {
console.log('object ' + obj._id + ' created');
setTimeout(dbSetup, 25, iopkg, callback);
});
} else {
console.log('object ' + obj._id + ' yet exists');
setTimeout(dbSetup, 25, iopkg, callback);
}
});
} else {
tools.createUuid(objects, function () {
// check if encrypt secret exists
objects.getObject('system.config', function (err, obj) {
if (obj && (!obj.native || !obj.native.secret)) {
require('crypto').randomBytes(24, function (ex, buf) {
obj.native = obj.native || {};
obj.native.secret = buf.toString('hex');
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.config', obj, function () {
setupReady(callback);
});
});
} else {
setupReady(callback);
}
});
});
}
}
function setupObjects(callback) {
dbConnect(params, function (_objects, _states) {
objects = _objects;
var iopkg = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
dbSetup(iopkg, callback);
});
}
this.setup = function (callback, ignoreIfExist, useRedis) {
password = require(__dirname + '/../password');
var config;
var isCreated = false;
var platform = require('os').platform();
var otherInstallDirs = [];
// Delete files for other OS
if (platform.match(/^win/)) {
otherInstallDirs.push(__dirname + '/../../' + tools.appName);
otherInstallDirs.push(__dirname + '/../../killall.sh');
otherInstallDirs.push(__dirname + '/../../reinstall.sh');
} else {
otherInstallDirs.push(__dirname + '/../../_service_' + tools.appName + '.bat');
otherInstallDirs.push(__dirname + '/../../' + tools.appName + '.bat');
// copy scripts to root directory
if (fs.existsSync(__dirname + '/../../../../node_modules/')) {
try {
if (fs.existsSync(__dirname + '/../../killall.sh')) {
fs.writeFileSync(__dirname + '/../../../../killall.sh', fs.readFileSync(__dirname + '/../../killall.sh'));
}
if (fs.existsSync(__dirname + '/../../reinstall.sh')) {
fs.writeFileSync(__dirname + '/../../../../reinstall.sh', fs.readFileSync(__dirname + '/../../reinstall.sh'));
}
if (fs.existsSync(__dirname + '/../..' + tools.appName)) {
fs.writeFileSync(__dirname + '/../../../..' + tools.appName, fs.readFileSync(__dirname + '/../..' + tools.appName));
}
} catch (e) {
console.warn('Cannot write file. Not critical: ' + e);
}
}
}
for (var t = 0; t < otherInstallDirs.length; t++) {
if (fs.existsSync(otherInstallDirs[t])) {
var stat = fs.statSync(otherInstallDirs[t]);
if (stat.isDirectory()) {
var files = fs.readdirSync(otherInstallDirs[t]);
for (var f = 0; f < files.length; f++) {
fs.unlinkSync(otherInstallDirs[t] + '/' + files[f]);
}
fs.rmdirSync(otherInstallDirs[t]);
} else {
try {
fs.unlinkSync(otherInstallDirs[t]);
}
catch(e) {
console.warn('Cannot delete file. Not critical: ' + e);
}
}
}
}
// Create log and tmp directory
if (!fs.existsSync(__dirname + '/../../tmp')) fs.mkdirSync(__dirname + '/../../tmp');
if (!fs.existsSync(tools.getConfigFileName())) {
isCreated = true;
if (fs.existsSync(__dirname + '/../../conf/' + tools.appName + '-dist.json')) {
config = require(__dirname + '/../../conf/' + tools.appName + '-dist.json');
} else {
config = require(__dirname + '/../../conf/' + tools.appName.toLowerCase() + '-dist.json');
}
console.log('creating conf/' + tools.appName + '.json');
config.objects.host = params.objects || '127.0.0.1';
config.states.host = params.states || '127.0.0.1';
if (useRedis) {
config.states.type = 'redis';
config.states.port = 6379;
}
// this path is relative to yunkong2.js-controller
config.dataDir = tools.getDefaultDataDir();
if (fs.existsSync(__dirname + '/../../../node_modules/' + tools.appName + '.js-controller')) {
mkpathSync(__dirname + '/../', config.dataDir);
} else {
mkpathSync(__dirname + '/../', '../' + config.dataDir);
}
// Create default data dir
fs.writeFileSync(tools.getConfigFileName(), JSON.stringify(config, null, 2));
try {
// Create
if (__dirname.toLowerCase().replace(/\\/g, '/').indexOf('node_modules/' + tools.appName + '.js-controller') !== -1) {
var parts = config.dataDir.split('/');
// Remove appName-data/
parts.pop();
parts.pop();
var path = parts.join('/');
if (!fs.existsSync(__dirname + '/../../' + path + '/log')) fs.mkdirSync(__dirname + '/../../' + path + '/log');
} else {
if (!fs.existsSync(__dirname + '/../../log')) fs.mkdirSync(__dirname + '/../../log');
}
} catch (e) {
console.log('Non-critical error: ' + e.message);
}
} else if (ignoreIfExist) {
if (callback) callback();
return;
}
setupObjects(function () {
if (callback) callback(isCreated);
});
}
}
module.exports = Setup;

360
lib/setup/setupUpgrade.js Normal file
View File

@@ -0,0 +1,360 @@
'use strict';
function Upgrade(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var that = this;
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
if (!options.installNpm) throw 'Invalid arguments: installNpm is missing';
if (!options.restartController) throw 'Invalid arguments: restartController is missing';
if (!options.getRepository) throw 'Invalid arguments: getRepository is missing';
var objects = options.objects;
var processExit = options.processExit;
var installNpm = options.installNpm;
var restartController = options.restartController;
var getRepository = options.getRepository;
var params = options.params;
var semver;
var Upload = require(__dirname + '/setupUpload.js');
var upload = new Upload(options);
var Install = require(__dirname + '/setupInstall.js');
var install = new Install(options);
this.upgradeAdapterHelper = function (repoUrl, list, i, forceDowngrade, callback) {
that.upgradeAdapter(repoUrl, list[i], forceDowngrade, function () {
i++;
while (repoUrl[list[i]] && repoUrl[list[i]].controller) {
i++;
}
if (list[i]) {
setImmediate(function () {
that.upgradeAdapterHelper(repoUrl, list, i, forceDowngrade, callback);
});
} else if (callback) {
callback();
}
});
};
function checkDependencies(dependencies) {
if (!dependencies) return '';
// like [{"js-controller": ">=0.10.1"}]
var adapters;
if (dependencies instanceof Array) {
adapters = {};
for (var a = 0; a < dependencies.length; a++) {
if (typeof dependencies[a] === 'string') continue;
for (var b in dependencies[a]) adapters[b] = dependencies[a][b];
}
} else {
adapters = dependencies;
}
for (var adapter in adapters) {
var adapterDir = tools.getAdapterDir(adapter);
var iopack;
if (!semver) semver = require('semver');
if (adapter === 'js-controller') {
try {
iopack = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
} catch (e) {
return 'Cannot find io-package.json in "' + __dirname + '/../../": ' + e;
}
if (!iopack || !iopack.common || !iopack.common.version) return 'No version of "js-controller"';
if (!semver.satisfies(iopack.common.version, adapters[adapter])) return 'Invalid version of js-controler. Required ' + adapters[adapter];
} else {
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
return 'Cannot find io-package.json in "' + adapterDir + '": ' + e;
}
if (!iopack || !iopack.common || !iopack.common.version) return 'No version of "' + adapter + '"';
if (!semver.satisfies(iopack.common.version, adapters[adapter])) return 'Invalid version of "' + adapter + '"';
}
}
return '';
}
this.upgradeAdapter = function (repoUrl, adapter, forceDowngrade, callback) {
if (!repoUrl || typeof repoUrl !== 'object') {
getRepository(repoUrl, params, function (err, sources) {
if (err) {
processExit(err);
} else {
that.upgradeAdapter(sources, adapter, forceDowngrade, callback);
}
});
return;
}
function finishUpgrade(name, iopack, callback) {
if (!iopack) {
var adapterDir = tools.getAdapterDir(name);
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
console.error('Cannot find io-package.json in ' + adapterDir);
processExit(10);
}
}
var count = 0;
installNpm(name, function (err, _name) {
if (err) {
processExit(err);
} else {
// Upload www and admin files of adapter into CouchDB
count++;
upload.uploadAdapter(name, false, true, function () {
// extend all adapter instance default configs with current config
// (introduce potentially new attributes while keeping current settings)
upload.upgradeAdapterObjects(name, iopack, function () {
count--;
if (!count) {
console.log('Adapter "' + name + '" updated');
if (callback) callback(name);
}
});
});
count++;
upload.uploadAdapter(name, true, true, function () {
count--;
if (!count) {
console.log('Adapter "' + name + '" updated');
if (callback) callback(name);
}
});
}
});
}
var sources = repoUrl;
var version;
if (adapter.indexOf('@') !== -1) {
var parts = adapter.split('@');
adapter = parts[0];
version = parts[1];
} else {
version = '';
}
if (version) forceDowngrade = true;
var adapterDir = tools.getAdapterDir(adapter);
// Read actual description of installed adapter with version
if (!version && !fs.existsSync(adapterDir + '/io-package.json')) {
console.log('Adapter "' + adapter + '"' + ((adapter.length < 15) ? new Array(15 - adapter.length).join(' '): '') + ' is not installed.');
if (callback) callback();
return;
}
// Get the url of io-package.json or direct the version
if (!repoUrl[adapter]) {
console.log('Adapter "' + adapter + '" is not in the repository and cannot be updated.');
if (callback) callback();
return;
}
var ioInstalled;
if (fs.existsSync(adapterDir + '/io-package.json')) {
ioInstalled = require(adapterDir + '/io-package.json');
}
if (!ioInstalled) {
ioInstalled = {common: {version: '0.0.0'}};
}
// If version is included in repository
if (repoUrl[adapter].version) {
if (!forceDowngrade) {
var error = checkDependencies(repoUrl[adapter].dependencies);
if (error) {
console.error(error);
if (callback) callback();
return;
}
}
if (!forceDowngrade && (repoUrl[adapter].version === ioInstalled.common.version ||
tools.upToDate(repoUrl[adapter].version, ioInstalled.common.version))) {
console.log('Adapter "' + adapter + '"' + ((adapter.length < 15) ? new Array(15 - adapter.length).join(' '): '') + ' is up to date.');
if (callback) callback();
} else {
console.log('Update ' + adapter + ' from @' + ioInstalled.common.version + ' to @' + (version || repoUrl[adapter].version));
// Get the adapter from web site
install.downloadPacket(sources, adapter + '@' + (version || repoUrl[adapter].version), null, function (name, ioPack) {
finishUpgrade(name, ioPack, callback);
});
}
} else if (repoUrl[adapter].meta) {
// Read repository from url or file
tools.getJson(repoUrl[adapter].meta, function (iopack) {
if (!iopack) {
console.error('Cannot parse file' + repoUrl[adapter].meta);
if (callback) callback();
return;
}
if (!forceDowngrade) {
var error = checkDependencies(iopack.common ? iopack.common.dependencies : null);
if (error) {
console.error(error);
if (callback) callback();
return;
}
}
if (!version && (iopack.common.version === ioInstalled.common.version ||
(!forceDowngrade && tools.upToDate(iopack.common.version, ioInstalled.common.version)))) {
console.log('Adapter "' + adapter + '"' + ((adapter.length < 15) ? new Array(15 - adapter.length).join(' '): '') + ' is up to date.');
if (callback) callback();
} else {
// Get the adapter from web site
console.log('Update ' + adapter + ' from @' + ioInstalled.common.version + ' to @' + (version || iopack.common.version));
install.downloadPacket(sources, adapter + '@' + (version || iopack.common.version), null, function (name, ioPack) {
finishUpgrade(name, ioPack, callback);
});
}
});
} else {
if (forceDowngrade) {
console.warn('Unable to get version for "' + adapter + '". Update anyway.');
console.log('Update ' + adapter + ' from @' + ioInstalled.common.version + ' to @' + version);
// Get the adapter from web site
install.downloadPacket(sources, adapter + '@' + version, null, function (name, ioPack) {
finishUpgrade(name, ioPack, callback);
});
} else {
console.error('Unable to get version for "' + adapter + '".');
if (callback) callback();
}
}
};
this.upgradeController = function (repoUrl, forceDowngrade, callback) {
if (!repoUrl || typeof repoUrl !== 'object') {
getRepository(repoUrl, params, function (err, sources) {
if (!sources) {
console.warn('Cannot get repository under "' + repoUrl + '"');
if (callback) callback(err);
} else {
that.upgradeController(sources, forceDowngrade, callback);
}
});
return;
}
var hostname = tools.getHostName();
var installed = JSON.parse(fs.readFileSync(__dirname + '/../../io-package.json', 'utf8'));
if (!installed || !installed.common || !installed.common.version) {
console.error('Host "' + hostname + '"' + ((hostname.length < 15) ? new Array(15 - hostname.length).join(' '): '') + ' is not installed.');
if (callback) callback();
return;
}
if (!repoUrl[installed.common.name]) {
// no info for controller
console.error('Cannot find this controller "' + installed.common.name + '" in repository.');
if (callback) callback();
return;
}
if (repoUrl[installed.common.name].version) {
if (!forceDowngrade && (repoUrl[installed.common.name].version === installed.common.version ||
tools.upToDate(repoUrl[installed.common.name].version, installed.common.version))) {
console.log('Host "' + hostname + '"' + ((hostname.length < 15) ? new Array(15 - hostname.length).join(' '): '') + ' is up to date.');
if (callback) {
callback();
}
} else {
console.log('Update ' + installed.common.name + ' from @' + installed.common.version + ' to @' + repoUrl[installed.common.name].version);
// Get the controller from web site
install.downloadPacket(repoUrl, installed.common.name + '@' + repoUrl[installed.common.name].version, null, function (name) {
installNpm(function (err, _name) {
if (err) {
processExit(err);
} else {
setChmod(function () {
restartController(callback);
});
}
});
});
}
} else {
tools.getJson(repoUrl[installed.common.name].meta, function (ioPack) {
if ((!ioPack || !ioPack.common) && !forceDowngrade) {
console.warn('Cannot read version. Write "' + tools.appName + ' upgrade self --force" to upgrade controller anyway.');
if (callback) {
callback();
}
return;
}
var version = ioPack && ioPack.common ? ioPack && ioPack.common.version : '';
if (version) {
version = '@' + version;
}
if ((ioPack && ioPack.common && ioPack.common.version === installed.common.version) ||
(!forceDowngrade && ioPack && ioPack.common && tools.upToDate(ioPack.common.version, installed.common.version))) {
console.log('Host "' + hostname + '"' + ((hostname.length < 15) ? new Array(15 - hostname.length).join(' '): '') + ' is up to date.');
if (callback) callback();
} else {
var name = (ioPack && ioPack.common && ioPack.common.name) ? ioPack.common.name : installed.common.name;
console.log('Update ' + name + ' from @' + installed.common.version + ' to ' + version);
// Get the controller from web site
install.downloadPacket(repoUrl, name + version, null, function (name) {
installNpm(function (err, _name) {
if (err) {
processExit(err);
} else {
setChmod(function () {
restartController(callback);
});
}
});
});
}
});
}
};
function setChmod(callback) {
var platform = require('os').platform();
console.log('Host "' + tools.getHostName() + '" (' + platform + ') updated');
// Call command chmod +x __dirname if under linux or darwin
if (platform === 'linux' || platform === 'darwin') {
var exec = require('child_process').exec;
var dir;
if (__dirname.toLowerCase().replace(/\\/g, '/').indexOf('node_modules/' + tools.appName + '.js-controller') !== -1) {
dir = require('path').normalize(__dirname + '/../../../..').replace(/\\/g, '/');
} else {
dir = require('path').normalize(__dirname + '/../..').replace(/\\/g, '/');
}
var cmd = 'chmod -R 777 ' + dir;
console.log('Execute: ' + cmd);
var child = exec(cmd);
child.stderr.pipe(process.stdout);
child.on('exit', function () {
console.log('Chmod finished. Restart controller');
if (callback) callback();
});
} else {
if (callback) callback();
}
}
}
module.exports = Upgrade;

543
lib/setup/setupUpload.js Normal file
View File

@@ -0,0 +1,543 @@
'use strict';
function Upload(options) {
const fs = require('fs');
const tools = require(__dirname + '/../tools.js');
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
let states = options.states;
let objects = options.objects;
let mime;
// get all instances of one adapter
function getInstances(adapter, callback) {
objects.getObjectList({startkey: 'system.adapter.' + adapter + '.', endkey: 'system.adapter.' + adapter + '.\u9999'}, (err, arr) => {
let instances = [];
if (!err && arr && arr.rows) {
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'instance') continue;
instances.push(arr.rows[i].value._id);
}
}
callback(instances);
});
}
// get all instances of all adapters in the list
function getAllInstances(adapters, callback) {
let instances = [];
let count = 0;
for (let k = 0; k < adapters.length; k++) {
if (!adapters[k]) continue;
if (adapters[k].indexOf('.') === -1) count++;
}
for (let i = 0; i < adapters.length; i++) {
if (!adapters[i]) continue;
if (adapters[i].indexOf('.') === -1) {
getInstances(adapters[i], inst => {
for (let j = 0; j < inst.length; j++) {
if (instances.indexOf(inst[j]) === -1) {
instances.push(inst[j]);
}
}
if (!--count && callback) {
callback(instances);
callback = null;
}
});
} else {
if (instances.indexOf(adapters[i]) === -1) {
instances.push(adapters[i]);
}
}
}
if (!count && callback) {
callback(instances);
callback = null;
}
}
// Check if some adapters must be restarted and restart them
function checkRestartOther(adapter, callback) {
let adapterDir = tools.getAdapterDir(adapter);
try {
let adapterConf = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
if (adapterConf.common.restartAdapters) {
if (typeof adapterConf.common.restartAdapters !== 'object') adapterConf.common.restartAdapters = [adapterConf.common.restartAdapters];
if (adapterConf.common.restartAdapters.length && adapterConf.common.restartAdapters[0]) {
getAllInstances(adapterConf.common.restartAdapters, instances => {
if (!instances || !instances.length) {
if (callback) {
callback();
callback = null;
}
} else {
let instancesCount = instances.length;
for (let r = 0; r < instances.length; r++) {
objects.getObject(instances[r], (err, obj) => {
// if instance is enabled
if (!err && obj && obj.common.enabled) {
obj.common.enabled = false; // disable instance
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject(obj._id, obj, err => {
if (!err) {
obj.common.enabled = true; // enable instance
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject(obj._id, obj, err => {
console.log('Adapter "' + obj._id + '" restarted.');
if (!--instancesCount && callback) {
callback();
callback = null;
}
});
} else {
if (err) {
console.error('Cannot restart adapter "' + obj._id + '": ' + err);
} else {
console.warn('Adapter "' + obj._id + '" is disabled and cannot be restarted.');
}
if (!--instancesCount && callback) {
callback();
callback = null;
}
}
});
} else if (!--instancesCount && callback) {
callback();
callback = null;
}
});
}
}
});
} else if (callback) {
callback();
callback = null;
}
} else if (callback) {
callback();
callback = null;
}
} catch (e) {
console.error('Cannot parse ' + adapterDir + '/io-package.json:' + e);
if (callback) {
callback();
callback = null;
}
}
}
this.uploadAdapterFull = (adapters, callback) => {
if (!adapters || !adapters.length) {
if (callback) callback();
return;
}
const adapter = adapters.pop();
this.uploadAdapter(adapter, true, true, () => {
this.upgradeAdapterObjects(adapter, () => {
this.uploadAdapter(adapter, false, true, () => {
setImmediate(() => this.uploadAdapterFull(adapters, callback));
});
});
});
};
this.uploadFile = (source, target, callback) => {
const request = require('request');
target = target.replace(/\\/g, '/');
source = source.replace(/\\/g, '/');
if (target[0] === '/') target = target.substring(1);
if (target[target.length - 1] === '/') {
let name = source.split('/').pop();
name = name.split('?')[0];
if (name.indexOf('.') === -1) name = 'index.html';
target += name;
}
let parts = target.split('/');
const adapter = parts[0];
parts.splice(0, 1);
target = parts.join('/');
if (source.match(/^http:\/\/|^https:\/\//)) {
request(source, (error, response, body) => {
if (!error && response.statusCode === 200) {
objects.writeFile(adapter, target, body, err => {
if (err) console.error(err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
});
} else {
console.error('Cannot get URL: ' + error || response.statusCode);
if (typeof callback === 'function') callback(error || response.statusCode, adapter + '/' + target);
}
});
} else {
try {
objects.writeFile(adapter, target, fs.readFileSync(source), err => {
if (err) console.error(err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
});
} catch (err) {
console.error('Cannot read file "' + source + '": ' + err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
}
}
};
// Upload www folder of adapter into ObjectsDB
this.uploadAdapter = (adapter, isAdmin, forceUpload, subTree, callback) => {
const id = adapter + (isAdmin ? '.admin' : '');
const adapterDir = tools.getAdapterDir(adapter);
let dir = adapterDir + (isAdmin ? '/admin' : '/www');
let files = [];
let rev;
if (typeof subTree === 'function') {
callback = subTree;
subTree = null;
}
if (subTree) {
dir += '/' + subTree;
}
if (!isAdmin) {
let cfg;
// check for common.wwwDontUpload (required for legacy adapter)
if (fs.existsSync(adapterDir + '/io-package.json')) {
cfg = require(adapterDir + '/io-package.json');
}
if (cfg && cfg.common && cfg.common.wwwDontUpload) {
if (typeof callback === 'function') callback(adapter);
return;
}
}
// do not upload www dir of admin adapter
if (adapter === 'admin' && !isAdmin) {
// To DO remove after a while
console.log('This should never happens!');
if (typeof callback === 'function') callback(adapter);
return;
}
// Create "upload progress" object if not exists
if (!isAdmin) {
objects.getObject('system.adapter.' + adapter + '.upload', (err, obj) => {
if (err || !obj) {
objects.setObject('system.adapter.' + adapter + '.upload',
{
_id: 'system.adapter.' + adapter + '.upload',
type: 'state',
common: {
name: adapter + '.upload',
type: 'number',
role: 'indicator.state',
unit: '%',
def: 0,
desc: 'Upload process indicator'
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: Date.now(),
native: {}
}
);
}
});
// Set indicator to 0
states.setState('system.adapter.' + adapter + '.upload', 0, true);
}
if (!mime) mime = require('mime');
function done(err, res) {
if (err) {
callback();
} else {
console.log('got ' + dir);
files = res;
setTimeout(_adapter => {
maxFiles = files.length || 1;
upload(_adapter);
}, 25, adapter);
}
}
let maxFiles = 0;
let lastProgressUpdate = (new Date()).getTime();
function upload(adapter) {
let file;
if (!files.length) {
if (!isAdmin) {
states.setState('system.adapter.' + adapter + '.upload', {val: 0, ack: true}, () => {
if (typeof callback === 'function') callback(adapter);
});
} else {
if (typeof callback === 'function') callback(adapter);
}
} else {
file = files.pop();
if (file === '.gitignore') {
upload(adapter);
return;
}
const mimeType = mime.lookup(file);
let attName;
attName = file.split('/' + tools.appName + '.');
if (attName.length === 1) {
// try to find anyway if adapter is not lower case
const pos = file.toLowerCase().indexOf(tools.appName.toLowerCase());
if (pos !== -1) {
attName = ['', file.substring(tools.appName.length + 2)];
}
}
attName = attName.pop();
attName = attName.split('/').slice(2).join('/');
if (files.length > 100) {
if (!(files.length % 50)) {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
} else if (files.length > 20) {
if (!(files.length % 10)) {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
} else {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
// Update upload indicator
if (!isAdmin) {
const now = (new Date()).getTime();
if (now - lastProgressUpdate > 1000) {
lastProgressUpdate = now;
states.setState('system.adapter.' + adapter + '.upload', {val: Math.round(1000 * (maxFiles - files.length) / maxFiles) / 10, ack: true});
}
}
fs.createReadStream(file).pipe(
objects.insert(id, attName, null, mimeType, {
rev: rev
}, (err, res) => {
if (err) {
console.log(err);
if (typeof callback === 'function') callback(adapter);
}
if (res) rev = res.rev;
setTimeout(upload, 50, adapter);
})
);
}
}
function walk(dir, done) {
let results = [];
fs.readdir(dir, (err, list) => {
if (err) return done(err);
let i = 0;
(function next() {
let file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, (err, stat) => {
if (stat && stat.isDirectory()) {
walk(file, (err, res) => {
results = results.concat(res);
next();
});
} else {
if (!file.match(/\.npmignore$/) && !file.match(/\.gitignore$/)) results.push(file);
next();
}
});
})();
});
}
objects.getObject(id, (err, res) => {
if (err || !res) {
objects.setObject(id, {
type: 'meta',
common: {
name: id.split('.').pop(),
type: isAdmin ? 'admin' : 'www'
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: Date.now(),
native: {}
}, (err, res) => {
if (res) rev = res.rev;
if (!isAdmin) {
checkRestartOther(adapter, () => walk(dir, done));
} else {
walk(dir, done);
}
});
} else {
if (!forceUpload) {
if (typeof callback === 'function') callback(adapter);
} else {
rev = res.rev;
if (!isAdmin) {
checkRestartOther(adapter, () => walk(dir, done));
} else {
walk(dir, done);
}
}
}
});
};
function extendNative(target, additional) {
for (let attr in additional) {
if (additional.hasOwnProperty(attr)) {
if (target[attr] === undefined) {
target[attr] = additional[attr];
} else if (typeof additional[attr] === 'object' && !(additional[attr] instanceof Array)) {
try {
target[attr] = target[attr] || {};
} catch (e) {
console.warn(`Cannot update attribute ${attr} of native`);
}
extendNative(target[attr], additional[attr]);
}
}
}
return target;
}
function extendCommon(target, additional) {
for (let attr in additional) {
if (additional.hasOwnProperty(attr)) {
if (attr === 'title' || attr === 'schedule' || attr === 'mode' || attr === 'loglevel' || attr === 'enabled') {
if (target[attr] === undefined) {
target[attr] = additional[attr];
}
} else if (typeof additional[attr] !== 'object' || (additional[attr] instanceof Array)) {
try {
target[attr] = additional[attr];
} catch (e) {
console.warn(`Cannot update attribute ${attr} of common`);
}
} else {
target[attr] = target[attr] || {};
if (typeof target[attr] !== 'object') {
target[attr] = {}; // here we clean the simple value with object
}
extendCommon(target[attr], additional[attr]);
}
}
}
return target;
}
this.upgradeAdapterObjects = (name, iopack, callback) => {
if (typeof iopack === 'function') {
callback = iopack;
iopack = null;
}
if (!iopack) {
const adapterDir = tools.getAdapterDir(name);
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
console.error('Cannot find io-package.json in ' + adapterDir);
iopack = null;
}
}
if (!iopack) {
callback(name);
} else {
objects.getObject('system.adapter.' + name, (err, obj) => {
if (err || !obj) {
console.error('system.adapter.' + name + ' does not exist');
callback(name);
} else {
obj.common = iopack.common || {};
obj.native = iopack.native || {};
obj.common.installedVersion = iopack.common.version;
const hostname = tools.getHostName();
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject('system.adapter.' + name, obj, () => {
// Update all instances of this host
objects.getObjectView('system', 'instance', {startkey: 'system.adapter.' + name + '.', endkey: 'system.adapter.' + name + '.\u9999'}, null, function (err, res) {
let cntr = 0;
if (res) {
for (let i = 0; i < res.rows.length; i++) {
if (res.rows[i].value.common.host === hostname) {
cntr++;
objects.getObject(res.rows[i].id, (err, _obj) => {
let newObject = JSON.parse(JSON.stringify(_obj));
// all common settings should be taken from new one
newObject.common = extendCommon(newObject.common, iopack.common);
newObject.native = extendNative(newObject.native, iopack.native);
newObject.common.installedVersion = iopack.common.version;
newObject.common.version = iopack.common.version;
// Compare objects to reduce restarts of instances
if (JSON.stringify(newObject) !== JSON.stringify(_obj)) {
console.log('Update "' + newObject._id + '"');
newObject.from = 'system.host.' + tools.getHostName() + '.cli';
newObject.ts = Date.now();
objects.setObject(newObject._id, newObject, () => {
if (!--cntr && callback) callback(name);
});
} else {
if (!--cntr && callback) callback(name);
}
});
}
}
}
// updates "_design/system" and co
if (iopack.objects && typeof iopack.objects === 'object') {
for (let _id in iopack.objects) {
if (!iopack.objects.hasOwnProperty(_id)) continue;
cntr++;
iopack.objects[_id].from = 'system.host.' + tools.getHostName() + '.cli';
iopack.objects[_id].ts = Date.now();
objects.setObject(iopack.objects[_id]._id, iopack.objects[_id], err => {
if (err) console.error('Cannot update object: ' + err);
if (!--cntr && callback) callback(name);
});
}
}
if (!cntr && callback) callback(name);
});
});
}
});
}
};
}
module.exports = Upload;

522
lib/setup/setupUsers.js Normal file
View File

@@ -0,0 +1,522 @@
'use strict';
function Users(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var that = this;
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
var objects = options.objects;
var processExit = options.processExit;
this.addUser = function (user, pw, callback) {
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (obj) {
if (callback) callback('User yet exists');
} else {
objects.setObject('system.user.' + _user, {
type: 'user',
common: {
name: user,
enabled: true
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: new Date().getTime(),
native: {}
}, function (err) {
if (!err) {
that.setPassword(user, pw, callback);
} else {
if (typeof callback === 'function') callback(err);
}
});
}
});
};
this.isUser = function (user, callback) {
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (callback) callback(null, !!obj);
});
};
this.setPassword = function (user, pw, callback) {
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('User does not exist');
return;
}
var password = require(__dirname + '/../password');
password(pw).hash(null, null, function (err, res) {
if (err) {
if (typeof callback === 'function') callback(err);
return;
}
obj.common.password = res;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject('system.user.' + _user, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
});
});
};
this.checkPassword = function (user, pw, callback) {
objects.getObject('system.user.' + user, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('User does not exist');
return;
}
var password = require(__dirname + '/../password');
password(pw).check(obj.common.password, function (err, res) {
if (typeof callback === 'function') callback(err, res);
});
});
};
this.delUser = function (user, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "userdel user"');
return;
}
var _user = user.replace(/\s/g, '_');
objects.getObject('system.user.' + _user, function (err, obj) {
if (err || !obj) {
if (callback) callback('User does not exist');
} else {
if (obj.common.dontDelete) {
if (callback) callback('Cannot delete user, while is system user');
} else {
objects.delObject('system.user.' + _user, function (err) {
// Remove this user from all groups
if (!err) {
objects.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, function (err, groups) {
var count = 0;
for (var i = 0; i < groups.rows.length; i++) {
if (groups.rows[i].value.type != 'group') continue;
// find all groups
if (groups.rows[i].value.common.members && groups.rows[i].value.common.members.indexOf('system.user.' + _user) != -1) {
var pos = groups.rows[i].value.common.members.indexOf('system.user.' + _user);
groups.rows[i].value.common.members.splice(pos, 1);
count++;
groups.rows[i].value.from = 'system.host.' + tools.getHostName() + '.cli';
groups.rows[i].value.ts = new Date().getTime();
objects.setObject(groups.rows[i].value._id, groups.rows[i].value, function (err) {
if (!(--count)) callback(err);
});
}
}
if (!count) callback();
});
} else if (callback) {
callback(err);
}
});
}
}
});
};
this.addUserToGroup = function (user, group, callback) {
var _user = user.replace(/\s/g, '_');
if (!group.match(/^system\.group\./)) group = 'system.group.' + group;
if (!_user.match(/^system\.user\./)) _user = 'system.user.' + _user;
objects.getObject(_user, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('User does not exist');
return;
}
objects.getObject(group, function (err, obj) {
if (err || !obj) {
if (typeof callback === 'function') callback('Group does not exist');
return;
}
obj.common = obj.common || {};
obj.common.members = obj.common.members || [];
if (obj.common.members.indexOf(_user) == -1) {
obj.common.members.push(_user);
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(group, obj, function (err) {
callback(err);
});
} else {
callback();
}
});
});
};
this.addUserPrompt = function (user, group, password, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "adduser newUser"');
return;
}
// Check group
if (group.substring(0, 13) !== 'system.group.' ) group = 'system.group.' + group;
objects.getObject(group, function (err, obj) {
if (!obj) {
if (callback) callback('Unknown group: ' + group);
return;
}
if (!password) {
var prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
var schema = {
properties: {
password: {
description: 'Enter your password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
},
repeatPassword: {
description: 'Repeat your password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
if (result) {
if (result.password !== result.repeatPassword) {
console.log('Passwords are not identical!');
processExit(31);
}
//create user
that.addUser(user, result.password, function (err) {
if (err) {
if (callback) callback(err);
} else {
that.addUserToGroup(user, group, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
}
});
} else {
if (callback) callback(err);
}
});
} else {
that.addUser(user, password, function (err) {
if (err) {
if (callback) callback(err);
} else {
that.addUserToGroup(user, group, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
}
});
}
});
};
this.setUserPassword = function (user, password, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "passwd username"');
return;
}
this.isUser(user, function (err, result) {
if (err) console.error('Cannot read user: ' + err);
if (!result) {
if (callback) callback('User "' + user + '" does not exist.');
} else {
// Check group
if (!password) {
var prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
var schema = {
properties: {
password: {
description: 'Enter your password:',
pattern: /^[^'"]*$/,
message: 'No " are allowed',
hidden: true
},
repeatPassword: {
description: 'Repeat your password:',
pattern: /^[^'"]*$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
if (result) {
if (result.password !== result.repeatPassword) {
if (callback) callback('Passwords are not identical!');
return;
}
// set user password
that.setPassword(user, result.password, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
} else {
if (callback) callback('No password entered!');
}
});
} else {
that.setPassword(user, password, function (err) {
if (err) {
if (callback) callback(err);
} else {
if (callback) callback();
}
});
}
}
});
};
this.enableUser = function (user, enable, callback) {
if (!user) {
if (callback) callback('Please define user name, like: "enable username"');
return;
}
if (user && user.match(/^system\.user\./)) user = user.substring('system.user.'.length);
if (user === 'admin' && !enable) {
if (callback) callback('User admin cannot be disabled');
return;
}
objects.getObject('system.user.' + user, function (err, obj) {
if (err) {
if (callback) callback('Cannot read user: ' + err);
} if (!obj) {
if (callback) callback('User "' + user + '" not found');
} else {
obj.common.enabled = enable;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
};
this.checkUserPassword = function (user, password, callback) {
var prompt;
var schema;
if (!user && !password) {
prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
schema = {
properties: {
username: {
description: 'Enter username to check password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: false
},
password: {
description: 'Enter current password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
that.checkPassword(result.username, result.password, function (err, res) {
if (err || !res) {
if (callback) callback('Password for user "' + result.username + '" does not matched' + (err ? ': ' + err : ''));
} else {
if (callback) callback(null);
}
});
});
} else if (!password) {
prompt = require('prompt');
prompt.message = '';
prompt.delimiter = '';
schema = {
properties: {
password: {
description: 'Enter current password:',
pattern: /^[^'"]+$/,
message: 'No " are allowed',
hidden: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
that.checkPassword(user, result.password, function (err, res) {
if (err || !res) {
if (callback) callback('Password for user "' + user + '" does not matched' + (err ? ': ' + err : ''));
} else {
if (callback) callback(null);
}
});
});
} else{
this.checkPassword(user, password, function (err, res) {
if (err || !res) {
if (callback) callback('Password for user "' + user + '" does not matched' + (err ? ': ' + err : ''));
} else {
if (callback) callback(null);
}
});
}
};
this.getUser = function (user, callback) {
objects.getObject('system.user.' + user, function (err, obj) {
if (err) {
if (callback) callback('Cannot read user: ' + err);
} if (!obj) {
if (callback) callback('User "' + user + '" not found');
} else {
if (callback) callback(null, obj.common.enabled);
}
});
};
this.getGroup = function (group, callback) {
objects.getObject('system.group.' + group, function (err, obj) {
if (err) {
if (callback) callback('Cannot read group: ' + err);
} if (!obj) {
if (callback) callback('Group "' + group + '" not found');
} else {
if (callback) callback(null, obj.common.enabled, obj.common.members);
}
});
};
this.enableGroup = function (group, enable, callback) {
if (!group) {
if (callback) callback('Please define group name, like: "enable groupname"');
return;
}
if (group && group.match(/^system\.group\./)) group = group.substring('system.group.'.length);
if (group === 'administrator' && !enable) {
if (callback) callback('Group "administrator" cannot be disabled');
return;
}
objects.getObject('system.group.' + group, function (err, obj) {
if (err) {
if (callback) callback('Cannot read group: ' + err);
} if (!obj) {
if (callback) callback('Group "' + group + '" not found');
} else {
obj.common.enabled = enable;
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
};
this.addGroup = function (group, callback) {
var _group = group.replace(/\s/g, '_');
objects.getObject('system.group.' + _group, function (err, obj) {
if (obj) {
if (callback) callback('Group yet exists');
} else {
objects.setObject('system.group.' + _group, {
type: 'group',
common: {
name: group,
enabled: true,
members: []
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: new Date().getTime(),
native: {}
}, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
};
this.delGroup = function (group, callback) {
var _group = group.replace(/\s/g, '_');
if (group === 'administrator') {
if (typeof callback === 'function') callback('Group "administrator" cannot be deleted');
} else {
objects.getObject('system.group.' + _group, function (err, obj) {
if (!obj) {
if (callback) callback('Group does not exists');
} else {
objects.delObject('system.group.' + _group, function (err) {
if (typeof callback === 'function') callback(err);
});
}
});
}
};
this.removeUserFromGroup = function (user, group, callback) {
var _group = group.replace(/\s/g, '_');
objects.getObject('system.group.' + _group, function (err, obj) {
if (!obj) {
if (callback) callback('Group does not exists');
} else {
var pos = obj.common.members.indexOf('system.user.' + user);
if (pos === -1) {
if (typeof callback === 'function') callback('User not in group');
} else {
obj.common.members.splice(pos, 1);
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = new Date().getTime();
objects.setObject(obj._id, obj, function (err) {
if (typeof callback === 'function') callback(err);
});
}
}
});
};
}
module.exports = Users;

197
lib/setup/setupVendor.js Normal file
View File

@@ -0,0 +1,197 @@
'use strict';
function Vendor(options) {
const fs = require('fs');
const tools = require(__dirname + '/../tools.js');
options = options || {};
let objects = options.objects;
// read info from '/etc/iob_vendor.json' and executes instructions stored there
this.checkVendor = (file, password) => {
file = file || '/etc/iob-vendor.json';
let data;
if (fs.existsSync(file)) {
try {
data = JSON.parse(fs.readFileSync(file).toString('utf8'));
} catch (e) {
return Promise.reject(`cannot read or parse "${file}": ${JSON.stringify(e)}`);
}
} else {
return Promise.reject(`"${file}" does not exist`);
}
let promises = [];
if (data.uuid) {
const uuid = data.uuid;
data.uuid = null;
// check UUID
promises.push(objects.getObjectAsync('system.meta.uuid').then(obj => {
if (obj && obj.native) {
if (obj.native.uuid !== uuid) {
obj.native.uuid = uuid;
console.log(`Update "system.meta.uuid:native.uuid" = "${obj.native.uuid}"`);
obj.nonEdit = obj.nonEdit || {};
obj.nonEdit.password = password;
return objects.setObjectAsync('system.meta.uuid', obj).then(() => {
console.log('object system.meta.uuid updated: ' + uuid);
}).catch(err => {
console.error(`Cannot update system.meta.uuid: ${err}`);
});
}
} else {
return objects.setObjectAsync('system.meta.uuid', {
type: 'meta',
common: {
name: 'uuid',
type: 'uuid'
},
ts: new Date().getTime(),
from: 'system.host.' + tools.getHostName() + '.tools',
native: {
uuid: uuid
}
}).then(() => {
console.log('object system.meta.uuid created: ' + uuid);
}).catch(err => {
console.error(`Cannot create system.meta.uuid: ${err}`);
});
}
}));
}
if (data.model) {
const model = data.model;
data.model = null;
const hostname = tools.getHostName();
promises.push(objects.getObjectAsync('system.host.' + hostname).then(obj => {
if (obj && obj.common) {
if ((model.name && model.name !== 'JS controller' && obj.common.title === 'JS controller') ||
(model.icon && !obj.common.icon) ||
(model.color && !obj.common.color)) {
obj.common.title = model.name;
obj.common.icon = model.icon;
obj.common.color = model.color;
obj.nonEdit = obj.nonEdit || {};
obj.nonEdit.password = password;
console.log(`Update "system.host.${hostname}:common.title" = "${obj.common.title}"`);
console.log(`Update "system.host.${hostname}:common.icon" = "${!!obj.common.icon}"`);
console.log(`Update "system.host.${hostname}:common.color" = "${obj.common.color}"`);
return objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`object "system.host.${hostname}" updated`);
}).catch(err => {
console.error(`Cannot update "system.host.${hostname}": ${err}`);
});
}
}
}));
}
if (data.vendor) {
const vendor = JSON.parse(JSON.stringify(data.vendor));
data._vendor = JSON.parse(JSON.stringify(vendor));
data.vendor = null;
// store vendor
promises.push(objects.getObjectAsync('system.config').then(obj => {
if (obj && obj.native) {
if (JSON.stringify(obj.native.vendor) !== JSON.stringify(vendor)) {
obj.native.vendor = vendor;
obj.nonEdit = obj.nonEdit || {};
obj.nonEdit.password = password;
return objects.setObjectAsync(obj._id, obj).then(() => {
console.log('object system.config updated');
}).catch(err => {
console.error(`Cannot update system.config: ${err}`);
});
}
}
}));
}
return Promise.all(promises).then(() => {
let _promises = [];
// update all existing objects according to vendor
if (data.objects) {
for (let id in data.objects) {
if (!data.objects.hasOwnProperty(id)) continue;
if (id.indexOf('*') === -1) {
((_id, _obj) => {
_promises.push(objects.getObjectAsync(_id).then(obj => {
if (obj) {
obj.nonEdit = obj.nonEdit || {};
const originalObj = JSON.stringify(obj);
_obj.nonEdit = _obj.nonEdit || {};
_obj.nonEdit.passHash = obj.nonEdit.passHash;
// merge objects
tools.copyAttributes(_obj, obj);
if (originalObj !== JSON.stringify(obj)) {
delete obj.nonEdit.passHash;
obj.nonEdit.password = password;
console.log(`Update "${obj._id}"`);
return objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`object "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
});
}
} else {
return objects.setObjectAsync(_id, _obj).then(() => {
console.log(`object "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
});
}
}));
})(id, data.objects[id]);
} else {
id = id.replace('*', '');
((_id, _obj) => {
_promises.push(objects.getObjectListAsync({
startkey: _id,
endkey: _id + '\u9999'
}, {checked: true}).then(arr => {
let tasks = [];
if (arr && arr.rows && arr.rows.length) {
for (let g = 0; g < arr.rows.length; g++) {
let obj = arr.rows[g].value;
if (obj) {
obj.nonEdit = obj.nonEdit || {};
const originalObj = JSON.stringify(obj);
_obj.nonEdit = _obj.nonEdit || {};
_obj.nonEdit.passHash = obj.nonEdit.passHash;
// merge objects
tools.copyAttributes(_obj, obj);
if (originalObj !== JSON.stringify(obj)) {
delete obj.nonEdit.passHash;
obj.nonEdit.password = password;
console.log(`Update "${obj._id}"`);
tasks.push(objects.setObjectAsync(obj._id, obj).then(() => {
console.log(`object "${obj._id}" updated`);
}).catch(err => {
console.error(`Cannot update "${obj._id}": ${err}`);
}));
}
}
}
}
return Promise.all(tasks);
}));
})(id, data.objects[id]);
}
}
}
return Promise.all(_promises);
});
};
return this;
}
module.exports = Vendor;

175
lib/setup/setupVisDebug.js Normal file
View File

@@ -0,0 +1,175 @@
'use strict';
function VisDebug(options) {
var fs = require('fs');
var tools = require(__dirname + '/../tools.js');
var path = require('path');
// allow use without new operator
if (!(this instanceof VisDebug)) return new VisDebug(options);
options = options || {};
if (!options.objects) throw 'Invalid arguments: objects is missing';
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
var objects = options.objects;
var processExit = options.processExit;
// upload widget directory to vis directory
function uploadWidgets(dir, adapter, path, callback) {
var dirs = fs.readdirSync(dir);
var count = 0;
for (var d = 0; d < dirs.length; d++) {
var stat = fs.statSync(dir + '/' + dirs[d]);
count++;
if (stat.isDirectory()) {
uploadWidgets(dir + '/' + dirs[d], adapter, path + '/' + dirs[d], function () {
if (!--count && callback) callback();
});
} else {
console.log('Upload "' + dir + '/' + dirs[d] + '"');
objects.writeFile(adapter, path + '/' + dirs[d], fs.readFileSync(dir + '/' + dirs[d]), function () {
if (!--count && callback) callback();
});
}
}
if (!count && callback) callback();
}
this.enableDebug = function (widgetset) {
if (widgetset) {
// Try to find out the adapter directory out of a list of options
var adapterDir;
var adapterNames2Try = ['vis-' + widgetset, widgetset];
if (adapterNames2Try[0] === adapterNames2Try[1]) adapterNames2Try.splice(1, 1);
for (var i = 0; i < adapterNames2Try.length; i++) {
try {
var adapterDir2Try = tools.getAdapterDir(adapterNames2Try[i]);
// Query the entry
var stats = fs.lstatSync(adapterDir2Try);
// Is it a directory?
if (stats.isDirectory()) {
//found it!
adapterDir = adapterDir2Try;
break;
}
} catch (e) {
}
}
if (!adapterDir) throw 'Adapter not found. Tried: ' + adapterNames2Try.join(', ');
}
// copy index.html.original to index.html
// copy edit.html.original to edit.html
// correct appName.json
// correct config.js
var visDir = __dirname + '/../../node_modules/' + tools.appName + '.vis';
if (!fs.existsSync(visDir)) {
visDir = __dirname + '/../../node_modules/' + tools.appName.toLowerCase() + '.vis';
if (!fs.existsSync(visDir)) {
visDir = __dirname + '/../../../' + tools.appName + '.vis';
if (!fs.existsSync(visDir)) {
visDir = __dirname + '/../../../' + tools.appName.toLowerCase() + '.vis';
if (!fs.existsSync(visDir)) {
console.error('Cannot find ' + tools.appName + '.vis');
processExit(40);
}
}
}
}
console.log('Upload "' + path.normalize(visDir + '/www/index.html.original') + '"');
var file = fs.readFileSync(visDir + '/www/index.html.original');
objects.writeFile('vis', 'index.html', file);
console.log('Upload "' + path.normalize(visDir + '/www/edit.html.original') + '"');
file = fs.readFileSync(visDir + '/www/edit.html.original');
objects.writeFile('vis', 'edit.html', file);
console.log('Modify "' + path.normalize(visDir + '/www/cache.manifest') + '"');
file = fs.readFileSync(visDir + '/www/cache.manifest', 'utf8');
var n = file.match(/# dev build (\d+)/, '5');
n = n[1];
file = file.replace('# dev build '+ n, '# dev build ' + (parseInt(n, 10) + 1));
objects.writeFile('vis', 'cache.manifest', file);
file = fs.readFileSync(tools.getConfigFileName(), 'utf8');
file = JSON.parse(file);
var count = 0;
if (!file.objects.noFileCache) {
file.objects.noFileCache = true;
fs.writeFileSync(tools.getConfigFileName(), JSON.stringify(file, null, 2));
count++;
objects.enableFileCache(false, function (err, actual) {
console.log('Disable cache');
if (!--count) processExit();
});
}
if (widgetset) {
count++;
objects.readFile('vis', 'js/config.js', null, function (err, data) {
data = data.replace(/[\r\n]/g, '');
var json = JSON.parse(data.match(/"widgetSets":\s(.*)};/)[1]);
var found = false;
for (var f = 0; f < json.length; f++) {
if (json[f] === widgetset || json[f].name === widgetset) {
found = true;
break;
}
}
// if widget-set not found in config.js
if (!found) {
console.log('Modify config.js');
var pckg = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
if (pckg.native && pckg.native.dependencies && pckg.native.dependencies.length){
json.push({
name: widgetset,
depends: pckg.native.dependencies
});
} else {
json.push(widgetset);
}
data = data.replace(/"widgetSets":\s+.*};/, '"widgetSets": ' + JSON.stringify(json, null, 2) + '};');
objects.writeFile('vis', 'js/config.js', data, function () {
// upload all files into vis
console.log('Upload ' + adapterDir + '/widgets');
uploadWidgets(adapterDir + '/widgets', 'vis', 'widgets', function () {
if (!--count) {
// timeoout to print all messages
setTimeout(function () {
processExit();
}, 100);
}
});
});
} else {
// upload all files into vis
console.log('Upload "' + adapterDir + '/widgets' + '"');
uploadWidgets(adapterDir + '/widgets', 'vis', 'widgets', function () {
if (!--count) {
// timeoout to print all messages
setTimeout(function () {
processExit();
}, 100);
}
});
}
});
} else {
if (!count) processExit();
}
};
}
module.exports = VisDebug;

13
lib/states.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
var getConfigFileName = require(__dirname + '/tools').getConfigFileName;
var config = JSON.parse(require('fs').readFileSync(getConfigFileName(), 'utf8'));
if (!config.states) config.states = {type: 'file'};
if (config.states.type === 'file') {
module.exports = require(__dirname + '/states/statesInMemClient');
} else if (config.states.type === 'redis') {
module.exports = require(__dirname + '/states/statesInRedis');
} else {
throw 'Unknown objects type: ' + config.objects.type;
}

View File

@@ -0,0 +1,368 @@
/**
* States DB in memory - Client
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
*
* MIT License
*
*/
/** @module statesRedis */
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const io = require('socket.io-client');
function StatesInMemClient(settings) {
var client;
var subscribes = {};
var connectionTimeout;
var log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/*console.log(msg);*/},
debug: function (msg) {/*console.log(msg);*/},
info: function (msg) {/*console.log(msg);*/},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
var __construct = (function () {
if (!settings.connection.secure) {
client = io.connect('http://' + ((settings.connection.host && settings.connection.host !== '0.0.0.0') ? settings.connection.host : '127.0.0.1') + ':' + (settings.connection.port || 9000));
} else {
client = io.connect('https://' + ((settings.connection.host && settings.connection.host !== '0.0.0.0') ? settings.connection.host : '127.0.0.1') + ':' + (settings.connection.port || 9000));
}
if (typeof settings.change === 'function') {
client.on('message', function (pattern, channel, message) {
log.silly(settings.namespace + ' inMem message ', pattern, channel, message);
try {
settings.change(channel, message);
} catch (e) {
log.error(settings.namespace + ' message ' + channel + ' ' + message + ' ' + e.message);
log.error(settings.namespace + ' ' + e.stack);
}
});
}
client.on('disconnect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.silly(settings.namespace + ' ' + error);
}
});
client.on('error', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.error(settings.namespace + ' ' + error.message);
log.error(settings.namespace + ' ' + error.stack);
}
});
client.on('connect', function (error) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (typeof settings.connected === 'function') settings.connected();
});
client.on('reconnect', function (error) {
// Re-init subscribes
for (var t in subscribes) {
for (var i = 0; i < subscribes[t].length; i++) {
client.emit(t, subscribes[t][i]);
}
}
if (typeof settings.connected === 'function') settings.connected();
});
connectionTimeout = setTimeout(function () {
if (typeof settings.connectTimeout === 'function') settings.connectTimeout('Connection timeout');
connectionTimeout = null;
}, 5000);
})();
/**
* @method setState
* @param id {String} the id of the value. '<namespace>.' will be prepended
* @param state {any}
*
*
* an object containing the actual value and some metadata:<br>
* setState(id, {'val': val, 'ts': ts, 'ack': ack, 'from': from, 'lc': lc})
*
* if no object is given state is treated as val:<br>
* setState(id, val)
*
* <ul><li><b>val</b> the actual value. Can be any JSON-stringifiable object. If undefined the
* value is kept unchanged.</li>
*
* <li><b>ack</b> a boolean that can be used to mark a value as confirmed, used in bidirectional systems which
* acknowledge that a value has been successfully set. Will be set to false if undefined.</li>
*
* <li><b>ts</b> a unix timestamp indicating the last write-operation on the state. Will be set by the
* setState method if undefined.</li>
*
* <li><b>lc</b> a unix timestamp indicating the last change of the actual value. this should be undefined
* when calling setState, it will be set by the setValue method itself.</li></ul>
*
* @param callback {Function} will be called when redis confirmed reception of the command
*
*
*/
this.setState = function (id, state, callback) {
if (!client) return;
client.emit('setState', id, state, callback);
};
// Used for restore function (do not call it
this.setRawState = function (id, state, callback) {
if (!client) return;
client.emit('setRawState', id, state, callback);
};
/**
* @method getState
*
* @param {String} id
* @param callback
*/
this.getState = function (id, callback) {
if (!client) return;
client.emit('getState', id, callback);
};
this.getStates = function (keys, callback) {
if (!client) return;
client.emit('getStates', keys, function (err, res) {
if (callback) callback(err, res);
});
};
this.delState = function (id, callback) {
if (!client) return;
client.emit('delState', id, callback);
};
this.getKeys = function (pattern, callback, dontModify) {
if (!client) return;
client.emit('getKeys', pattern, callback);
};
/**
* @method subscribe
*
* @param {string} pattern
* @param {function} callback
*/
this.subscribe = function (pattern, callback) {
subscribes.subscribe = subscribes.subscribe || [];
if (subscribes.subscribe.indexOf(pattern) === -1) subscribes.subscribe.push(pattern);
if (!client) return;
client.emit('subscribe', pattern, callback);
};
this.unsubscribe = function (pattern, callback) {
if (subscribes.subscribe) {
var pos = subscribes.subscribe.indexOf(pattern);
if (pos !== -1) subscribes.subscribe.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribe', pattern, callback);
};
// this.pushFifoExists = function (id, state, callback) {
// if (!client) return;
// client.emit('pushFifoExists', id, state, callback);
// };
//
// this.pushFifo = function (id, state, callback) {
// if (!client) return;
// client.emit('pushFifo', id, state, callback);
// };
//
// this.lenFifo = function (id, callback) {
// if (!client) return;
// client.emit('lenFifo', id, callback);
// };
//
// this.getFifo = function (id, callback) {
// if (!client) return;
// client.emit('getFifo', id, callback);
// };
//
// this.getFifoRange = function (id, start, end, callback) {
// if (!client) return;
// client.emit('getFifoRange', id, start, end, callback);
// };
//
// this.trimFifo = function (id, minLength, maxLength, callback) {
// if (!client) return;
// client.emit('trimFifo', id, minLength, maxLength, callback);
// };
this.pushMessage = function (id, state, callback) {
if (!client) return;
client.emit('pushMessage', id, state, callback);
};
this.lenMessage = function (id, callback) {
if (!client) return;
client.emit('lenMessage', id, callback);
};
this.getMessage = function (id, callback) {
if (!client) return;
client.emit('getMessage', id, callback);
};
this.delMessage = function (id, messageId, callback) {
if (!client) return;
client.emit('delMessage', id, messageId, callback);
};
this.subscribeMessage = function (pattern, callback) {
subscribes.subscribeMessage = subscribes.subscribeMessage || [];
if (subscribes.subscribeMessage.indexOf(pattern) === -1) subscribes.subscribeMessage.push(pattern);
if (!client) return;
client.emit('subscribeMessage', pattern, callback);
};
this.unsubscribeMessage = function (pattern, callback) {
if (subscribes.subscribeMessage) {
var pos = subscribes.subscribeMessage.indexOf(pattern);
if (pos !== -1) subscribes.subscribeMessage.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribeMessage', pattern, callback);
};
this.pushLog = function (id, state, callback) {
if (!client) return;
client.emit('pushLog', id, state, callback);
};
this.lenLog = function (id, callback) {
if (!client) return;
client.emit('lenLog', id, callback);
};
this.getLog = function (id, callback) {
if (!client) return;
client.emit('getLog', id, callback);
};
this.delLog = function (id, logId, callback) {
if (!client) return;
client.emit('delLog', id, logId, callback);
};
this.subscribeLog = function (pattern, callback) {
subscribes.subscribeLog = subscribes.subscribeLog || [];
if (subscribes.subscribeLog.indexOf(pattern) === -1) subscribes.subscribeLog.push(pattern);
if (!client) return;
client.emit('subscribeLog', pattern, callback);
};
this.unsubscribeLog = function (pattern, callback) {
if (subscribes.subscribeLog) {
var pos = subscribes.subscribeLog.indexOf(pattern);
if (pos !== -1) subscribes.subscribeLog.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribeLog', pattern, callback);
};
this.getSession = function (id, callback) {
if (!client) return;
client.emit('getSession', id, callback);
};
this.setSession = function (id, expire, obj, callback) {
if (!client) return;
client.emit('setSession', id, expire, obj, callback);
};
this.destroySession = function (id, callback) {
if (!client) return;
client.emit('destroySession', id, callback);
};
this.getConfig = function (id, callback) {
if (!client) return;
client.emit('getConfig', id, callback);
};
this.getConfigKeys = function (pattern, callback, dontModify) {
if (!client) return;
client.emit('getConfigKeys', pattern, callback);
};
this.getConfigs = function (keys, callback, dontModify) {
if (!client) return;
client.emit('getConfigs', keys, callback);
};
this.setConfig = function (id, obj, callback) {
if (!client) return;
client.emit('setConfig', id, obj, callback);
};
this.delConfig = function (id, callback) {
if (!client) return;
client.emit('delConfig', id, callback);
};
this.subscribeConfig = function (pattern, callback) {
subscribes.subscribeConfig = subscribes.subscribeConfig || [];
if (subscribes.subscribeConfig.indexOf(pattern) === -1) subscribes.subscribeConfig.push(pattern);
if (!client) return;
client.emit('subscribeConfig', pattern, callback);
};
this.unsubscribeConfig = function (pattern, callback) {
if (subscribes.subscribeConfig) {
var pos = subscribes.subscribeConfig.indexOf(pattern);
if (pos !== -1) subscribes.subscribeConfig.splice(pos, 1);
}
if (!client) return;
client.emit('unsubscribeConfig', pattern, callback);
};
this.setBinaryState = function (id, data, callback) {
if (!client) return;
client.emit('setBinaryState', id, data, callback);
};
this.getBinaryState = function (id, callback) {
if (!client) return;
client.emit('getBinaryState', id, callback);
};
this.delBinaryState = function (id, callback) {
if (!client) return;
client.emit('delBinaryState', id, callback);
};
}
module.exports = StatesInMemClient;

File diff suppressed because it is too large Load Diff

636
lib/states/statesInRedis.js Normal file
View File

@@ -0,0 +1,636 @@
/**
* States DB in redis - Client
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
* Copyright 2013-2014 hobbyquaker
*
* MIT License
*
*/
/** @module statesRedis */
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const redis = require('redis');
function StateRedis(settings) {
const redisNamespace = (settings.redisNamespace || 'io') + '.';
const namespaceMsg = (settings.namespaceMsg || 'messagebox') + '.';
const namespaceLog = (settings.namespaceLog || 'log') + '.';
const namespaceSession = (settings.namespaceSession || 'session') + '.';
// const namespaceConfig = (settings.namespaceConfig || 'config') + '.';
const onChange = settings.change; // on change handler
let globalMessageId = Math.round(Math.random() * 100000000);
let globalLogId = Math.round(Math.random() * 100000000);
settings.namespace = settings.namespace || settings.hostname || '';
let client;
let clientBin;
let sub;
const ioRegExp = new RegExp('^' + redisNamespace);
let log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/* console.log(msg); */},
debug: function (msg) {/* console.log(msg); */},
info: function (msg) {/* console.log(msg); */},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
// limit max number of log entries in the list
settings.connection.maxQueue = settings.connection.maxQueue || 1000;
if (settings.connection.options) {
if (settings.connection.options.retry_max_delay) {
const retry_max_delay = settings.connection.options.retry_max_delay;
// convert redis 0.1 options to redis 3.0
settings.connection.options.retry_strategy = function (options) {
// A function that receives an options object as parameter including the retry attempt,
// the total_retry_time indicating how much time passed since the last time connected,
// the error why the connection was lost and the number of times_connected in total.
// If you return a number from this function, the retry will happen exactly after that
// time in milliseconds. If you return a non-number, no further retry will happen and
// all offline commands are flushed with errors. Return an error to return that
// specific error to all offline commands.
return retry_max_delay;
/*if (options.error.code === 'ECONNREFUSED') {
// End reconnecting on a specific error and flush all commands with a individual error
return new Error('The server refused the connection');
}
if (options.total_retry_time > 1000 * 60 * 60) {
// End reconnecting after a specific timeout and flush all commands with a individual error
return new Error('Retry time exhausted');
}
if (options.times_connected > 10) {
// End reconnecting with built in error
return undefined;
}
// reconnect after
return Math.max(options.attempt * 100, 3000);*/
};
delete settings.connection.options.retry_max_delay;
}
}
/**
* @method setState
* @param id {String} the id of the value. '<redisNamespace>.' will be prepended
* @param state {any}
*
*
* an object containing the actual value and some metadata:<br>
* setState(id, {'val': val, 'ts': ts, 'ack': ack, 'from': from, 'lc': lc})
*
* if no object is given state is treated as val:<br>
* setState(id, val)
*
* <ul><li><b>val</b> the actual value. Can be any JSON-stringifiable object. If undefined the
* value is kept unchanged.</li>
*
* <li><b>ack</b> a boolean that can be used to mark a value as confirmed, used in bidirectional systems which
* acknowledge that a value has been successfully set. Will be set to false if undefined.</li>
*
* <li><b>ts</b> a unix timestamp indicating the last write-operation on the state. Will be set by the
* setState method if undefined.</li>
*
* <li><b>lc</b> a unix timestamp indicating the last change of the actual value. this should be undefined
* when calling setState, it will be set by the setValue method itself.</li></ul>
*
* @param callback {Function} will be called when redis confirmed reception of the command
*/
this.setState = function (id, state, callback) {
let expire;
if (state.expire) {
expire = state.expire;
delete state.expire;
}
//var that = this;
let obj = {};
if (typeof state !== 'object') {
state = {
val: state
};
}
client.get(redisNamespace + id, function (err, oldObj) {
// TODO Error Handling
if (err) log.warn(settings.namespace + ' ' + err);
if (!oldObj) {
oldObj = {};
} else {
try {
oldObj = JSON.parse(oldObj);
} catch (e) {
oldObj = {};
}
}
if (state.val !== undefined) {
obj.val = state.val;
} else {
obj.val = oldObj.val;
}
if (state.ack !== undefined) {
obj.ack = state.ack;
} else {
obj.ack = false;
}
if (state.ts !== undefined) {
obj.ts = (state.ts < 946681200000) ? state.ts * 1000 : state.ts; // if less 2000.01.01 00:00:00
} else {
obj.ts = (new Date()).getTime();
}
if (state.q !== undefined) {
obj.q = state.q;
} else {
obj.q = 0;
}
obj.from = state.from;
let hasChanged;
if (state.lc !== undefined) {
obj.lc = state.lc;
} else {
if (typeof obj.val === 'object') {
hasChanged = JSON.stringify(oldObj.val) !== JSON.stringify(obj.val);
} else {
hasChanged = oldObj.val !== obj.val;
}
if (!oldObj.lc || hasChanged) {
obj.lc = obj.ts;
} else {
obj.lc = oldObj.lc;
}
}
// publish event in redis
log.silly(settings.namespace + ' redis publish ' + redisNamespace + id + ' ' + JSON.stringify(obj));
client.publish(redisNamespace + id, JSON.stringify(obj));
// set object in redis
if (expire) {
//console.log('setex',redisNamespace + id, expire, JSON.stringify(obj));
client.setex(redisNamespace + id, expire, JSON.stringify(obj), function () {
if (typeof callback === 'function') {
callback();
}
});
} else {
//console.log('set',redisNamespace + id, JSON.stringify(obj));
client.set(redisNamespace + id, JSON.stringify(obj), function () {
if (typeof callback === 'function') {
callback();
}
});
}
});
};
// Used for restore function (do not call it)
this.setRawState = function (id, state, callback) {
//console.log('set',redisNamespace + id, JSON.stringify(obj));
client.set(redisNamespace + id, state, function () {
if (typeof callback === 'function') {
callback();
}
});
};
/**
* @method getState
*
* @param {String} id
* @param callback
*/
this.getState = function (id, callback) {
client.get(redisNamespace + id, function (err, obj) {
if (err) {
log.warn(settings.namespace + ' redis get ' + id + ', error - ' + err);
} else {
log.silly(settings.namespace + ' redis get ' + id + ' ok: ' + obj);
}
if (typeof callback === 'function') {
callback(err, obj ? JSON.parse(obj) : null);
}
});
};
this.getStates = function (keys, callback, dontModify) {
if (!keys) {
if (callback) callback('no keys', null);
return;
}
if (!keys.length) {
if (callback) callback(null, []);
return;
}
let _keys;
if (!dontModify) {
_keys = [];
for (let i = 0; i < keys.length; i++) {
_keys[i] = redisNamespace + keys[i];
}
} else {
_keys = keys;
}
client.mget(_keys, function (err, obj) {
if (err) {
log.warn(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length + ', err: ' + err);
} else {
log.silly(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length);
}
if (typeof callback === 'function') callback(err, obj);
});
};
// Destructor of the class. Called by shutting down.
this.destroy = function () {
if (client) {
client.end(true);
client = null;
}
if (sub) {
sub.end();
sub = null;
}
};
this.delState = function (id, callback) {
client.del(redisNamespace + id, function (err) {
if (err) {
log.warn(settings.namespace + ' redis del ' + id + ', error - ' + err);
} else {
client.publish(redisNamespace + id, 'null');
log.silly(settings.namespace + ' redis del ' + id + ', ok');
}
if (typeof callback === 'function') callback(err);
});
};
this.getKeys = function (pattern, callback, dontModify) {
client.keys(redisNamespace + pattern, function (err, obj) {
log.silly(settings.namespace + ' redis keys ' + obj.length + ' ' + pattern);
if (typeof callback === 'function') {
if (obj && !dontModify) {
const len = redisNamespace.length;
for (let i = 0; i < obj.length; i++) {
obj[i] = obj[i].substring(len);
}
}
callback(err, obj);
}
});
};
/**
* @method subscribe
*
* @param pattern
* @param {function} callback callback function (optional)
*/
this.subscribe = function (pattern, callback) {
log.silly(settings.namespace + ' redis psubscribe ' + redisNamespace + pattern);
sub.psubscribe(redisNamespace + pattern, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribe = function (pattern, callback) {
log.silly(settings.namespace + ' redis punsubscribe ' + redisNamespace + pattern);
sub.punsubscribe(redisNamespace + pattern, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.pushMessage = function (id, state, callback) {
state._id = globalMessageId++;
if (globalMessageId >= 0xFFFFFFFF) globalMessageId = 0;
client.publish(namespaceMsg + id, JSON.stringify(state));
if (typeof callback === 'function') callback(null, id);
};
// todo: delete it
this.lenMessage = function (id, callback) {
if (typeof callback === 'function') callback(null, 0, id);
};
// todo: delete it
this.getMessage = function (id, callback) {
if (typeof callback === 'function') callback(null, null, id);
};
// todo: delete it
this.delMessage = function (id, messageId, callback) {
if (typeof callback === 'function') callback(null, id);
};
// todo: delete it
this.clearAllMessages = function (callback) {
client.keys(namespaceLog + '*', function (err, obj) {
if (obj) {
for (let i = 0; i < obj.length; i++) {
log.silly('redis clear message for ' + obj[i]);
client.del(obj[i]);
}
}
if (typeof callback === 'function') callback(err);
});
};
this.subscribeMessage = function (id, callback) {
if (id && id[0] === '.') id = id.substring(1);
log.silly('redis subscribeMessage ' + namespaceMsg + id);
sub.psubscribe(namespaceMsg + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribeMessage = function (id, callback) {
if (id && id[0] === '.') id = id.substring(1);
log.silly('redis unsubscribeMessage ' + namespaceMsg + id);
sub.punsubscribe(namespaceMsg + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.pushLog = function (id, log, callback) {
log._id = globalLogId++;
if (globalLogId >= 0xFFFFFFFF) globalLogId = 0;
client.publish(namespaceLog + id, JSON.stringify(log));
if (typeof callback === 'function') callback(null, id);
};
// todo: delete it
this.lenLog = function (id, callback) {
if (typeof callback === 'function') callback('Not exists', 0, id);
// client.llen(namespaceLog + id, function (err, obj) {
// if (typeof callback === 'function') callback(err, obj, id);
// });
};
// todo: delete it
this.getLog = function (id, callback) {
if (typeof callback === 'function') {
callback('Not exists', null, 0);
}
};
// todo: delete it
this.delLog = function (id, logId, callback) {
if (typeof callback === 'function') {
callback('Not exists');
}
};
// todo: delete it
this.clearAllLogs = function (callback) {
client.keys(namespaceLog + '*', function (err, obj) {
if (obj) {
for (let i = 0; i < obj.length; i++) {
log.silly(settings.namespace + ' redis clear log for ' + obj[i]);
client.del(obj[i]);
}
}
if (typeof callback === 'function') {
callback(err);
}
});
};
this.subscribeLog = function (id, callback) {
log.silly(settings.namespace + ' redis subscribeMessage ' + namespaceLog + id);
sub.psubscribe(namespaceLog + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribeLog = function (id, callback) {
log.silly(settings.namespace + ' redis unsubscribeMessage ' + namespaceLog + id);
sub.punsubscribe(namespaceLog + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.getSession = function (id, callback) {
client.get(namespaceSession + id, function (err, obj) {
log.silly(settings.namespace + ' redis get ' + id + ' ' + obj);
if (typeof callback === 'function') callback(obj ? JSON.parse(obj) : null);
});
};
this.setSession = function (id, expire, obj, callback) {
client.setex(namespaceSession + id, expire, JSON.stringify(obj), function () {
log.silly(settings.namespace + ' redis setex', id, expire, obj);
if (typeof callback === 'function') callback();
});
};
this.destroySession = function (id, callback) {
id = namespaceSession + id;
log.silly(settings.namespace + ' redis del ' + id);
client.del(id, function () {
if (typeof callback === 'function') callback();
});
};
/* this.getConfig = function (id, callback) {
id = namespaceConfig + id;
client.get(id, function (err, obj) {
log.silly(settings.namespace + ' redis get ' + id + ' ' + obj);
if (typeof callback === 'function') callback(err, obj ? JSON.parse(obj) : null);
});
};
this.getConfigKeys = function (pattern, callback, dontModify) {
client.keys(namespaceConfig + pattern, function (err, obj) {
log.silly(settings.namespace + ' redis config keys ' + obj.length + ' ' + pattern);
if (typeof callback === 'function') {
if (obj && !dontModify) {
var len = redisNamespace.length;
for (var i = 0; i < obj.length; i++) {
obj[i] = obj[i].substring(len);
}
}
callback(err, obj);
}
});
};
this.getConfigs = function (keys, callback, dontModify) {
if (!keys) {
if (callback) callback('no keys', null);
return;
}
if (!keys.length) {
if (callback) callback(null, []);
return;
}
var _keys;
if (!dontModify) {
_keys = [];
for (var i = 0; i < keys.length; i++) {
_keys[i] = namespaceConfig + keys[i];
}
} else {
_keys = keys;
}
client.mget(_keys, function (err, obj) {
if (err) {
log.warn(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length + ', err: ' + err);
} else {
log.silly(settings.namespace + ' redis mget ' + ((!obj) ? 0 : obj.length) + ' ' + _keys.length);
}
if (typeof callback === 'function') callback(err, obj);
});
};
this.setConfig = function (id, obj, callback) {
id = namespaceConfig + id;
client.set(id, JSON.stringify(obj), function (err) {
log.silly(settings.namespace + ' redis set', id, obj);
client.publish(id, JSON.stringify(obj));
if (typeof callback === 'function') callback(err, {id: id});
});
};
this.delConfig = function (id, callback) {
id = namespaceConfig + id;
log.silly(settings.namespace + ' redis del ' + id);
client.del(id, function (err) {
client.publish(id, null);
if (typeof callback === 'function') callback(err);
});
};
this.subscribeConfig = function (id, callback) {
log.silly(settings.namespace + ' redis subscribeConfig ' + namespaceConfig + id);
sub.psubscribe(namespaceConfig + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};
this.unsubscribeConfig = function (id, callback) {
log.silly(settings.namespace + ' redis unsubscribeConfig ' + namespaceConfig + id);
sub.punsubscribe(namespaceConfig + id, function (err) {
if (typeof callback === 'function') callback(err);
});
};*/
function _createBinaryClient() {
if (!clientBin) {
settings.connection.options = settings.connection.options || {};
let opt = JSON.parse(JSON.stringify(settings.connection.options));
opt.return_buffers = true;
if (settings.connection.port === 0) {
// initiate a unix socket connection using the parameter 'host'
clientBin = redis.createClient(settings.connection.host, opt);
} else {
clientBin = redis.createClient(settings.connection.port, settings.connection.host, opt);
}
}
}
this.setBinaryState = function (id, data, callback) {
if (!clientBin) _createBinaryClient ();
clientBin.set(id, data, callback);
};
this.getBinaryState = function (id, callback) {
if (!clientBin) _createBinaryClient ();
clientBin.get(id, function (err, data) {
if (!err && data) {
if (callback) callback(err, new Buffer(data, 'binary'));
} else {
if (callback) callback(err);
}
});
};
this.delBinaryState = function (id, callback) {
if (!clientBin) _createBinaryClient ();
clientBin.del(id, function () {
if (typeof callback === 'function') callback();
});
};
(function __construct() {
if (settings.connection.port === 0) {
// initiate a unix socket connection using the parameter 'host'
client = redis.createClient(settings.connection.host, settings.connection.options);
sub = redis.createClient(settings.connection.host, settings.connection.options);
} else {
client = redis.createClient(settings.connection.port, settings.connection.host, settings.connection.options);
sub = redis.createClient(settings.connection.port, settings.connection.host, settings.connection.options);
}
if (typeof onChange === 'function') {
sub.on('pmessage', function (pattern, channel, message) {
log.debug(settings.namespace + ' redis pmessage ', pattern, channel, message);
try {
if (ioRegExp.test(channel)) {
onChange(channel.slice(redisNamespace.length), message ? JSON.parse(message) : null);
} else {
onChange(channel, message ? JSON.parse(message) : null);
}
} catch (e) {
log.error(settings.namespace + ' pmessage ' + channel + ' ' + message + ' ' + e.message);
log.error(settings.namespace + ' ' + e.stack);
}
});
}
client.on('error', error => {
if (typeof settings.disconnected === 'function') {
settings.disconnected(error);
} else {
log.error(settings.namespace + ' ' + error.message);
log.error(settings.namespace + ' ' + error.stack);
}
});
sub.on('error', error => {
log.error(settings.namespace + ' No redis connection!');
});
sub.on('connect', error => {
if (settings.connection.port === 0) {
log.info(settings.namespace + ' States connected to redis: ' + settings.connection.host);
} else {
log.info(settings.namespace + ' States connected to redis: ' + settings.connection.host + ':' + settings.connection.port);
}
});
client.on('connect', error => {
if (typeof settings.connected === 'function') settings.connected();
});
})();
return this;
}
module.exports = StateRedis;

1203
lib/tools.js Normal file

File diff suppressed because it is too large Load Diff

144
lib/uploadFiles.js Normal file
View File

@@ -0,0 +1,144 @@
/**
*
* upload.js
*
* bulk upload a folder as attachment into one CouchDB object
*
* 8'2014 hobbyquaker <hq@ccu.io>
*
*/
'use strict';
var yargs = require('yargs')
.alias('d', 'dir')
.alias('o', 'object')
.alias('p', 'prefix')
.demand(['object'])
.usage('$0 --object object._id [--dir directory] [--prefix prefix]\n' +
'Example: \n' +
' Upload the content of the folder "images" into the virtual folder "img" of the virtual filesystem "fs.www"\n' +
' $0 -o fs.www -d images -p img')
;
var fs = require('fs');
var mime = require('mime');
var Objects = require(__dirname + '/objects.js');
var tools = require('./tools');
var files = [];
var rev;
var dir;
var prefix;
var db = new Objects({
logger: {
silly: function (msg) { },
debug: function (msg) { },
info: function (msg) { },
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: function (type) {
var id = yargs.argv.object;
dir = yargs.argv.dir || './';
prefix = yargs.argv.prefix || '';
db.getObject(yargs.argv.object, function (err, res) {
if (err || !res) {
db.setObject(yargs.argv.object, {
type: 'fs',
parent: 'fs',
common: {
name: yargs.argv.object.split('.').pop()
},
ts: new Date().getTime(),
from: 'system.host.' + tools.getHostName() + '.cli',
native: {}
}, function (err, res) {
rev = res.rev;
main();
});
} else {
rev = res._rev;
main();
}
});
}
});
function main() {
walk(dir, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
files = res;
upload();
});
}
function upload() {
var file;
if (!files.length) {
console.log('done. ' + yargs.argv.object + '._rev=' + rev);
process.exit(0);
} else {
file = files.pop();
var mimeType = mime.lookup(file);
var attName = prefix + (file.split('/').slice(1).join('/'));
console.log('upload', file, attName, mimeType);
fs.createReadStream(file).pipe(
db.insert(yargs.argv.object, attName, null, mimeType, {
rev: rev
}, function (err, res) {
if (err) {
console.log(err);
process.exit(1);
}
rev = res.rev;
setTimeout(function () {
upload();
}, 50);
})
);
}
}
function walk(dir, done) {
var results = [];
fs.readdir(dir, function (err, list) {
if (err) return done(err);
var i = 0;
(function next() {
var file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, function (err, stat) {
if (stat && stat.isDirectory()) {
walk(file, function (err, res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
}

53
lib/vis/states.js Normal file
View File

@@ -0,0 +1,53 @@
var getUsedObjectIDs = require(__dirname + '/../www/js/visUtils').getUsedObjectIDs;
function calcProject(objects, projects, instance, result, callback) {
if (!projects || !projects.length) {
callback(null, result || []);
return;
}
result = result || [];
var project = projects.shift();
if (!project || !project.isDir) {
setImmediate(calcProject, objects, projects, instance, result, callback);
return;
}
// calculate datapoints in one project
objects.readFile('vis.' + instance, '/' + project.file + '/vis-views.json', function (err, data) {
var json;
try {
json = JSON.parse(data);
} catch (e) {
console.error('Cannot parse "/' + project.file + '/vis-views.json');
setImmediate(calcProject, objects, projects, instance, result, callback);
return;
}
var dps = getUsedObjectIDs(json, false);
if (dps && dps.IDs) {
result.push({id: 'vis.' + instance + '.datapoints.' + project.file.replace(/[.\\s]/g, '_'), val: dps.IDs.length});
}
setImmediate(calcProject, objects, projects, instance, result, callback);
});
}
function calcProjects(objects, states, instance, config, callback) {
objects.readDir('vis.' + instance, '/', function (err, projects) {
if (err || !projects || !projects.length) {
callback && callback(err || null, [{id: 'vis.' + instance + '.datapoints.total', val: 0}]);
} else {
calcProject(objects, projects, instance, [], function (err, result) {
if (result && result.length) {
var total = 0;
for (var r = 0; r < result.length; r++) {
total += result[r].val;
}
result.push({id: 'vis.' + instance + '.datapoints.total', val: total});
}
callback && callback(err, result);
});
}
});
}
module.exports = calcProjects;

537
lib/www/js/visUtils.js Normal file
View File

@@ -0,0 +1,537 @@
function getWidgetGroup(views, view, widget) {
var widgets = views[view].widgets;
var members;
for (var w in widgets) {
if (!widgets.hasOwnProperty(w)) continue;
members = views[view].widgets[w].data.members;
if (members && members.indexOf(widget) !== -1) {
return w;
}
}
return null;
}
function extractBinding(format) {
var oid = format.match(/{(.+?)}/g);
var result = null;
if (oid) {
if (oid.length > 50) {
console.warn('Too many bindings in one widget: ' + oid.length + '[max = 50]');
}
for (var p = 0; p < oid.length && p < 50; p++) {
var _oid = oid[p].substring(1, oid[p].length - 1);
if (_oid[0] === '{') continue;
// If first symbol '"' => it is JSON
if (_oid && _oid[0] === '"') continue;
var parts = _oid.split(';');
result = result || [];
var systemOid = parts[0].trim();
var visOid = systemOid;
var test1 = visOid.substring(visOid.length - 4);
var test2 = visOid.substring(visOid.length - 3);
if (visOid && test1 !== '.val' && test2 !== '.ts' && test2 !== '.lc' && test1 !== '.ack') {
visOid = visOid + '.val';
}
var isSeconds = (test2 === '.ts' || test2 === '.lc');
test1 = systemOid.substring(systemOid.length - 4);
test2 = systemOid.substring(systemOid.length - 3);
if (test1 === '.val' || test1 === '.ack') {
systemOid = systemOid.substring(0, systemOid.length - 4);
} else if (test2 === '.lc' || test2 === '.ts') {
systemOid = systemOid.substring(0, systemOid.length - 3);
}
var operations = null;
var isEval = visOid.match(/[\d\w_.]+:[-\d\w_.]+/) || (!visOid.length && parts.length > 0);//(visOid.indexOf(':') !== -1) && (visOid.indexOf('::') === -1);
if (isEval) {
var xx = visOid.split(':', 2);
var yy = systemOid.split(':', 2);
visOid = xx[1];
systemOid = yy[1];
operations = operations || [];
operations.push({
op: 'eval',
arg: [{
name: xx[0],
visOid: visOid,
systemOid: systemOid
}]
});
}
for (var u = 1; u < parts.length; u++) {
// eval construction
if (isEval) {
if (parts[u].trim().match(/^[\d\w_.]+:[-.\d\w_]+$/)) {//parts[u].indexOf(':') !== -1 && parts[u].indexOf('::') === -1) {
var _systemOid = parts[u].trim();
var _visOid = _systemOid;
test1 = _visOid.substring(_visOid.length - 4);
test2 = _visOid.substring(_visOid.length - 3);
if (test1 !== '.val' && test2 !== '.ts' && test2 !== '.lc' && test1 !== '.ack') {
_visOid = _visOid + '.val';
}
test1 = systemOid.substring(_systemOid.length - 4);
test2 = systemOid.substring(_systemOid.length - 3);
if (test1 === '.val' || test1 === '.ack') {
_systemOid = _systemOid.substring(0, _systemOid.length - 4);
} else if (test2 === '.lc' || test2 === '.ts') {
_systemOid = _systemOid.substring(0, _systemOid.length - 3);
}
var x1 = _visOid.split(':', 2);
var y1 = _systemOid.split(':', 2);
operations[0].arg.push({
name: x1[0],
visOid: x1[1],
systemOid: y1[1]
});
} else {
parts[u] = parts[u].replace(/::/g, ':');
if (operations[0].formula) {
var n = JSON.parse(JSON.stringify(operations[0]));
n.formula = parts[u];
operations.push(n);
} else {
operations[0].formula = parts[u];
}
}
} else {
var parse = parts[u].match(/([\w\s\/+*-]+)(\(.+\))?/);
if (parse && parse[1]) {
parse[1] = parse[1].trim();
// operators requires parameter
if (parse[1] === '*' ||
parse[1] === '+' ||
parse[1] === '-' ||
parse[1] === '/' ||
parse[1] === '%' ||
parse[1] === 'min' ||
parse[1] === 'max') {
if (parse[2] === undefined) {
console.log('Invalid format of format string: ' + format);
parse[2] = null;
} else {
parse[2] = (parse[2] || '').trim().replace(',', '.');
parse[2] = parse[2].substring(1, parse[2].length - 1);
parse[2] = parseFloat(parse[2].trim());
if (parse[2].toString() === 'NaN') {
console.log('Invalid format of format string: ' + format);
parse[2] = null;
} else {
operations = operations || [];
operations.push({op: parse[1], arg: parse[2]});
}
}
} else
// date formatting
if (parse[1] === 'date') {
operations = operations || [];
parse[2] = (parse[2] || '').trim();
parse[2] = parse[2].substring(1, parse[2].length - 1);
operations.push({op: parse[1], arg: parse[2]});
} else
// returns array[value]. e.g.: {id.ack;array(ack is false,ack is true)}
if (parse[1] === 'array') {
operations = operations || [];
param = (parse[2] || '').trim();
param = param.substring(1, param.length - 1);
param = param.split(',');
if (Array.isArray(param)) {
operations.push ({op: parse[1], arg: param}); //xxx
}
} else
// value formatting
if (parse[1] === 'value') {
operations = operations || [];
var param = (parse[2] === undefined) ? '(2)' : (parse[2] || '');
param = param.trim();
param = param.substring(1, param.length - 1);
operations.push({op: parse[1], arg: param});
} else
// operators have optional parameter
if (parse[1] === 'pow' || parse[1] === 'round' || parse[1] === 'random') {
if (parse[2] === undefined) {
operations = operations || [];
operations.push({op: parse[1]});
} else {
parse[2] = (parse[2] || '').trim().replace(',', '.');
parse[2] = parse[2].substring(1, parse[2].length - 1);
parse[2] = parseFloat(parse[2].trim());
if (parse[2].toString() === 'NaN') {
console.log('Invalid format of format string: ' + format);
parse[2] = null;
} else {
operations = operations || [];
operations.push({op: parse[1], arg: parse[2]});
}
}
} else
// operators without parameter
{
operations = operations || [];
operations.push({op: parse[1]});
}
} else {
console.log('Invalid format ' + format);
}
}
}
result.push({
visOid: visOid,
systemOid: systemOid,
token: oid[p],
operations: operations ? operations : undefined,
format: format,
isSeconds: isSeconds
});
}
}
return result;
}
function getUsedObjectIDs(views, isByViews) {
if (!views) {
console.log('Check why views are not yet loaded!');
return null;
}
var _views = isByViews ? {} : null;
var IDs = [];
var visibility = {};
var bindings = {};
var lastChanges = {};
var signals = {};
var view;
var id;
var sidd;
for (view in views) {
if (!views.hasOwnProperty(view)) continue;
if (view === '___settings') continue;
if (_views) _views[view] = [];
for (id in views[view].widgets) {
if (!views[view].widgets.hasOwnProperty(id)) continue;
// Check all attributes
var data = views[view].widgets[id].data;
var style = views[view].widgets[id].style;
// fix error in naming
if (views[view].widgets[id].groupped) {
views[view].widgets[id].grouped = true;
delete views[view].widgets[id].groupped;
}
// rename hqWidgets => hqwidgets
if (views[view].widgets[id].widgetSet === 'hqWidgets') {
views[view].widgets[id].widgetSet = 'hqwidgets';
}
// rename RGraph => rgraph
if (views[view].widgets[id].widgetSet === 'RGraph') {
views[view].widgets[id].widgetSet = 'rgraph';
}
// rename timeAndWeather => timeandweather
if (views[view].widgets[id].widgetSet === 'timeAndWeather') {
views[view].widgets[id].widgetSet = 'timeandweather';
}
// convert "Show on Value" to HTML
if (views[view].widgets[id].tpl === 'tplShowValue') {
views[view].widgets[id].tpl = 'tplHtml';
views[view].widgets[id].data['visibility-oid'] = views[view].widgets[id].data.oid;
views[view].widgets[id].data['visibility-val'] = views[view].widgets[id].data.value;
delete views[view].widgets[id].data.oid;
delete views[view].widgets[id].data.value;
}
// convert "Hide on >0/True" to HTML
if (views[view].widgets[id].tpl === 'tplHideTrue') {
views[view].widgets[id].tpl = 'tplHtml';
views[view].widgets[id].data['visibility-cond'] = '!=';
views[view].widgets[id].data['visibility-oid'] = views[view].widgets[id].data.oid;
views[view].widgets[id].data['visibility-val'] = true;
delete views[view].widgets[id].data.oid;
}
// convert "Hide on 0/False" to HTML
if (views[view].widgets[id].tpl === 'tplHide') {
views[view].widgets[id].tpl = 'tplHtml';
views[view].widgets[id].data['visibility-cond'] = '!=';
views[view].widgets[id].data['visibility-oid'] = views[view].widgets[id].data.oid;
views[view].widgets[id].data['visibility-val'] = false;
delete views[view].widgets[id].data.oid;
}
// convert "Door/Window sensor" to HTML
if (views[view].widgets[id].tpl === 'tplHmWindow') {
views[view].widgets[id].tpl = 'tplValueBool';
views[view].widgets[id].data.html_false = views[view].widgets[id].data.html_closed;
views[view].widgets[id].data.html_true = views[view].widgets[id].data.html_open;
delete views[view].widgets[id].data.html_closed;
delete views[view].widgets[id].data.html_open;
}
// convert "Door/Window sensor" to HTML
if (views[view].widgets[id].tpl === 'tplHmWindowRotary') {
views[view].widgets[id].tpl = 'tplValueListHtml8';
views[view].widgets[id].data.count = 2;
views[view].widgets[id].data.value0 = views[view].widgets[id].data.html_closed;
views[view].widgets[id].data.value1 = views[view].widgets[id].data.html_open;
views[view].widgets[id].data.value2 = views[view].widgets[id].data.html_tilt;
delete views[view].widgets[id].data.html_closed;
delete views[view].widgets[id].data.html_open;
delete views[view].widgets[id].data.html_tilt;
}
// convert "tplBulbOnOff" to tplBulbOnOffCtrl
if (views[view].widgets[id].tpl === 'tplBulbOnOff') {
views[view].widgets[id].tpl = 'tplBulbOnOffCtrl';
views[view].widgets[id].data.readOnly = true;
}
// convert "tplValueFloatBarVertical" to tplValueFloatBar
if (views[view].widgets[id].tpl === 'tplValueFloatBarVertical') {
views[view].widgets[id].tpl = 'tplValueFloatBar';
views[view].widgets[id].data.orientation = 'vertical';
}
for (var attr in data) {
if (!data.hasOwnProperty(attr) || !attr) continue;
/* TODO DO do not forget remove it after a while. Required for import from DashUI */
if (attr === 'state_id') {
data.state_oid = data[attr];
delete data[attr];
attr = 'state_oid';
} else
if (attr === 'number_id') {
data.number_oid = data[attr];
delete data[attr];
attr = 'number_oid';
} else
if (attr === 'toggle_id') {
data.toggle_oid = data[attr];
delete data[attr];
attr = 'toggle_oid';
} else
if (attr === 'set_id') {
data.set_oid = data[attr];
delete data[attr];
attr = 'set_oid';
} else
if (attr === 'temp_id') {
data.temp_oid = data[attr];
delete data[attr];
attr = 'temp_oid';
} else
if (attr === 'drive_id') {
data.drive_oid = data[attr];
delete data[attr];
attr = 'drive_oid';
} else
if (attr === 'content_id') {
data.content_oid = data[attr];
delete data[attr];
attr = 'content_oid';
} else
if (attr === 'dialog_id') {
data.dialog_oid = data[attr];
delete data[attr];
attr = 'dialog_oid';
} else
if (attr === 'max_value_id') {
data.max_value_oid = data[attr];
delete data[attr];
attr = 'max_value_oid';
} else
if (attr === 'dialog_id') {
data.dialog_oid = data[attr];
delete data[attr];
attr = 'dialog_oid';
} else
if (attr === 'weoid') {
data.woeid = data[attr];
delete data[attr];
attr = 'woeid';
}
if (typeof data[attr] === 'string') {
var m;
var oids = extractBinding(data[attr]);
if (oids) {
for (var t = 0; t < oids.length; t++) {
var ssid = oids[t].systemOid;
if (ssid) {
if (IDs.indexOf(ssid) === -1) IDs.push(ssid);
if (_views && _views[view].indexOf(ssid) === -1) _views[view].push(ssid);
if (!bindings[ssid]) bindings[ssid] = [];
oids[t].type = 'data';
oids[t].attr = attr;
oids[t].view = view;
oids[t].widget = id;
bindings[ssid].push(oids[t]);
}
if (oids[t].operations && oids[t].operations[0].arg instanceof Array) {
for (var ww = 0; ww < oids[t].operations[0].arg.length; ww++) {
ssid = oids[t].operations[0].arg[ww].systemOid;
if (!ssid) continue;
if (IDs.indexOf(ssid) === -1) IDs.push(ssid);
if (_views && _views[view].indexOf(ssid) === -1) _views[view].push(ssid);
if (!bindings[ssid]) bindings[ssid] = [];
bindings[ssid].push(oids[t]);
}
}
}
} else
if (attr !== 'oidTrueValue' && attr !== 'oidFalseValue' && ((attr.match(/oid\d{0,2}$/) || attr.match(/^oid/) || attr.match(/^signals-oid-/) || attr === 'lc-oid') && data[attr])) {
if (data[attr] && data[attr] !== 'nothing_selected') {
if (IDs.indexOf(data[attr]) === -1) IDs.push(data[attr]);
if (_views && _views[view].indexOf(data[attr]) === -1) _views[view].push(data[attr]);
}
// Visibility binding
if (attr === 'visibility-oid' && data['visibility-oid']) {
var vid = data['visibility-oid'];
if (vid.match(/^groupAttr(\d+)$/)) {
var vgroup = getWidgetGroup(views, view, id);
if (vgroup) vid = views[view].widgets[vgroup].data[vid];
}
if (!visibility[vid]) visibility[vid] = [];
visibility[vid].push({view: view, widget: id});
}
// Signal binding
if (attr.match(/^signals-oid-/) && data[attr]) {
var sid = data[attr];
if (sid.match(/^groupAttr(\d+)$/)) {
var group = getWidgetGroup(views, view, id);
if (group) sid = views[view].widgets[group].data[sid];
}
if (!signals[sid]) signals[sid] = [];
signals[sid].push({
view: view,
widget: id,
index: parseInt(attr.substring('signals-oid-'.length), 10)
});
}
if (attr === 'lc-oid') {
var lcsid = data[attr];
if (lcsid.match(/^groupAttr(\d+)$/)) {
var ggroup = getWidgetGroup(views, view, id);
if (ggroup) lcsid = views[view].widgets[ggroup].data[lcsid];
}
if (!lastChanges[lcsid]) lastChanges[lcsid] = [];
lastChanges[lcsid].push({
view: view,
widget: id
});
}
} else
if ((m = attr.match(/^attrType(\d+)$/)) && data[attr] === 'id') {
var _id = 'groupAttr' + m[1];
if (data[_id]) {
if (IDs.indexOf(data[_id]) === -1) IDs.push(data[_id]);
if (_views && _views[view].indexOf(data[_id]) === -1) _views[view].push(data[_id]);
}
}
}
}
// build bindings for styles
if (style) {
for (var cssAttr in style) {
if (!style.hasOwnProperty(cssAttr) || !cssAttr) continue;
if (typeof style[cssAttr] === 'string') {
var objIDs = extractBinding(style[cssAttr]);
if (objIDs) {
for (var tt = 0; tt < objIDs.length; tt++) {
sidd = objIDs[tt].systemOid;
if (sidd) {
if (IDs.indexOf(sidd) === -1) IDs.push(sidd);
if (_views && _views[view].indexOf(sidd) === -1) _views[view].push(sidd);
if (!bindings[sidd]) bindings[sidd] = [];
objIDs[tt].type = 'style';
objIDs[tt].attr = cssAttr;
objIDs[tt].view = view;
objIDs[tt].widget = id;
bindings[sidd].push(objIDs[tt]);
}
if (objIDs[tt].operations && objIDs[tt].operations[0].arg instanceof Array) {
for (var w = 0; w < objIDs[tt].operations[0].arg.length; w++) {
sidd = objIDs[tt].operations[0].arg[w].systemOid;
if (!sidd) continue;
if (IDs.indexOf(sidd) === -1) IDs.push(sidd);
if (_views && _views[view].indexOf(sidd) === -1) _views[view].push(sidd);
if (!bindings[sidd]) bindings[sidd] = [];
bindings[sidd].push(objIDs[tt]);
}
}
}
}
}
}
}
}
}
if (_views) {
var changed;
do {
changed = false;
// Check containers
for (view in views) {
if (!views.hasOwnProperty(view)) continue;
if (view === '___settings') continue;
for (id in views[view].widgets) {
if (!views[view].widgets.hasOwnProperty(id)) continue;
// Add all OIDs from this view to parent
if (views[view].widgets[id].tpl === 'tplContainerView' && views[view].widgets[id].data.contains_view) {
var ids = _views[views[view].widgets[id].data.contains_view];
if (ids) {
for (var a = 0; a < ids.length; a++) {
if (ids[a] && _views[view].indexOf(ids[a]) === -1) {
_views[view].push(ids[a]);
changed = true;
}
}
} else {
console.warn('View does not exist: "' + views[view].widgets[id].data.contains_view + '"');
}
}
}
}
} while (changed);
}
return {IDs: IDs, byViews: _views, visibility: visibility, bindings: bindings, lastChanges: lastChanges, signals: signals};
}
if (module && module.parent) {
module.exports.getUsedObjectIDs = getUsedObjectIDs;
}

366
lib/zipFiles.js Normal file
View File

@@ -0,0 +1,366 @@
'use strict';
var JSZip;
var tools = require(__dirname + '/tools');
function _getAllFilesInDir(objects, id, name, options, callback, result) {
result = result || [];
objects.readDir(id, name, options, function (err, files) {
var count = 0;
var errors = [];
if (files) {
for (var f = 0; f < files.length; f++) {
if (files[f].isDir) {
count++;
_getAllFilesInDir(objects, id, name + '/' + files[f].file, options, function (err, _result) {
if (err) errors.push(err);
if (!--count) callback(errors.length ? errors : null, _result);
}, result);
} else {
result.push(name + '/' + files[f].file);
}
}
}
if (!count) callback(null, result);
});
}
function _addFile(objects, id, name, options, zip, files, callback) {
objects.readFile(id, name, options, function (err, data, mime) {
if (!zip) {
console.log(err);
callback('Cannot read file "' + name + '": ' + err, files);
} else {
// if handler installed
if (options.stringify) {
try {
data = options.stringify(name, data, options ? options.settings : null, files);
} catch (error) {
console.error('Cannot stringify file "' + name + '": ' + error);
if (!err) err = 'Cannot stringify file "' + name + '": ' + error;
}
}
var parts = name.split('/');
if (parts.length > 1) {
parts.shift();
name = parts.join('/');
}
zip.file(name, data);
setImmediate(function () {
callback(err, files);
});
}
});
}
// pack all files as zip and send it back
function readDirAsZip(objects, id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (name[0] === '/') name = name.substring(1);
options = options || {};
var adapter = id;
if (adapter.indexOf('.') !== -1) {
adapter = id.split('.')[0];
}
// try to load processor of adapter
try {
options.stringify = require(tools.appName + '.' + adapter + '/lib/convert.js').stringify;
} catch (e) {
}
_getAllFilesInDir(objects, id, name, options, function (err, files) {
var count = 0;
if (files) {
JSZip = JSZip || require('jszip');
var zip = new JSZip();
var additional = [];
for (var f = 0; f < files.length; f++) {
count++;
_addFile(objects, id, files[f], options, zip, additional, function (err, additional) {
if (!--count) {
if (additional && additional.length) {
for (var ff = 0; ff < additional.length; ff++) {
if (!additional[ff] || typeof additional[ff] !== 'string') continue;
count++;
if (additional[ff][0] === '/') additional[ff] = additional[ff].substring(1);
var parts = additional[ff].split('/');
var adapter = parts.shift();
_addFile(objects, adapter, parts.join('/'), options, zip, null, function (err) {
if (!--count) {
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
zip = null;
}
});
}
if (!count) {
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
zip = null;
}
} else {
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
zip = null;
}
}
});
}
}
if (!count) {
callback(err, null);
}
});
}
function _checkDir(objects, id, root, parts, options, callback) {
if (!parts || !parts.length) {
callback();
return;
}
root += '/' + parts.shift();
objects.readDir(id, root, options, function (err, files) {
if (err === 'Not exists') {
objects.mkdir(id, root, options, function (err) {
_checkDir(objects, id, root, parts, options, callback);
});
} else {
_checkDir(objects, id, root, parts, options, callback);
}
});
}
function _writeOneFile(objects, zip, id, name, filename, options, callback) {
zip.files[filename].async('nodebuffer').then(function (data) {
var _err;
if (options.parse) {
try {
data = options.parse(name, filename, data, options ? options.settings : null);
} catch (e) {
_err = e;
}
}
var fName = name + filename;
var parts = fName.split('/');
parts.pop();
_checkDir(objects, id, '', parts, options, function () {
objects.writeFile(id, name + filename, data, options, function (err) {
callback(_err || err);
});
});
}, function (err) {
callback(err);
});
}
function writeDirAsZip(objects, id, name, data, options, callback) {
JSZip = JSZip || require('jszip');
var zip = new JSZip();
options = options || {};
var adapter = id;
if (adapter.indexOf('.') !== -1) {
adapter = id.split('.')[0];
}
// try to load processor of adapter
try {
options.parse = require(tools.appName + '.' + adapter + '/lib/convert.js').parse;
} catch (e) {
}
try {
zip.loadAsync(data).then(function () {
var count = 0;
var error = [];
if (name[name.length - 1] !== '/') name += '/';
for (var filename in zip.files) {
if (!filename || filename[filename.length - 1] === '/') continue;
count++;
try {
_writeOneFile(objects, zip, id, name, filename, options, function (err) {
if (err) {
error.push('Cannot write file "' + filename + '":' + err.toString());
}
if (!--count && callback) {
callback(error.length ? error.join(', ') : null);
callback = null;
}
});
} catch (error) {
if (callback) {
callback(error.toString());
callback = null;
}
}
}
}, function (error) {
if (callback) {
callback(error.toString());
callback = null;
}
});
} catch (error) {
if (callback) {
callback(error.toString());
callback = null;
}
}
}
// pack all files as zip and send it back
function readObjectsAsZip(objects, rootId, adapter, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (adapter) {
// try to load processor of adapter
try {
options.stringify = require(tools.appName + '.' + adapter + '/lib/convert.js').stringify;
} catch (e) {
}
}
objects.getConfigKeys(rootId + '.*', options, function (err, keys) {
objects.getObjects(keys, options, function(err, objs) {
JSZip = JSZip || require('jszip');
var zip = new JSZip();
for (var f = 0; f < objs.length; f++) {
var data = {id: keys[f], data: objs[f]};
if (options.stringify) {
try {
data = options.stringify(data, options ? options.settings : null);
} catch (e) {
data.id = keys[f].replace(/\./g, '/').substring(rootId.length + 1) + '.json';
}
} else {
data.id = keys[f].replace(/\./g, '/').substring(rootId.length + 1) + '.json';
}
if (typeof data.data === 'object') data.data = JSON.stringify(data.data, null, 2);
zip.file(data.id, data.data);
}
zip.generateAsync({type: 'base64'})
.then(function (base64) {
callback(err, base64);
}, function (err) {
callback(err);
});
});
});
}
function _writeOneObject(objects, zip, rootId, filename, options, callback) {
zip.files[filename].async('nodebuffer').then(function (data) {
data = {data: data.toString(), id: filename};
if (options.parse){
try {
data = options.parse(data, options ? options.settings : null);
} catch (e) {
callback('Cannot custom parse "' + data.id + '": ' + e);
return;
}
} else {
data.id = (rootId ? (rootId + '.') : '') + data.id.replace(/\//g, '.').replace(/\.json$/, '');
}
if (typeof data.data !== 'object') {
try {
data.data = JSON.parse(data.data);
} catch (e) {
callback('Cannot parse "' + data.id + '": ' + e);
return;
}
}
if (data && data.id && data.data) {
options.ts = new Date().getTime();
options.from = 'system.host.' + tools.getHostName() + '.cli';
objects.setObject(data.id, data.data, options, function (err) {
callback(err);
});
} else {
if (data && data.error) {
callback(data.error);
} else {
callback();
}
}
}, function (err) {
callback('Cannot parse unzip: ' + err);
});
}
function writeObjectsAsZip(objects, rootId, adapter, data, options, callback) {
JSZip = JSZip || require('jszip');
options = options || {};
if (adapter) {
// try to load processor of adapter
try {
options.parse = require(tools.appName + '.' + adapter + '/lib/convert.js').parse;
} catch (e) {
}
}
var zip = new JSZip();
try {
zip.loadAsync(data).then(function () {
var count = 0;
var error = [];
for (var filename in zip.files) {
if (filename[filename.length - 1] === '/') continue;
count++;
_writeOneObject(objects, zip, rootId, filename, options, function (err) {
if (err) error.push(err.toString());
if (!--count && callback) {
callback(error.length ? error.join(', ') : null);
callback = null;
}
});
}
}, function (error) {
if (callback) {
callback(error.toString());
callback = null;
}
});
} catch (error) {
if (callback) {
callback(error.toString());
callback = null;
}
}
}
module.exports.readDirAsZip = readDirAsZip;
module.exports.writeDirAsZip = writeDirAsZip;
module.exports.readObjectsAsZip = readObjectsAsZip;
module.exports.writeObjectsAsZip = writeObjectsAsZip;