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

51 52 53
program
  .command('wizard [step]')
  .description('Launch the configuration Wizard')
54 55
  .action(connect(function (step, server, conf) {
    var wiz = wizard(server);
56 57
    var task = {
      'currency': wiz.configCurrency,
58
      'openpgp': wiz.configOpenpgp,
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
      '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) {
75
        server.checkConfig(conf, function (err) {
76 77 78 79 80 81
          logger.warn(err);
        });
        next();
      }
    ], function (err, result) {
      err && logger.error(err);
82
      server.disconnect();
83 84 85 86
      process.exit();
    });
  }));

87 88 89
program
  .command('sync [host] [port]')
  .description('Tries to synchronise data with remote uCoin node')
90
  .action(connect(function (host, port, server, conf) {
91

92 93
    // Disable daemon
    conf.sync.AMDaemon = "OFF";
94
    conf.createNext = false;
95

96 97 98 99
    async.series([
      function (next){
        // Synchronize
        var Synchroniser = require('../app/lib/sync');
100
        var remote = new Synchroniser(server, host, port, false, conf);
101 102 103 104 105 106
        remote.sync(next);
      },
    ], function (err) {
      if(err){
        logger.error('Error during sync:', err);
      }
107
      server.disconnect();
108
      process.exit();
109 110 111
    });
  }));

112 113 114
program
  .command('allow-key [key]')
  .description('Add given key to authorized keys of this node')
115
  .action(connect(ucoin.createHDCServer, function (key, server, conf) {
116 117 118
    key = key || "";
    key = key.toUpperCase();
    async.waterfall([
119 120 121
      function (next){
        server.initServer(next);
      },
122 123 124 125 126
      function (next) {
        if (!key.isSha1()) {
          next("Key must match a SHA-1 hash");
          return;
        }
127
        next();
128
      },
129
      function (next){
130
        server.KeyService.setKnown(key, next);
131 132 133
      }
    ], function (err, result) {
      if(err){
134
        logger.error('Error: %s', err);
135 136
        server.disconnect();
        process.exit();
137 138
        return;
      }
139
      logger.debug("Key %s is now allowed to be stored" , key);
140
      server.disconnect();
141
      process.exit();
142 143 144
    });
  }));

145 146 147
program
  .command('manage-key [key]')
  .description('Add given key to stack of managed keys of this node')
148 149
  .action(connect(ucoin.createHDCServer, function (key, server, conf) {
    handleKey(server, key, true, 'Key %s is now managed');
150 151 152 153 154
  }));

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

159
function handleKey (server, key, isManaged, message) {
160 161 162
  key = key || "";
  key = key.toUpperCase();
  async.waterfall([
163
    function (next){
164 165 166 167
      server.initServices(next);
    },
    function (next){
      server.KeyService.handleKey(key, isManaged, next);
168 169 170
    }
  ], function (err, result) {
    if(err){
171
      logger.error('Error: %s', err);
172
      server.disconnect();
173 174
      return;
    }
175
    logger.debug(message , key);
176
    server.disconnect();
177
    process.exit();
178 179 180
  });
}

181 182 183
program
  .command('check-config')
  .description('Checks the node\'s configuration')
184 185 186 187 188 189 190 191
  .action(connect(function (server, conf) {
    server.checkConfig(function (err) {
      if (err)
        logger.warn(err);
      else
        logger.warn('Configuration seems correct.');
      server.disconnect();
      process.exit();
192 193 194 195
    });
    return;
  }));

196 197 198
program
  .command('config')
  .description('Register configuration in database')
199 200
  .action(connect(function (server, conf) {
    conf.save(function (err) {
201
      if(err){
202
        logger.error("Configuration could not be saved: " + err);
203 204
      }
      else{
205
        logger.debug("Configuration saved.");
206
      }
207
      server.disconnect();
208
      process.exit();
209 210 211
      return;
    });
  }));
212

213 214 215
program
  .command('reset [config|data]')
  .description('Reset configuration or data in database')
216
  .action(connect(function (type, server, conf) {
217
    if(!~['config', 'data'].indexOf(type)){
218
      logger.error('Bad command: usage `reset config` or `reset data`');
219
      server.disconnect();
220 221 222
      return;
    }
    if(type == 'data'){
223
      server.reset(function (err) {
224
        if(err)
225
          logger.error(err);
226 227 228
        else
          logger.warn('Data successfuly reseted.');
        server.disconnect();
229
        process.exit();
230 231 232
      });
    }
    if(type == 'config'){
233
      server.resetConf(function (err) {
234
        if(err)
235
          logger.error(err);
236 237 238
        else
          logger.warn('Configuration successfuly reseted.');
        server.disconnect();
239
        process.exit();
240 241 242 243
      });
    }
  }));

244 245 246
program
  .command('start')
  .description('Start uCoin server using given --currency')
247
  .action(connect(function (server, conf) {
248 249

    // Launching server
250
    server.listenBMA(function (err, app) {
251
      if(err){
252
        console.error(err);
253
        this.disconnect();
254 255 256
        process.exit();
        return;
      }
257
      logger.debug('Server ready!');
258 259 260 261 262
    });
  }));

var config = {};

263
function overrideConf(conn, conf) {
264

265
  // Ensure is not null and have good structure
266
  var Configuration = conn.model('Configuration');
267
  conf = conf || new Configuration();
268

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

  // Update conf
311 312
  if(cli.server.pgp.key) cli.server.pgp.key = fs.readFileSync(cli.server.pgp.key, 'utf8');
  conf.currency    = cli.currency || conf.currency;
313
  conf.openpgpjs   = cli.server.openpgpjs != undefined ? cli.server.openpgpjs : conf.openpgpjs;
314 315 316 317
  conf.ipv4        = cli.server.ipv4address || conf.ipv4;
  conf.ipv6        = cli.server.ipv6address || conf.ipv6;
  conf.port        = cli.server.port || conf.port;
  conf.pgpkey      = cli.server.pgp.key || conf.pgpkey;
318
  conf.pgppasswd   = cli.server.pgp.password != undefined ? cli.server.pgp.password : conf.pgppasswd;
319 320 321 322
  conf.remotehost  = cli.server.remote.host != undefined ? cli.server.remote.host : conf.remotehost;
  conf.remoteipv4  = cli.server.remote.ipv4 != undefined ? cli.server.remote.ipv4 : conf.remoteipv4;
  conf.remoteipv6  = cli.server.remote.ipv6 != undefined ? cli.server.remote.ipv6 : conf.remoteipv6;
  conf.remoteport  = cli.server.remote.port != undefined ? cli.server.remote.port : conf.remoteport;
323 324
  conf.kmanagement = cli.policy.keys || conf.kmanagement;
  conf.kaccept     = cli.policy.pubkeys || conf.kaccept;
325
  conf.sync        = {
326 327 328 329 330 331 332 333
    AMDaemon: cli.sync.AMDaemon || conf.sync.AMDaemon,
    AMStart: cli.sync.AMStart || conf.sync.AMStart,
    AMFreq: cli.sync.AMFreq || conf.sync.AMFreq,
    UDFreq: cli.sync.UDFreq || conf.sync.UDFreq,
    UD0: cli.sync.UD0 || conf.sync.UD0,
    UDPercent: cli.sync.UDPercent || conf.sync.UDPercent,
    Consensus: cli.sync.Consensus || conf.sync.Consensus,
    MSExpires: cli.sync.MSExpires || conf.sync.MSExpires,
334 335
    VTExpires: cli.sync.VTExpires || conf.sync.VTExpires,
    Algorithm: cli.sync.Algorithm || conf.sync.Algorithm
336
  };
337 338 339

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

343 344 345 346 347
function connect(serverFactory, callback) {
  if (arguments.length == 1) {
    callback = serverFactory;
    serverFactory = ucoin.createRegistryServer;
  }
348 349
  return function () {
    var cbArgs = arguments;
350
    var dbName = program.mdb || "ucoin_default";
351

352 353
    var server = serverFactory({ name: dbName, host: program.mhost, port: program.mport });

354
    // Connecting to DB
355
    server.connect(function (err) {
356 357

      if(err){
358 359 360
        logger.warn(err);
        server.disconnect();
        process.exit(1);
361 362
        return;
      }
363 364 365

      server.conf = overrideConf(server.conn, server.conf);

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

374 375 376 377 378 379 380 381 382 383
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);
  }
}

384
program.parse(process.argv);