diff --git a/cli/main.js b/cli/main.js
index dc0b98a9..8eb36763 100644
--- a/cli/main.js
+++ b/cli/main.js
@@ -46,6 +46,7 @@ function main() {
.command('start')
.description('Start the current node')
.option('-c, --config
', 'Specify the directory with Bitcore Node configuration')
+ .option('-d, --daemon', 'Make bitcore-node a daemon (running in the background)')
.action(function(cmd){
if (cmd.config) {
cmd.config = path.resolve(process.cwd(), cmd.config);
@@ -54,6 +55,9 @@ function main() {
if (!configInfo) {
configInfo = defaultConfig();
}
+ if(cmd.daemon) {
+ configInfo.config.daemon = true;
+ }
start(configInfo);
});
diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js
index bae01a23..a459a7a4 100644
--- a/lib/scaffold/start.js
+++ b/lib/scaffold/start.js
@@ -7,6 +7,9 @@ var bitcore = require('bitcore');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var log = index.log;
+var child_process = require('child_process');
+var fs = require('fs');
+
log.debug = function() {};
/**
@@ -111,10 +114,10 @@ function registerSyncHandlers(node, delay) {
/**
* Will register event handlers to stop the node for `process` events
* `uncaughtException` and `SIGINT`.
- * @param {Object} proc - The Node.js process
+ * @param {Object} _process - The Node.js process
* @param {Node} node
*/
-function registerExitHandlers(proc, node) {
+function registerExitHandlers(_process, node) {
function exitHandler(options, err) {
if (err) {
@@ -126,27 +129,27 @@ function registerExitHandlers(proc, node) {
if(err) {
log.error('Failed to stop services: ' + err);
}
- proc.exit(-1);
+ _process.exit(-1);
});
}
if (options.sigint) {
node.stop(function(err) {
if(err) {
log.error('Failed to stop services: ' + err);
- return proc.exit(1);
+ return _process.exit(1);
}
log.info('Halted');
- proc.exit(0);
+ _process.exit(0);
});
}
}
//catches uncaught exceptions
- proc.on('uncaughtException', exitHandler.bind(null, {exit:true}));
+ _process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
//catches ctrl+c event
- proc.on('SIGINT', exitHandler.bind(null, {sigint:true}));
+ _process.on('SIGINT', exitHandler.bind(null, {sigint:true}));
}
/**
@@ -164,16 +167,20 @@ function registerExitHandlers(proc, node) {
function start(options) {
var fullConfig = _.clone(options.config);
- fullConfig.services = setupServices(require, options.path, options.config);
+ fullConfig.services = start.setupServices(require, options.path, options.config);
fullConfig.datadir = path.resolve(options.path, options.config.datadir);
+ if (fullConfig.daemon) {
+ start.spawnChildProcess(fullConfig.datadir, process);
+ }
+
var node = new BitcoreNode(fullConfig);
// set up the event handlers for logging sync information
- registerSyncHandlers(node);
+ start.registerSyncHandlers(node);
// setup handlers for uncaught exceptions and ctrl+c
- registerExitHandlers(process, node);
+ start.registerExitHandlers(process, node);
node.on('ready', function() {
log.info('Bitcore Node ready');
@@ -187,7 +194,44 @@ function start(options) {
}
+/**
+ * This function will fork the passed in process and exit the parent process
+ * in order to daemonize the process. If there is already a daemon for this pid (process),
+ * then the function just returns. Stdout and stderr both append to one file, 'bitcore-node.log'
+ * located in the datadir.
+ * @param {String} datadir - The data directory where the bitcoin blockchain and config live.
+ * @param {Object} _process - The process that needs to fork a child and then, itself, exit.
+ */
+function spawnChildProcess(datadir, _process) {
+
+ if (_process.env.__bitcore_node) {
+ return _process.pid;
+ }
+
+ var args = [].concat(_process.argv);
+ args.shift();
+ var script = args.shift();
+ var env = _process.env;
+ var cwd = _process.cwd();
+ env.__bitcore_node = true;
+
+ var stderr = fs.openSync(datadir + '/bitcore-node.log', 'a+');
+ var stdout = stderr;
+
+ var cp_opt = {
+ stdio: ['ignore', stdout, stderr],
+ env: env,
+ cwd: cwd,
+ detached: true
+ };
+
+ var child = child_process.spawn(_process.execPath, [script].concat(args), cp_opt);
+ child.unref();
+ return _process.exit();
+}
+
module.exports = start;
module.exports.registerExitHandlers = registerExitHandlers;
module.exports.registerSyncHandlers = registerSyncHandlers;
module.exports.setupServices = setupServices;
+module.exports.spawnChildProcess = spawnChildProcess;
diff --git a/test/scaffold/start.unit.js b/test/scaffold/start.unit.js
index 2a592045..1b507db0 100644
--- a/test/scaffold/start.unit.js
+++ b/test/scaffold/start.unit.js
@@ -163,4 +163,104 @@ describe('#start', function() {
});
});
});
+ describe('#spawnChildProcess', function() {
+
+ it('should build the appropriate arguments to spawn a child process', function() {
+ var child = {
+ unref: function() {}
+ };
+ var _process = {
+ exit: function() {},
+ env: {
+ __bitcore_node: false
+ },
+ argv: [
+ 'node',
+ 'bitcore-node'
+ ],
+ cwd: function(){return ''},
+ pid: 999,
+ execPath: '/tmp'
+ };
+ var fd = {};
+ var spawn = sinon.stub().returns(child);
+ var openSync = sinon.stub().returns(fd);
+ var spawnChildProcess = proxyquire('../../lib/scaffold/start', {
+ fs: {
+ openSync: openSync
+ },
+ child_process: {
+ spawn: spawn
+ }
+ }).spawnChildProcess;
+
+ spawnChildProcess('/tmp', _process);
+
+ spawn.callCount.should.equal(1);
+ spawn.args[0][0].should.equal(_process.execPath);
+ var expected = [].concat(_process.argv);
+ expected.shift();
+ spawn.args[0][1].should.deep.equal(expected);
+ var cp_opt = {
+ stdio: ['ignore', fd, fd],
+ env: _process.env,
+ cwd: '',
+ detached: true
+ };
+ spawn.args[0][2].should.deep.equal(cp_opt);
+ openSync.callCount.should.equal(1);
+ openSync.args[0][0].should.equal('/tmp/bitcore-node.log');
+ openSync.args[0][1].should.equal('a+');
+ });
+ it('should not spawn a new child process if there is already a daemon running', function() {
+ var _process = {
+ exit: function() {},
+ env: {
+ __bitcore_node: true
+ },
+ argv: [
+ 'node',
+ 'bitcore-node'
+ ],
+ cwd: 'cwd',
+ pid: 999,
+ execPath: '/tmp'
+ };
+ var spawnChildProcess = proxyquire('../../lib/scaffold/start', {}).spawnChildProcess;
+ spawnChildProcess('/tmp', _process).should.equal(999);
+ });
+ });
+ describe('daemon', function() {
+ var sandbox;
+ var spawn;
+ var setup;
+ var registerSync;
+ var registerExit;
+ var start = require('../../lib/scaffold/start');
+ var options = {
+ config: {
+ datadir: '/tmp',
+ daemon: true
+ }
+ }
+ beforeEach(function() {
+ sandbox = sinon.sandbox.create();
+ spawn = sandbox.stub(start, 'spawnChildProcess', function() {});
+ setup = sandbox.stub(start, 'setupServices', function() {});
+ registerSync = sandbox.stub(start, 'registerSyncHandlers', function() {});
+ registerExit = sandbox.stub(start, 'registerExitHandlers', function() {});
+ });
+ afterEach(function() {
+ sandbox.restore();
+ });
+ it('call spawnChildProcess if there is a config option to do so', function() {
+ start(options);
+ spawn.callCount.should.equal(1);
+ });
+ it('not call spawnChildProcess if there is not an option to do so', function() {
+ options.config.daemon = false;
+ start(options);
+ spawn.callCount.should.equal(0);
+ });
+ });
});