ucoind 14 KB
Newer Older
1
#!/usr/bin/env node
Cédric Moreau's avatar
Cédric Moreau committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
var fs          = require('fs');
var os          = require('os');
var async       = require('async');
var _           = require('underscore');
var program     = require('commander');
var mongoose    = require('mongoose');
var moment      = require('moment');
var inquirer    = require('inquirer');
var openpgp     = require('openpgp');
var jpgp        = require('../app/lib/jpgp');
var wizard      = require('../app/lib/wizard');
var router      = require('../app/lib/streams/router');
var multicaster = require('../app/lib/streams/multicaster');
var logger      = require('../app/lib/logger')('ucoind');
var ucoin       = require('./..');
17

Cédric Moreau's avatar
Cédric Moreau committed
18 19 20 21
function keys (val) {
  return val.split(',');
}

22 23 24 25
// Constants
var LISTEN_HTTP = true;
var DO_NOT_LISTEN_HTTP = false;

26
program
Cédric Moreau's avatar
v0.6.0  
Cédric Moreau committed
27
  .version('0.6.0')
28
  .usage('<command> [options]')
29
  .option('-p, --port <port>', 'Port to listen for requests', parseInt)
30
  .option('-c, --currency <name>', 'Name of the currency managed by this node.')
Cédric Moreau's avatar
Cédric Moreau committed
31 32
  .option('--mhost <host>', 'MongoDB host.')
  .option('--mport <port>', 'MongoDB port.')
33
  .option('-d, --mdb <name>', 'MongoDB database name (defaults to "ucoin_default").')
34 35
  .option('--pgpkey <keyPath>', 'Path to the private key used for signing HTTP responses.')
  .option('--pgppasswd <password>', 'Password for the key provided with --httpgp-key option.')
Cédric Moreau's avatar
Cédric Moreau committed
36 37
  .option('--ipv4 <address>', 'IPV4 interface to listen for requests')
  .option('--ipv6 <address>', 'IPV6 interface to listen for requests')
Cédric Moreau's avatar
Cédric Moreau committed
38
  .option('--remoteh <host>', 'Remote interface others may use to contact this node')
Cédric Moreau's avatar
Cédric Moreau committed
39 40
  .option('--remote4 <host>', 'Remote interface for IPv4 access')
  .option('--remote6 <host>', 'Remote interface for IPv6 access')
Cédric Moreau's avatar
Cédric Moreau committed
41
  .option('--remotep <port>', 'Remote port others may use to contact this node')
42
  .option('--kmanagement <ALL|KEYS>', 'Define key management policy')
Cédric Moreau's avatar
Cédric Moreau committed
43
  .option('--kaccept <ALL|KEYS>', 'Define key acceptance policy')
44
  .option('--amdaemon <ON|OFF>', 'ucoin is launched with a specific daemon helping to get other peers\' votes')
45
  .option('--amstart <timestamp>', 'First amendment generated date', parseMoment)
46 47 48 49 50 51
  .option('--amfreq <timestamp>', 'Amendments frequency, in seconds', parseInt)
  .option('--udfreq <timestamp>', 'Universal Dividend frequency, in seconds', parseInt)
  .option('--ud0 <integer>', 'First Universal Dividend value (a.k.a \'UD0\')', parseInt)
  .option('--udpercent <float>', 'Percent of monetary mass growth per UD', parseFloat)
  .option('--consensus <float>', 'Percent of voters required to accept an amendment', parseFloat)
  .option('--msvalidity <timestamp>', 'Duration of a valid membership, in seconds', parseInt)
52
  .option('--vtvalidity <timestamp>', 'Duration of a valid voter, in seconds', parseInt)
53
  .option('--openpgpjs', 'Prefer using embedded Openpgpjs implementation for signing requests')
54
  .option('--algorithm <AnyKey:1Sig>', 'Algorithms to use for community changes. Use \':\' to give multiple values', splitColon)
55
  ;
Cédric Moreau's avatar
Cédric Moreau committed
56

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
program
  .command('send-pubkey [host] [port]')
  .description('Send self pubkey to given node')
  .action(function (host, port) {
    // Only show message "Saved"
    require('log4js').configure({
      "appenders": [{
        category: "ucoind",
        type: "console"
      }]
    });
    connect(DO_NOT_LISTEN_HTTP, ucoin.createPeerServer, function (server, conf) {
      // Only show message "Saved"
      require('log4js').configure({
        "appenders": [{
          category: "multicaster",
          type: "console"
        }]
      });
      async.waterfall([
        function (next){
          server.on('peerInited', next);
        },
        function (next){
          server.PublicKeyService.getTheOne(server.PeeringService.cert.fingerprint, next);
        },
        function (pubkey, next){
          var Peer = server.conn.model('Peer');
          var peer = new Peer({
            endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
          });
          multicaster().sendPubkey(peer, pubkey, next);
        },
      ], function (err, result) {
        if (err) logger.error(err);
        server.disconnect();
        process.exit();
      });
    })(null);
  });

98 99 100
program
  .command('wizard [step]')
  .description('Launch the configuration Wizard')
101 102 103 104 105 106 107
  .action(function (step) {
    // Only show message "Saved"
    require('log4js').configure({
      "appenders": [{
        category: "ucoind",
        type: "console"
      }]
108
    });
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    connect(DO_NOT_LISTEN_HTTP, ucoin.createRegistryServer, function (step, server, conf) {
      var wiz = wizard(server);
      var task = {
        'currency': wiz.configCurrency,
        'openpgp': wiz.configOpenpgp,
        'network': wiz.configNetwork,
        'key': wiz.configKey,
        'autovote': wiz.configAutovote
      };
      var wizDo = task[step] || wiz.configAll;
      async.waterfall([
        function (next){
          wizDo(conf, next);
        },
        function (next){
          conf.save(function (err) {
            !err && logger.debug("Configuration saved.");
            next(err);
          });
        },
        function (next) {
          server.checkConfig(conf, function (err) {
            logger.warn(err);
          });
          next();
        }
      ], function (err, result) {
        err && logger.error(err);
        server.disconnect();
        process.exit();
      });
    })(step, null);
  });
142

143 144 145
program
  .command('sync [host] [port]')
  .description('Tries to synchronise data with remote uCoin node')
146
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createRegistryServer, function (host, port, server, conf) {
147

148 149
    // Disable daemon
    conf.sync.AMDaemon = "OFF";
150
    conf.createNext = false;
151

152 153 154 155
    async.series([
      function (next){
        // Synchronize
        var Synchroniser = require('../app/lib/sync');
156
        var remote = new Synchroniser(server, host, port, false, conf);
157 158 159 160 161 162
        remote.sync(next);
      },
    ], function (err) {
      if(err){
        logger.error('Error during sync:', err);
      }
163
      server.disconnect();
164
      process.exit();
165 166 167
    });
  }));

168 169 170
program
  .command('allow-key [key]')
  .description('Add given key to authorized keys of this node')
171
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
172 173 174 175 176 177 178 179
    key = key || "";
    key = key.toUpperCase();
    async.waterfall([
      function (next) {
        if (!key.isSha1()) {
          next("Key must match a SHA-1 hash");
          return;
        }
180
        next();
181
      },
182
      function (next){
183
        server.KeyService.setKnown(key, next);
184 185 186
      }
    ], function (err, result) {
      if(err){
187
        logger.error('Error: %s', err);
188 189
        server.disconnect();
        process.exit();
190 191
        return;
      }
192
      logger.debug("Key %s is now allowed to be stored" , key);
193
      server.disconnect();
194
      process.exit();
195 196 197
    });
  }));

198 199 200
program
  .command('manage-key [key]')
  .description('Add given key to stack of managed keys of this node')
201
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
202
    handleKey(server, key, true, 'Key %s is now managed');
203 204 205 206 207
  }));

program
  .command('forget-key [key]')
  .description('Remove given key of the managed keys\' stack of this node')
208
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
209
    handleKey(server, key, false, 'Key %s no more managed from now');
210 211
  }));

212
function handleKey (server, key, isManaged, message) {
213 214 215
  key = key || "";
  key = key.toUpperCase();
  async.waterfall([
216
    function (next){
217 218 219 220
      server.initServices(next);
    },
    function (next){
      server.KeyService.handleKey(key, isManaged, next);
221 222 223
    }
  ], function (err, result) {
    if(err){
224
      logger.error('Error: %s', err);
225
      server.disconnect();
226 227
      return;
    }
228
    logger.debug(message , key);
229
    server.disconnect();
230
    process.exit();
231 232 233
  });
}

234 235 236
program
  .command('check-config')
  .description('Checks the node\'s configuration')
237 238 239 240 241 242 243 244
  .action(connect(function (server, conf) {
    server.checkConfig(function (err) {
      if (err)
        logger.warn(err);
      else
        logger.warn('Configuration seems correct.');
      server.disconnect();
      process.exit();
245 246 247 248
    });
    return;
  }));

249 250 251
program
  .command('config')
  .description('Register configuration in database')
252
  .action(connect(false, function (server, conf) {
253
    conf.save(function (err) {
254
      if(err){
255
        logger.error("Configuration could not be saved: " + err);
256 257
      }
      else{
258
        logger.debug("Configuration saved.");
259
      }
260
      server.disconnect();
261
      process.exit();
262 263 264
      return;
    });
  }));
265

266 267 268
program
  .command('reset [config|data]')
  .description('Reset configuration or data in database')
269
  .action(connect(function (type, server, conf) {
270
    if(!~['config', 'data'].indexOf(type)){
271
      logger.error('Bad command: usage `reset config` or `reset data`');
272
      server.disconnect();
273 274 275
      return;
    }
    if(type == 'data'){
276
      server.reset(function (err) {
277
        if(err)
278
          logger.error(err);
279 280 281
        else
          logger.warn('Data successfuly reseted.');
        server.disconnect();
282
        process.exit();
283 284 285
      });
    }
    if(type == 'config'){
286
      server.resetConf(function (err) {
287
        if(err)
288
          logger.error(err);
289 290 291
        else
          logger.warn('Configuration successfuly reseted.');
        server.disconnect();
292
        process.exit();
293 294 295 296
      });
    }
  }));

297 298 299
program
  .command('start')
  .description('Start uCoin server using given --currency')
300
  .action(connect(LISTEN_HTTP, ucoin.createRegistryServer, function (server, conf) {
301

Cédric Moreau's avatar
Cédric Moreau committed
302 303 304 305
    server
      .pipe(router(server.PeeringService.cert.fingerprint, server.conn))
      .pipe(multicaster());

306
    // Launching server
307
    server.on('BMALoaded', function (err, app) {
308
      if(err){
309
        console.error(err);
310
        this.disconnect();
311 312 313
        process.exit();
        return;
      }
314
      logger.debug('Server ready!');
315 316 317
    });
  }));

318
function overrideConf(conf) {
319

320 321
  conf.sync = conf.sync || {};
  var cli = {
322
    currency: program.currency,
323 324 325 326 327 328 329 330 331 332
    server: {
      port: program.port,
      ipv4address: program.ipv4,
      ipv6address: program.ipv6,
      pgp: {
        key: program.pgpkey,
        password: program.pgppasswd
      },
      remote: {
        host: program.remoteh,
333 334
        ipv4: program.remote4,
        ipv6: program.remote6,
335
        port: program.remotep
336 337
      },
      openpgpjs: program.openpgpjs
338 339 340 341 342
    },
    db: {
      host: program.mhost,
      port: program.mport,
      database: program.mdb,
343
    },
344 345
    policy: {
      keys: program.kmanagement,
Cédric Moreau's avatar
Cédric Moreau committed
346
      pubkeys: program.kaccept
347 348
    },
    sync: {
349
      AMDaemon: program.amdaemon,
350 351 352
      AMStart: program.amstart,
      AMFreq: program.amfreq,
      UDFreq: program.udfreq,
353 354
      UD0: program.ud0,
      UDPercent: program.udpercent,
355
      Consensus: program.consensus,
356
      Algorithm: program.algorithm
357 358
    }
  };
359

360
  if(cli.server.pgp.key) cli.server.pgp.key = fs.readFileSync(cli.server.pgp.key, 'utf8');
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383

  // Update conf
  if (cli.currency)                         conf.currency       = cli.currency;
  if (cli.server.ipv4address)               conf.ipv4           = cli.server.ipv4address;
  if (cli.server.ipv6address)               conf.ipv6           = cli.server.ipv6address;
  if (cli.server.port)                      conf.port           = cli.server.port;
  if (cli.server.openpgpjs != undefined)    conf.openpgpjs      = cli.server.openpgpjs;
  if (cli.server.pgp.key)                   conf.pgpkey         = cli.server.pgp.key;
  if (cli.server.pgp.password != undefined) conf.pgppasswd      = cli.server.pgp.password;
  if (cli.server.remote.host != undefined)  conf.remotehost     = cli.server.remote.host;
  if (cli.server.remote.ipv4 != undefined)  conf.remoteipv4     = cli.server.remote.ipv4;
  if (cli.server.remote.ipv6 != undefined)  conf.remoteipv6     = cli.server.remote.ipv6;
  if (cli.server.remote.port != undefined)  conf.remoteport     = cli.server.remote.port;
  if (cli.policy.keys)                      conf.kmanagement    = cli.policy.keys;
  if (cli.policy.pubkeys)                   conf.kaccept        = cli.policy.pubkeys;
  if (cli.sync.AMDaemon)                    conf.sync.AMDaemon  = cli.sync.AMDaemon;
  if (cli.sync.AMStart)                     conf.sync.AMStart   = cli.sync.AMStart;
  if (cli.sync.AMFreq)                      conf.sync.AMFreq    = cli.sync.AMFreq;
  if (cli.sync.UDFreq)                      conf.sync.UDFreq    = cli.sync.UDFreq;
  if (cli.sync.UD0)                         conf.sync.UD0       = cli.sync.UD0;
  if (cli.sync.UDPercent)                   conf.sync.UDPercent = cli.sync.UDPercent;
  if (cli.sync.Consensus)                   conf.sync.Consensus = cli.sync.Consensus;
  if (cli.sync.Algorithm)                   conf.sync.Algorithm = cli.sync.Algorithm;
384 385 386

  // Specific internal settings
  conf.createNext = true;
387 388
  return conf;
}
389

390
function connect(listenHTTP, serverFactory, callback) {
391
  if (arguments.length == 1) {
392 393 394 395
    callback = listenHTTP;
    listenHTTP = false;
    serverFactory = ucoin.createHDCServer;
  } else if (arguments.length == 2) {
396
    callback = serverFactory;
397
    serverFactory = ucoin.createHDCServer;
398
  }
399 400
  return function () {
    var cbArgs = arguments;
401
    var dbName = program.mdb || "ucoin_default";
402

403
    var server = serverFactory({ name: dbName, host: program.mhost, port: program.mport, listenBMA: listenHTTP }, overrideConf({}));
404

405
    // Connecting to DB
406
    server.on('services', function (err) {
407 408

      if(err){
409 410 411
        logger.warn(err);
        server.disconnect();
        process.exit(1);
412 413
        return;
      }
414

415
      cbArgs.length--;
416 417
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
418 419 420 421
      callback.apply(this, cbArgs);
    });
  };
}
422

423 424 425
function parseMoment (d) {
  if (d.toLowerCase() == 'now') {
    return parseInt(moment().format("X"));
426
  } else if (d.match(/^\d{2}-\d{2}-\d{4}$/)) {
427
    return parseInt(moment(d, "DD-MM-YYYY").format("X"));
428 429
  } else if (d.match(/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$/)) {
    return parseInt(moment(d, "DD-MM-YYYY hh:mm:ss").format("X"));
430 431 432 433 434
  } else {
    return parseInt(d);
  }
}

435 436 437 438
function splitColon (str) {
  return str.split(':');
}

439
program.parse(process.argv);