Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Features:
* Clustering - take advantage of multiple CPU cores
* Properly handles SIGTERM and SIGHUP for integration with service wrappers
* Supports POSIX operating systems (does not support Windows)
* Supports using sockets opened by systemd or launchd

Usage:
------
Expand Down Expand Up @@ -131,7 +132,6 @@ If you want to deploy on a restricted port such as 80 or 443 without sudo, try
Note that there are 3 layers of process spawning between the naught CLI
and your server. So you'll want to use the `--deep` option with authbind.


Using a service wrapper:
------------------------

Expand Down Expand Up @@ -160,6 +160,73 @@ When you run with `--daemon-mode false`, the process tree looks like this:
* worker 2
* etc

Using a socket from systemd or launchd
--------------------------------------

When using naught from systemd or launchd, you must use `--daemon-mode false`.

systemd and launchd can be configured to listen on a port and launch naught
when a connection is detected on that port. The intention is that your server
will only run when it is actually needed. systemd or launchd will provide the
open socket to naught on a file descriptor, and naught will pass that file
descriptor on to your server as it launches it. naught will set the
`LISTEN_FD` environment variable to the number of the file descriptor on
which your server should listen, which it could do like this:

```js
server.listen(process.env.LISTEN_FD ? {fd: parseInt(process.env.LISTEN_FD, 10)} : (process.env.PORT || 8000));
```

naught automatically detects if it was launched by systemd and passes the
socket along. For use from launchd, pass the `--launchd-socket` flag when
starting naught, and provide the name of the key you defined in the Sockets
dictionary in your server's launchd plist. Here is a sample plist:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>NODE_ENV</key>
<string>production</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>Label</key>
<string>com.example.myserver</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/node</string>
<string>node_modules/.bin/naught</string>
<string>start</string>
<string>--daemon-mode</string>
<string>false</string>
<string>--launchd-socket</string>
<string>Listeners</string>
<string>server.js</string>
</array>
<key>Sockets</key>
<dict>
<key>Listeners</key>
<dict>
<key>SockFamily</key>
<string>IPv4v6</string>
<key>SockServiceName</key>
<integer>8000</integer>
</dict>
</dict>
<key>WorkingDirectory</key>
<string>/opt/myserver</string>
</dict>
</plist>
```

If you want to pass a socket in a file descriptor to naught started from some
process other than systemd or launchd, you can set the LISTEN_FD environment
variable to the file descriptor number when you launch naught.

CLI:
----

Expand Down Expand Up @@ -202,6 +269,7 @@ CLI:
--daemon-mode true
--remove-old-ipc false
--node-args ''
--launchd-socket ''


naught stop [options] [ipc-file]
Expand Down
64 changes: 64 additions & 0 deletions example/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// npm install express express-domain-errors express-graceful-exit
var domain = require('domain');
var domainError = require('express-domain-errors');
var express = require('express');
var gracefulExit = require('express-graceful-exit');
var http = require('http');
var util = require('util');

var serverDomain = domain.create();
serverDomain.run(function () {
var app, server;

function sendOfflineMsg() {
if (process.send) {
process.send('offline');
}
}

function doGracefulExit(err) {
console.log('Server shutting down');
gracefulExit.gracefulExitHandler(app, server);
}

process.on('message', function (message) {
if (message === 'shutdown') {
doGracefulExit();
}
});

app = express();
app.use(domainError(sendOfflineMsg, doGracefulExit));
app.use(gracefulExit.middleware(app));

app.get('/', function (req, res) {
res.set('Content-Type', 'text/plain');
res.send('Hello world\n');
});
app.get('/bad', function (req, res) {
process.nextTick(/*process.domain.intercept*/(function () {
nonexistentFunction();
}));
});

app.use(function (req, res, next) {
var err = new Error(util.format('The requested URL %s was not found on this server.', req.url));
err.status = 404;
next(err);
});

app.use(function (err, req, res, next) {
var status = err.status || 500;
var message = err.message || 'The server encountered an internal error or misconfiguration and was unable to complete your request.';
res.set('Content-Type', 'text/plain');
res.status(status);
res.send(http.STATUS_CODES[status] + '\n\n' + message + '\n');
});

server = app.listen(process.env.LISTEN_FD ? {fd: parseInt(process.env.LISTEN_FD, 10)} : (process.env.PORT || 8000), function () {
console.log('Server listening on port %d', server.address().port);
if (process.send) {
process.send('online');
}
});
});
32 changes: 31 additions & 1 deletion lib/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function startDaemon(argv) {
var script = argv.shift();
var nodeArgsStr = argv.shift();
var pidFile = argv.shift();
var launchdSocket = argv.shift();

var naughtLog = null;
var stderrLog = null;
Expand Down Expand Up @@ -187,9 +188,38 @@ function startDaemon(argv) {
var nodeArgs = splitCmdLine(nodeArgsStr);
var stdoutValue = (stdoutBehavior === 'inherit') ? process.stdout : stdoutBehavior;
var stderrValue = (stderrBehavior === 'inherit') ? process.stderr : stderrBehavior;
var listenFd = parseInt(process.env.LISTEN_FD, 10);
if (!listenFd) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure, but I suspect the test here could be wrong.

A non defined LISTEN_FD will result in a NaN value, so still truthy value.
Maybe this should be :

if (!isNaN(listenFd)) { ..

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NaN isn't truthy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct. my bad.
👍

if (parseInt(process.env.LISTEN_PID, 10) === process.pid) {
// systemd
var listenFds = parseInt(process.env.LISTEN_FDS, 10);
if (listenFds === 0) {
// systemd provided no sockets
// log this?
} else if (listenFds > 1) {
// systemd provided too many sockets
// log this?
} else {
// SD_LISTEN_FDS_START
listenFd = 3;
}
} else if (launchdSocket) {
// launchd
try {
listenFd = require('node-launchd').getSocketFileDescriptorForName(launchdSocket);
} catch (e) {
// could not get launchd socket
}
}
}
var stdio = [process.stdin, stdoutValue, stderrValue, 'ipc'];
if (listenFd) {
process.env.LISTEN_FD = stdio.length;
stdio.push(listenFd);
}
master = spawn(process.execPath, nodeArgs.concat([path.join(__dirname, "master.js"), workerCount, script]).concat(argv), {
env: process.env,
stdio: [process.stdin, stdoutValue, stderrValue, 'ipc'],
stdio: stdio,
cwd: process.cwd(),
});
master.on('message', onMessage);
Expand Down
7 changes: 5 additions & 2 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ var cmds = {
" --cwd " + CWD + "\n" +
" --daemon-mode true\n" +
" --remove-old-ipc false\n" +
" --node-args ''",
" --node-args ''\n" +
" --launchd-socket ''",
fn: function(argv){
var options = {
'worker-count': '1',
Expand All @@ -66,6 +67,7 @@ var cmds = {
'daemon-mode': 'true',
'remove-old-ipc': 'false',
'node-args': '',
'launchd-socket': '',
};
var arr = chompArgv(options, argv)
, err = arr[0]
Expand Down Expand Up @@ -379,7 +381,8 @@ function startScript(options, script, argv){
options['max-log-size'],
path.resolve(CWD, script),
options['node-args'],
options['pid-file']
options['pid-file'],
options['launchd-socket']
].concat(argv);
if (options['daemon-mode']) {
startDaemonChild(args);
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
"mkdirp": "~0.5.0",
"async": "~0.9.0"
},
"optionalDependencies": {
"node-launchd": "0.0.3"
},
"bugs": {
"url": "https://github.com/andrewrk/naught/issues"
},
Expand Down