Nexus()

nexus

nexus foo
  • {"type":"param","types":["Object"],"name":"options","description":""}
  • function Nexus(opts) {
      var self = this
      EE2.call(self,{wildcard:true,delimiter:'::',maxListeners:20})
      try { self.package = require('./package.json') } catch(e) {}
      self.apps = {}
      self.config = initConfig(opts)
      self.initDb()
    }
    
    Nexus.prototype = new EE2

    Nexus.prototype.version()

    @param {Function} cb with 2 args: err, version

    Nexus.prototype.version = function version(cb) {
      cb = _.isFunction(cb) ? cb : function() {}
      if (!this.package || !this.package.version)
        return cb(new Error('could not read/parse the package.json'))
      cb(null, this.package.version)
    }

    Nexus.prototype.config()

    @param {Function} cb with 2 args: err, version

    Nexus.prototype.config = function config(opts, cb) {
      cb = _.isFunction(arguments[arguments.length-1])
           ? arguments[arguments.length-1]
           : function() {}
    
      if (!!this._config) return cb(null, this._config)
    
      var self = this
        , cfg = self._config = {}
        , cfgFile = {}
        , cfgPath = home+'/.nexus/config.js'
        , home = ( process.platform === "win32"
                 ? process.env.USERPROFILE
                 : process.env.HOME )
    
      if (arguments.length < 2) opts = {}
      if (opts && _.isString(opts)) cfgPath = opts
      if (opts && _.isObject(opts)) cfg = opts
    
      try { fileConfig = require(configPath) }
      catch (e) {} // no config-file, so we use currConfig or hardcoded defaults
    
      var defaultPrefix = path.join(home,'.nexus')
    
      cfg.apps    = cfg.apps    || cfgFile.apps    || path.join(defaultPrefix,'apps')
      cfg.tmp     = cfg.tmp     || cfgFile.tmp     || path.join(defaultPrefix,'tmp')
      cfg.dbs     = cfg.dbs     || cfgFile.dbs     || path.join(defaultPrefix,'dbs')
      cfg.logs    = cfg.logs    || cfgFile.logs    || path.join(defaultPrefix,'logs')
      // client
      cfg.key     = cfg.key     || cfgFile.key     || null
      cfg.cert    = cfg.cert    || cfgFile.cert    || null
      // server
      cfg.ca      = cfg.ca      || cfgFile.ca      || null
      cfg.host    = cfg.host    || cfgFile.host    || '0.0.0.0'
      cfg.port    = cfg.port    || cfgFile.port    || null
      cfg.socket  = cfg.socket  || cfgFile.socket  || null
      // remotes
      cfg.remotes = cfg.remotes || cfgFile.remotes || {}
    
      cfg.execTimeout = 1000*60*30
      cfg.restartTimeout = 200
      cfg.restartTimeoutLimit = 1000*10
    
      var ensureDirs = [cfg.apps, cfg.tmp, cfg.dbs, cfg.logs]
      if (cfg.ca) ensureDirs.push(cfg.ca)
    
      ensureDirs.forEach(function(x){
        if (!fs.existsSync(x)) mkdirp.sync(x, 0755)
      })
      // async.map(ensureDirs,function(x, next){
      //   fs.exists(x, function(e){
      //     if (!e) return mkdirp(x, 0755, next)
      //     next()
      //   })
      // }, function(err){
      //   cb(err, cfg)
      // })
    }

    Nexus.prototype.install()

    @param {Object} options

  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, object containing information about"}
  • {"type":"","string":"the fresh installed application"}
  • Nexus.prototype.install = function install(opts, cb) {
    
    }

    Nexus.prototype.ls()

    list installed apps

    ls({'package.name':'foo'},cb)
    
  • {"type":"param","types":["Object"],"name":"filter","description":"(optional)"}
  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, array containing information"}
  • {"type":"","string":"about installed applications"}
  • Nexus.prototype.ls = function ls(filter, cb) {
      var self = this
    
      cb = _.isFunction(arguments[arguments.length-1])
           ? arguments[arguments.length-1]
           : function() {}
    
      if ( arguments.length < 2
           || !_.isObject(filter)
           || Object.keys(filter).length==0)
        filter = null
    
      fs.readdir(self.config.apps, function(err, dirs){
        if (err) return cb(err)
        async.map(dirs, function(x, next){
          var app = {}
          app.name = x
          var appPath = path.join(self._config.apps,x)
          var pkgPath = path.join(appPath, 'package.json')
          var gitPath = path.join(appPath, '.git')
          var tasks =
          async.series([checkPackage,checkGit], function(err, data){
            if (err) return cb(err)
            next(err, app)
          })
    
          function checkPackage(next){
            fs.exists(pkgPath,function(exists){
              if (!exists) return next()
              fs.readFile(pkgPath,function(err,data){
                if (err) return next()
                try { app.package = JSON.parse(data) }
                catch(e) { app.package = 'INVALID' }
                next()
              })
            })
          }
    
          function checkGit(next){
            fs.exists(gitPath,function(exists){
              if (!exists) return next()
              var getDiff = 'git diff'
              var getCommit = 'git log --no-color | head -n1'
              cp.exec(getCommit, {cwd:appPath}, function(err, stdout, stderr){
                if (err || stderr) return next() // ignore..
                app.git = {}
                app.git.commit = stdout.trim().split(/\s+/)[1]
                next()
              })
            })
          }
        }, function(err, d){
          var result = []
          _.each(d,function(data){
            if (!filter)
              return result.push(data)
            var filteredData = objFilter(filter, data)
            if (filteredData) result.push(filteredData)
          })
          cb(null, result)
        })
      })
    }

    Nexus.prototype.ps()

    get information about running apps

    filter all apps with name "foo" and also show the id:

    ps( { name : 'foo', id : true }, cb )
    
  • {"type":"param","types":["Object"],"name":"filter","description":""}
  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, an array containing infos about apps"}
  • Nexus.prototype.ps = function ps(filter, cb) {
      var self = this
    
      cb = _.isFunction(arguments[arguments.length-1])
           ? arguments[arguments.length-1]
           : function() {}
    
      if ( arguments.length < 2
           || !_.isObject(filter)
           || Object.keys(filter).length==0 )
        filter = null
    
      if (Object.keys(self.apps).length == 0)
        return cb(null, [])
    
      var result = []
      async.map(Object.keys(self.apps),function(x,next){
        self.apps[x].info(function(err,data){
          if (!filter) {
            result.push(data)
            return next()
          }
          var filteredData = objFilter(filter, data)
          if (filteredData) result.push(filteredData)
          next()
        })
      }, function(err){
        cb(null, result)
      })
    }

    Nexus.prototype.start()

    start an application

    start({name:'someInstalledApp',env:{},command:'node foo',max:3},cb)

  • {"type":"param","types":["Object"],"name":"options","description":""}
  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, version"}
  • Nexus.prototype.start = function start(opts, cb) {
      debug('nexus.start',opts)
      opts = _.isObject(opts) ? opts : {}
      cb = arguments[arguments.length - 1]
      cb = _.isFunction(cb) ? cb : function(){}
      if (!opts.name || !_.isString(opts.name)) 
        return cb(new Error('invalid options, no app defined'))
      
      var self = this
      opts.cwd = path.join(self._config.apps, opts.name)
      fs.exists(opts.cwd, function(e){
        if (!e) 
          return cb(new Error('invalid name, the cwd doesnt exist: '+opts.cwd))
        var id = genId(10)
        while (self.apps[id]) id = genId()
        opts.id = id
        opts.restartTimeout = self._config.restartTimeout
        opts.restartTimeoutLimit = self._config.restartTimeoutLimit
        if (!opts.command) 
          return cb(new Error('invalid options, no command defined'))
        // #TODO 
        // 1) check for opts.command
        // 2) check for nexus.json.start
        // 3) check for package.json.scripts.start
        new App(opts, function(err,app){
          if (err) return cb(err)
          self.apps[id] = app
          self.apps[id].onAny(function(){
            var args = [].slice.call(arguments)
            args.unshift('app::'+id+'::'+this.event)
            self.emit.apply(self, args)
          })
          self.apps[id].start(cb)
        })
      })
    }

    Nexus.prototype.restart()

    @param {String|Array} id(s) of apps

  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, version"}
  • Nexus.prototype.restart = function start(ids, cb) {
      ids = _.isString(ids) ? [ids] : _.isArray(ids) ? ids : null
      cb = arguments[arguments.length - 1]
      cb = _.isFunction(cb) ? cb : function(){}
      var self = this
      if (!ids)
        return cb(new Error('invalid options, missing id(s)'))
      async.map(ids,function(id,next){
        id = id.toString()
        if (!self.apps[id]) return next()
        self.apps[id].restart(next)
      }, cb)
    }

    Nexus.prototype.stop()

    @param {String|Array} id(s) of apps

  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, version"}
  • Nexus.prototype.stop = function stop(ids, cb) {
      var self = this
      ids = _.isString(ids) ? [ids] : _.isArray(ids) ? ids : null
      cb = arguments[arguments.length - 1]
      cb = _.isFunction(cb) ? cb : function(){}
      if (!ids || ids.length==0)
        return cb(new Error('invalid options, missing id(s)'))
      async.map(ids,function(id,next){
        if (!self.apps[id]) return next()
        self.apps[id].stop(function(err,data){
          if (err) return cb(err)
          self.apps[id].removeAllListeners()
          delete self.apps[id]
          next(null,data)
        })
      },cb)
    }

    Nexus.prototype.stopall()

    @param {Function} cb with 2 args: err, version

    Nexus.prototype.stopall = function stopall(cb) {
      var self = this
      var appIds = Object.keys(this.apps)
      if (appIds.length == 0)
        return cb(null, [])
      this.stop(appIds, cb)
    }

    Nexus.prototype.exec()

    @param {Object} options

  • {"type":"param","types":["Function"],"name":"cb","description":"with 2 args: err, version"}
  • Nexus.prototype.exec = function exec(opts, cb) {
      var self = this
      opts = opts || {}
      cb = _.isFunction(arguments[arguments.length-1])
           ? arguments[arguments.length-1]
           : function() {}
    
      if (!opts.command)
        return cb(new Error('invalid arguments, missing command'))
    
      if (_.isArray(opts.command))
        opts.command = opts.command.join(' ')
    
      if (!_.isString(opts.command))
        return cb(new Error('invalid arguments, invalid command (must be array or string)'))
    
      ;['kill','stdout','stderr'].forEach(function(x){
        opts[x] = opts[x] && _.isFunction(opts[x]) ? opts[x] : function(){}
      })
    
      var sh = (process.platform === "win32") ? 'cmd' : 'sh'
      var shFlag = (process.platform === "win32") ? '/c' : '-c'
      var cwd = opts.name ? path.join(self._config.apps,opts.name) : self._config.apps
      fs.exists(cwd,function(exists){
        if (!exists)
          return cb(new Error('invalid arguments, cwd does not exist: '+cwd))
        var child = cp.spawn(sh, [shFlag, opts.command], {cwd:cwd})
        opts.kill(kill)
        setTimeout(kill, self._config.execTimeout)
        child.stdout.on('data',function(d){
          opts.stdout(d.toString().replace(/\n$/, ''))
        })
        child.stderr.on('data',function(d){
          opts.stderr(d.toString().replace(/\n$/, ''))
        })
        child.on('exit',cb)
        child.on('error',function(e){console.log('exec cp-error',opts,e)})
        function kill() {
          pstree(child.pid, function(err, children){
            var tokill = []
            children.map(function(p){
              tokill.push(p.PID)
            })
            tokill.unshift(child.pid)
            tokill.forEach(function(x){
              process.kill(x)
            })
          })
        }
      })
    }
    
    Nexus.prototype.initDb = function initDb(cb) {
      cb = _.isFunction(cb) ? cb : function() {}
    
      var self = this
      var db
      var dbPath = self._config.port
                   ? path.join(self._config.dbs, self._config.port)
                   : self._config.socket
                     ? path.join(self._config.dbs, self._config.socket.replace(/\//g,'_'))
                     : null
    
      if (!dbPath)
        return cb(new Error('cant init db without defined port or socket'))
    
      var rebootFlag = self._config.reboot
      var todo = [unlinkDb, loadDb, subStop, subStart]
    
      if (self._config.reboot) todo.unshift(reboot)
    
      async.series(todo, function(x,next){x(next)}, cb)
    
      function reboot(cb) {
        var db = dirty(dbPath).on('load',function(){
          db.forEach(function(k,v){
            console.log('rebooting',v)
            if (v) nexus.start(v)
          })
          cb()
        })
      }
    
      function unlinkDb(cb) {
        fs.exists(dbPath,function(exists){
          if (exists) return fs.unlink(dbPath,cb)
          cb()
        })
      }
    
      function loadDb(cb) {
        db = dirty(dbPath).on('load',cb)
      }
    
      function subStop(cb) {
        self.on('app::*::stop',function(){
          var id = this.event.split('::')[1]
          db.rm(id)
        })
        cb()
      }
    
      function subStart(cb) {
        self.on('app::*::start',function(pid){
          var id = this.event.split('::')[1]
          self.ps({id:id}, function(err, d){
            if (err) return
            db.set(id, d)
          })
        })
        cb()
      }
    }

    Nexus.prototype.dnodeService()

    @return {Function} dnode-middleware (remote, conn)

    Nexus.prototype.dnodeService = function dnodeService() {
      var self = this
    
      return function(remote, conn){
        var service = this
        var subs = {}
    
        ;[ 'version', 'install', 'ls', 'ps', 'start', 'restart'
         , 'stop', 'stopall', 'exec'
         ].forEach(function(x){ service[x] = Nexus.prototype[x].bind(self) })
    
        service.subscribe  = function subscribe(event, emit, cb) {
          if (event == '*' || event == 'all') event = '**'
          if (!subs[event]) {
            subs[event] = function(data){ emit(this.event,data) }
            self.on(event, subs[event])
          }
          cb && cb()
        }
        service.unsubscribe = function unsubscribe(events, cb) {
          cb = _.isFunction(arguments.length-1) ? arguments.length-1 : function(){}
          events = _.isString(events)
                   ? [events] : (_.isArray(events) && events.length>0)
                                ? events : Object.keys(subs)
          events.forEach(function(x){
            self.removeListener(x, subs[x])
            delete subs[x]
          })
        }
    
        conn.once('end',function(){
          service.unsubscribe()
        })
      }
    }

    Nexus.prototype.connect()

    Nexus.prototype.connect = function connect() {
      var self = this
      var client = dnode(this.dnodeService()).listen()
      self.config(function(err,cfg){
        readKeys({key:cfg.keys})
      })
      return server
    }

    Nexus.prototype.listen()

    Nexus.prototype.listen = function listen() {
      var server = dnode(this.dnodeService())
      server.listen.apply(server,arguments)
      return server
    }

    App()

    @param {Object} options

  • {"type":"param","types":["Function"],"name":"callback","description":""}
  • function App(opts, cb) {
      debug('new app',opts)
      opts = _.isObject(opts) ? opts : null
      cb = _.isFunction(cb) ? cb : function(){}
      if (!opts) 
        return cb(new Error('invalid options'))
      if (!_.isString(opts.name)) 
        return cb(new Error('invalid options, no app defined'))
      if (!opts.command) 
        return cb(new Error('invalid options, no command defined'))
      if (!_.isString(opts.command)) {
        if (_.isArray(opts.command))
          opts.command = opts.command.join(' ')
        else
          return cb(new Error('invalid command - must be array or string'))
      }
      
      var self = this
      EE2.call(self,{wildcard:true,delimiter:'::',maxListeners:20})
      cb = _.isFunction(cb) ? cb : function(){}
      opts = opts || {}
      self.id = opts.id
      self.status = 'starting'
      self.name = opts.name
      self.command = opts.command
      self.startedOnce = false
      self.restartFlag = false
      self.stopFlag = false
      // after the app crashed it will restart it after `restartTimeout` (ms)
      self.restartTimeout = opts.restartTimeout || 200
      // everytime the app crashes `restartTimeout` gets increased by 100ms
      // until it reaches `restartTimeoutLimit`
      self.restartTimeoutLimit = opts.restartTimeoutLimit || 100000
      self.child = null
      self.crashed = 0
      self.max = opts.max
      self.cwd = opts.cwd
      self.env = opts.env || {}
      self.env.NEXUS_ID = self.id
      
      if (!_.isString(self.command)) {
        if (_.isArray(self.command))
          self.command = self.command.join(' ')
        else
          return cb(new Error('invalid command'))
      }
      cb(null, self)
      return self
    }
    
    App.prototype = new EE2

    App.prototype.start()

    @param {Function} callback

    App.prototype.start = function start(cb) {
      cb = _.isFunction(cb) ? cb : function(){}
      var self = this
      self.startedOnce = true
      if (self.child)
        return cb(new Error('is already running'))
      self.status = 'starting'
      var sh = (process.platform === "win32") ? 'cmd' : 'sh'
      var shFlag = (process.platform === "win32") ? '/c' : '-c'
      var child = cp.spawn( sh
                          , [shFlag, self.command]
                          , { cwd : self.cwd
                            , env : self.env
                            } )
      self.child = child
      self.ctime = Date.now()
      self.status = 'running'
      self.emit('start', self.child.pid)
      self.info(cb)
      child.stdout.on('data',function(d){
        self.emit('stdout', d.toString().replace(/\n$/, ''))
      })
      child.stderr.on('data',function(d){
        self.emit('stderr', d.toString().replace(/\n$/, ''))
      })
      child.once('exit', function(code, sig){
        self.emit('exit', code)
        self.child = null
        if ((code != 0) && !self.stopFlag) {
          self.status = 'crashed'
          self.crashed++
          if (!self.max || self.crashed < self.max) {
            if ((Date.now()-self.ctime) > 60000)
              self.restartTimeout = 200
            else if (self.restartTimeout < self.restartTimeoutLimit)
              self.restartTimeout += 100
            setTimeout(function(){
              self.start()
            },self.restartTimeout)
          }
        } 
        else {
          // this app has no running child-process, though it can be restarted
          self.status = 'idle'
        }
      })
    }

    App.prototype.restart()

    @param {Function} callback

    App.prototype.restart = function restart(cb) {
      var self = this
      self.status = 'restarting'
      self.crashed = 0
      self.restartFlag = true
      self.stop(function(){
        setTimeout(function(){
          self.restartFlag = false
          self.start(cb)
        },200)
      })
    }

    App.prototype.stop()

    @param {Function} callback

    App.prototype.stop = function stop(cb) {
      var self = this
      self.status = 'stopping'
      self.stopFlag = true
      if (self.child) {
        var timer = setTimeout(function(){
          cb(new Error( 'tried to kill process (pid:'+self.child.pid+') '
                      + 'but it did not exit yet' ) )
        },5000)
        self.child.once('exit',function(){
          self.stopFlag = false
          clearTimeout(timer)
          self.emit('stop')
          self.info(cb)
        })
        pstree(self.child.pid, function(err, children){
          if (err) return cb(err)
          var pids = children.map(function (p) {return p.PID})
          pids.unshift(self.child.pid)
          cp.spawn('kill', ['-9'].concat(pids)).on('exit',function(){})
        })
      }
      else {
        self.stopFlag = false
        self.info(cb)
      }
    }

    App.prototype.info()

    @param {Function} callback

    App.prototype.info = function info(cb) {
      cb = _.isFunction(cb) ? cb : function(){}
      var self = this
      var r = {}
      r.id = self.id
      r.pid = self.child ? self.child.pid : null
      r.status = self.status
      r.name = self.name
      r.command = self.command
      cb(null, r)
    }

    objFilter()

    @param {Object} filter

  • {"type":"param","types":["Object"],"name":"data","description":""}
  • {"type":"return","types":["Object","false"],"description":"filtered data or false"}
  • function objFilter(filter, data) {
      var result = {}
      var all = true
    
      _.each(filter,function(y,j){
        if (!result) return
        if (_.isBoolean(y)) {
          all = false
          var info = objPath(data,j)
          if (info !== undefined) result[j] = info
          else result[j] = 'UNDEFINED'
        }
        else {
          y = y.toString()
          var info = objPath(data,j)
          if (info != y) result = false
          else result[j] = info
        }
      })
    
      if (result && all) return data
      return result
    }

    objPath()

    objPath - create/access objects with a key-string

  • {"type":"param","types":["Object"],"name":"scope","description":""}
  • {"type":"param","types":["String"],"name":"key-string","description":"(e.g. 'a.b.c.d')"}
  • {"type":"param","types":["Mixed"],"name":"value","description":"to store in key (optional)"}
  • {"type":"return","types":["Mixed"],"description":"if value-param is given it will return the object, otherwise it will return the value of the selected key, undefined values will get false.."}
  • function objPath(obj, keyString, value) {
      var keys = keyString.split('.')
      if (obj[keys[0]] === undefined) obj[keys[0]] = {}
      var data = obj[keys[0]]
        , keys = keys.slice(1)
    
      if (!value) { // get data
        var value = data
        for (var i=0, len=keys.length; i

    genId()

    @param {Integer} length

  • {"type":"return","types":["String"],"description":"random id with requested length"}
  • function genId(len) {
      len = len ? parseInt(len) : 8
      var ret = ''
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwyxz0123456789'
      while (len--)
        ret += chars[Math.round(Math.random() * (chars.length-1))]
      return ret
    }

    config()

    @param {Object} options

  • {"type":"return","types":["Object"],"description":"config"}
  • function config(opts) {
      var cfg = {}
        , cfgFile = {}
        , cfgPath = home+'/.nexus/config.js'
        , home = ( process.platform === "win32"
                 ? process.env.USERPROFILE
                 : process.env.HOME )
    
      if (opts && _.isString(opts)) cfgPath = opts
      if (opts && _.isObject(opts)) cfg = opts
    
      try { cfgFile = require(cfgPath) }
      catch (e) {} // no config-file, so we use currConfig or hardcoded defaults
    
      var defaultPrefix = path.join(home,'.nexus')
    
      cfg.apps    = cfg.apps    || cfgFile.apps    || path.join(defaultPrefix,'apps')
      cfg.tmp     = cfg.tmp     || cfgFile.tmp     || path.join(defaultPrefix,'tmp')
      cfg.dbs     = cfg.dbs     || cfgFile.dbs     || path.join(defaultPrefix,'dbs')
      cfg.logs    = cfg.logs    || cfgFile.logs    || path.join(defaultPrefix,'logs')
      // client
      cfg.key     = cfg.key     || cfgFile.key     || null
      cfg.cert    = cfg.cert    || cfgFile.cert    || null
      // server
      cfg.ca      = cfg.ca      || cfgFile.ca      || null
      cfg.host    = cfg.host    || cfgFile.host    || '0.0.0.0'
      cfg.port    = cfg.port    || cfgFile.port    || null
      cfg.socket  = cfg.socket  || cfgFile.socket  || null
      // remotes
      cfg.remotes = cfg.remotes || cfgFile.remotes || {}
    
      cfg.execTimeout = 1000*60*30
      cfg.restartTimeout = 200
      cfg.restartTimeoutLimit = 1000*10
    
      var ensureDirs = [cfg.apps, cfg.tmp, cfg.dbs, cfg.logs]
      if (cfg.ca) ensureDirs.push(cfg.ca)
    
      ensureDirs.forEach(function(x){
        if (!fs.existsSync(x)) mkdirp.sync(x, 0755)
      })
      // async.map(ensureDirs,function(x, next){
      //   fs.exists(x, function(e){
      //     if (!e) return mkdirp(x, 0755, next)
      //     next()
      //   })
      // }, function(err){
      //   cb(err, cfg)
      // })
      return cfg
    }
    
    function readKeys(opts, cb) {
      cb = arguments[arguments.length-1]
      cb = _.isFunction(cb) ? cb : function(){}
      opts = opts || {}
      var result = {}
      
      async.parallel([readKey, readCert, readCa], function(err){
        if (err) return cb(err)
        cb(null, result)
      })
      
      function readKey(next) {
        if (!opts.key) return next()
        fs.readFile(opts.key,function(err, data){
          if (err) return next(err)
          result.key = data
          next()
        })
      }
      
      function readCert(next) {
        if (!opts.key) return next()
        fs.readFile(opts.key,function(err, data){
          if (err) return next(err)
          result.key = data
          next()
        })
      }
      
      function readCa(next) {
        if (!opts.ca) return next()
        fs.readdir(opts.ca,function(err,data){
          if (err) return next(err)
          if (data.length > 0) {
            result.requestCert = true
            result.rejectUnauthorized = true
            async.map(data,function(x, done){
              fs.readFile(path.join(opts.ca,x), done) 
            }, function(err, data){
              if (err) return next(err)
              result.ca = data
              next()
            })
          } else {
            next()
          }
        })
      }
    }