ucoind 12.3 KB
Newer Older
1
#!/usr/bin/env node
2
var jpgp     = require('../app/lib/jpgp');
3
var wizard   = require('../app/lib/wizard');
4
var fs       = require('fs');
5
var os       = require('os');
6 7
var async    = require('async');
var _        = require('underscore');
8 9
var program  = require('commander');
var mongoose = require('mongoose');
10
var moment   = require('moment');
11 12
var inquirer = require('inquirer');
var openpgp  = require('openpgp');
13
var logger   = require('../app/lib/logger')('ucoind');
14
var ucoin    = require('./..');
15

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

20 21 22 23
// Constants
var LISTEN_HTTP = true;
var DO_NOT_LISTEN_HTTP = false;

24
program
Cédric Moreau's avatar
v0.5.6  
Cédric Moreau committed
25
  .version('0.5.6')
26
  .usage('<command> [options]')
27
  .option('-p, --port <port>', 'Port to listen for requests', parseInt)
28
  .option('-c, --currency <name>', 'Name of the currency managed by this node.')
Cédric Moreau's avatar
Cédric Moreau committed
29 30
  .option('--mhost <host>', 'MongoDB host.')
  .option('--mport <port>', 'MongoDB port.')
31
  .option('-d, --mdb <name>', 'MongoDB database name (defaults to "ucoin_default").')
32 33
  .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
34 35
  .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
36
  .option('--remoteh <host>', 'Remote interface others may use to contact this node')
Cédric Moreau's avatar
Cédric Moreau committed
37 38
  .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
39
  .option('--remotep <port>', 'Remote port others may use to contact this node')
40
  .option('--kmanagement <ALL|KEYS>', 'Define key management policy')
Cédric Moreau's avatar
Cédric Moreau committed
41
  .option('--kaccept <ALL|KEYS>', 'Define key acceptance policy')
42
  .option('--amdaemon <ON|OFF>', 'ucoin is launched with a specific daemon helping to get other peers\' votes')
43
  .option('--amstart <timestamp>', 'First amendment generated date', parseMoment)
44 45 46 47 48 49
  .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)
50
  .option('--vtvalidity <timestamp>', 'Duration of a valid voter, in seconds', parseInt)
51
  .option('--openpgpjs', 'Prefer using embedded Openpgpjs implementation for signing requests')
52
  .option('--algorithm <AnyKey|1Sig>', 'Algorithm to use for membership')
53
  ;
Cédric Moreau's avatar
Cédric Moreau committed
54

55 56 57
program
  .command('wizard [step]')
  .description('Launch the configuration Wizard')
58 59 60 61 62 63 64
  .action(function (step) {
    // Only show message "Saved"
    require('log4js').configure({
      "appenders": [{
        category: "ucoind",
        type: "console"
      }]
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 98
    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);
  });
99

100 101 102
program
  .command('sync [host] [port]')
  .description('Tries to synchronise data with remote uCoin node')
103
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createRegistryServer, function (host, port, server, conf) {
104

105 106
    // Disable daemon
    conf.sync.AMDaemon = "OFF";
107
    conf.createNext = false;
108

109 110 111 112
    async.series([
      function (next){
        // Synchronize
        var Synchroniser = require('../app/lib/sync');
113
        var remote = new Synchroniser(server, host, port, false, conf);
114 115 116 117 118 119
        remote.sync(next);
      },
    ], function (err) {
      if(err){
        logger.error('Error during sync:', err);
      }
120
      server.disconnect();
121
      process.exit();
122 123 124
    });
  }));

125 126 127
program
  .command('allow-key [key]')
  .description('Add given key to authorized keys of this node')
128
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
129 130 131 132 133 134 135 136
    key = key || "";
    key = key.toUpperCase();
    async.waterfall([
      function (next) {
        if (!key.isSha1()) {
          next("Key must match a SHA-1 hash");
          return;
        }
137
        next();
138
      },
139
      function (next){
140
        server.KeyService.setKnown(key, next);
141 142 143
      }
    ], function (err, result) {
      if(err){
144
        logger.error('Error: %s', err);
145 146
        server.disconnect();
        process.exit();
147 148
        return;
      }
149
      logger.debug("Key %s is now allowed to be stored" , key);
150
      server.disconnect();
151
      process.exit();
152 153 154
    });
  }));

155 156 157
program
  .command('manage-key [key]')
  .description('Add given key to stack of managed keys of this node')
158
  .action(connect(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
159
    handleKey(server, key, true, 'Key %s is now managed');
160 161 162 163 164
  }));

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

169
function handleKey (server, key, isManaged, message) {
170 171 172
  key = key || "";
  key = key.toUpperCase();
  async.waterfall([
173
    function (next){
174 175 176 177
      server.initServices(next);
    },
    function (next){
      server.KeyService.handleKey(key, isManaged, next);
178 179 180
    }
  ], function (err, result) {
    if(err){
181
      logger.error('Error: %s', err);
182
      server.disconnect();
183 184
      return;
    }
185
    logger.debug(message , key);
186
    server.disconnect();
187
    process.exit();
188 189 190
  });
}

191 192 193
program
  .command('check-config')
  .description('Checks the node\'s configuration')
194 195 196 197 198 199 200 201
  .action(connect(function (server, conf) {
    server.checkConfig(function (err) {
      if (err)
        logger.warn(err);
      else
        logger.warn('Configuration seems correct.');
      server.disconnect();
      process.exit();
202 203 204 205
    });
    return;
  }));

206 207 208
program
  .command('config')
  .description('Register configuration in database')
209
  .action(connect(false, function (server, conf) {
210
    conf.save(function (err) {
211
      if(err){
212
        logger.error("Configuration could not be saved: " + err);
213 214
      }
      else{
215
        logger.debug("Configuration saved.");
216
      }
217
      server.disconnect();
218
      process.exit();
219 220 221
      return;
    });
  }));
222

223 224 225
program
  .command('reset [config|data]')
  .description('Reset configuration or data in database')
226
  .action(connect(function (type, server, conf) {
227
    if(!~['config', 'data'].indexOf(type)){
228
      logger.error('Bad command: usage `reset config` or `reset data`');
229
      server.disconnect();
230 231 232
      return;
    }
    if(type == 'data'){
233
      server.reset(function (err) {
234
        if(err)
235
          logger.error(err);
236 237 238
        else
          logger.warn('Data successfuly reseted.');
        server.disconnect();
239
        process.exit();
240 241 242
      });
    }
    if(type == 'config'){
243
      server.resetConf(function (err) {
244
        if(err)
245
          logger.error(err);
246 247 248
        else
          logger.warn('Configuration successfuly reseted.');
        server.disconnect();
249
        process.exit();
250 251 252 253
      });
    }
  }));

254 255 256
program
  .command('start')
  .description('Start uCoin server using given --currency')
257
  .action(connect(LISTEN_HTTP, ucoin.createRegistryServer, function (server, conf) {
258 259

    // Launching server
260
    server.on('BMALoaded', function (err, app) {
261
      if(err){
262
        console.error(err);
263
        this.disconnect();
264 265 266
        process.exit();
        return;
      }
267
      logger.debug('Server ready!');
268 269 270
    });
  }));

271
function overrideConf(conf) {
272

273 274
  conf.sync = conf.sync || {};
  var cli = {
275
    currency: program.currency,
276 277 278 279 280 281 282 283 284 285
    server: {
      port: program.port,
      ipv4address: program.ipv4,
      ipv6address: program.ipv6,
      pgp: {
        key: program.pgpkey,
        password: program.pgppasswd
      },
      remote: {
        host: program.remoteh,
286 287
        ipv4: program.remote4,
        ipv6: program.remote6,
288
        port: program.remotep
289 290
      },
      openpgpjs: program.openpgpjs
291 292 293 294 295
    },
    db: {
      host: program.mhost,
      port: program.mport,
      database: program.mdb,
296
    },
297 298
    policy: {
      keys: program.kmanagement,
Cédric Moreau's avatar
Cédric Moreau committed
299
      pubkeys: program.kaccept
300 301
    },
    sync: {
302
      AMDaemon: program.amdaemon,
303 304 305
      AMStart: program.amstart,
      AMFreq: program.amfreq,
      UDFreq: program.udfreq,
306 307
      UD0: program.ud0,
      UDPercent: program.udpercent,
308
      Consensus: program.consensus,
309
      Algorithm: program.algorithm
310 311
    }
  };
312

313
  if(cli.server.pgp.key) cli.server.pgp.key = fs.readFileSync(cli.server.pgp.key, 'utf8');
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336

  // 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;
337 338 339

  // Specific internal settings
  conf.createNext = true;
340 341
  return conf;
}
342

343
function connect(listenHTTP, serverFactory, callback) {
344
  if (arguments.length == 1) {
345 346 347 348
    callback = listenHTTP;
    listenHTTP = false;
    serverFactory = ucoin.createHDCServer;
  } else if (arguments.length == 2) {
349
    callback = serverFactory;
350
    serverFactory = ucoin.createHDCServer;
351
  }
352 353
  return function () {
    var cbArgs = arguments;
354
    var dbName = program.mdb || "ucoin_default";
355

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

358
    // Connecting to DB
359
    server.on('services', function (err) {
360 361

      if(err){
362 363 364
        logger.warn(err);
        server.disconnect();
        process.exit(1);
365 366
        return;
      }
367

368
      cbArgs.length--;
369 370
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
371 372 373 374
      callback.apply(this, cbArgs);
    });
  };
}
375

376 377 378 379 380 381 382 383 384 385
function parseMoment (d) {
  if (d.toLowerCase() == 'now') {
    return parseInt(moment().format("X"));
  } else if (d.match(/\d{2}-\d{2}-\d{4}/)) {
    return parseInt(moment(d, "DD-MM-YYYY").format("X"));
  } else {
    return parseInt(d);
  }
}

386
program.parse(process.argv);