├── .gitignore ├── examples ├── advanced │ ├── config.json │ ├── app.js │ └── ctrl.js └── simple │ ├── app.js │ └── ctrl.js ├── package.json ├── LICENSE ├── lib ├── constants.js ├── wrapper.js └── daemonize.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .project 3 | -------------------------------------------------------------------------------- /examples/advanced/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": "Hello World!" 3 | } 4 | -------------------------------------------------------------------------------- /examples/simple/app.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require("http"); 3 | 4 | http.createServer(function(req, res) { 5 | 6 | res.writeHead(200, { 7 | "Content-Type": "text/plain" 8 | }); 9 | res.end("Hello World"); 10 | 11 | }).listen(8080); 12 | -------------------------------------------------------------------------------- /examples/simple/ctrl.js: -------------------------------------------------------------------------------- 1 | 2 | var daemon = require("daemonize2").setup({ 3 | main: "app.js", 4 | name: "sampleapp", 5 | pidfile: "sampleapp.pid" 6 | }); 7 | 8 | switch (process.argv[2]) { 9 | 10 | case "start": 11 | daemon.start(); 12 | break; 13 | 14 | case "stop": 15 | daemon.stop(); 16 | break; 17 | 18 | default: 19 | console.log("Usage: [start|stop]"); 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daemonize2", 3 | "version": "0.4.2", 4 | "description": "Module for easy creation of daemons for Node 0.8.x", 5 | "author": "Kuba Niegowski", 6 | "contributors": [], 7 | "homepage": "https://github.com/niegowski/node-daemonize2/", 8 | "keywords" : [ 9 | "daemon" 10 | ], 11 | "engines": { 12 | "node": ">0.8.x" 13 | }, 14 | "main": "./lib/daemonize.js" 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Kuba Niegowski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /examples/advanced/app.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require("fs"), 3 | http = require("http"), 4 | cluster = require('cluster'); 5 | 6 | 7 | var config = loadConfig(), 8 | logStream = openLog("/tmp/sampleapp.log"); 9 | 10 | 11 | function loadConfig() { 12 | return JSON.parse(fs.readFileSync(__dirname + "/config.json")); 13 | } 14 | 15 | function openLog(logfile) { 16 | return fs.createWriteStream(logfile, { 17 | flags: "a", encoding: "utf8", mode: 0644 18 | }); 19 | } 20 | 21 | function log(msg) { 22 | logStream.write(msg + "\n"); 23 | } 24 | 25 | 26 | log("Starting..."); 27 | 28 | 29 | if (cluster.isMaster) { 30 | log("Forking workers") 31 | 32 | var cpus = require('os').cpus().length; 33 | for (var i = 0; i < cpus; i++) { 34 | cluster.fork(); 35 | } 36 | return; 37 | } 38 | 39 | log("Initiating worker") 40 | 41 | 42 | 43 | 44 | var server = http.createServer(function(req, res) { 45 | log("Request from " + req.connection.remoteAddress); 46 | 47 | res.writeHead(200, { 48 | "Content-Type": "text/plain" 49 | }); 50 | res.end(config.msg); 51 | 52 | }).listen(8080); 53 | 54 | log("Started."); 55 | 56 | 57 | process.on("uncaughtException", function(err) { 58 | log(err.stack); 59 | }); 60 | 61 | process.on("SIGUSR1", function() { 62 | log("Reloading..."); 63 | 64 | config = loadConfig(); 65 | 66 | log("Reloaded."); 67 | }); 68 | 69 | process.once("SIGTERM", function() { 70 | log("Stopping..."); 71 | 72 | server.on("close", function() { 73 | log("Stopped."); 74 | 75 | logStream.on("close", function() { 76 | process.exit(0); 77 | }).end(); 78 | 79 | }).close(); 80 | }); 81 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Kuba Niegowski 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | "use strict"; 22 | 23 | var exitCodes = exports.exitCodes = { 24 | argMainRequired: [96, "'main' argument is required"], 25 | mainNotFound: [97, "Specified 'main' module cannot be found"], 26 | chdirFailed: [99, "Failed to change working directory to root"], 27 | setgidNoPriv: [100, "No privilege to change group id"], 28 | setgidFailed: [101, "Failed to change group id"], 29 | setuidNoPriv: [102, "No privilege to change user id"], 30 | setuidFailed: [103, "Failed to change user id"] 31 | }; 32 | 33 | exports.findExitCode = function(code) { 34 | for (var name in exitCodes) 35 | if (exitCodes[name][0] == code) 36 | return exitCodes[name][1]; 37 | return code; 38 | }; 39 | -------------------------------------------------------------------------------- /examples/advanced/ctrl.js: -------------------------------------------------------------------------------- 1 | 2 | var daemon = require("daemonize2").setup({ 3 | main: "app.js", 4 | name: "sampleapp", 5 | pidfile: "/var/run/sampleapp.pid", 6 | user: "www", 7 | group: "www", 8 | silent: true 9 | }); 10 | 11 | if (process.getuid() != 0) { 12 | console.log("Expected to run as root"); 13 | process.exit(1); 14 | } 15 | 16 | daemon 17 | .on("starting", function() { 18 | console.log("Starting daemon..."); 19 | }) 20 | .on("started", function(pid) { 21 | console.log("Daemon started. PID: " + pid); 22 | }) 23 | .on("stopping", function() { 24 | console.log("Stopping daemon..."); 25 | }) 26 | .on("stopped", function(pid) { 27 | console.log("Daemon stopped."); 28 | }) 29 | .on("running", function(pid) { 30 | console.log("Daemon already running. PID: " + pid); 31 | }) 32 | .on("notrunning", function() { 33 | console.log("Daemon is not running"); 34 | }) 35 | .on("error", function(err) { 36 | console.log("Daemon failed to start: " + err.message); 37 | }); 38 | 39 | 40 | switch (process.argv[2]) { 41 | 42 | case "start": 43 | daemon.start(); 44 | break; 45 | 46 | case "stop": 47 | daemon.stop(); 48 | break; 49 | 50 | case "kill": 51 | daemon.kill(); 52 | break; 53 | 54 | case "restart": 55 | daemon.stop(function(err) { 56 | daemon.start(); 57 | }); 58 | break; 59 | 60 | case "reload": 61 | console.log("Reload."); 62 | daemon.sendSignal("SIGUSR1"); 63 | break; 64 | 65 | case "status": 66 | var pid = daemon.status(); 67 | if (pid) 68 | console.log("Daemon running. PID: " + pid); 69 | else 70 | console.log("Daemon is not running."); 71 | break; 72 | 73 | default: 74 | console.log("Usage: [start|stop|kill|restart|reload|status]"); 75 | } 76 | -------------------------------------------------------------------------------- /lib/wrapper.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Kuba Niegowski 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | "use strict"; 22 | 23 | var constants = require("./constants"); 24 | 25 | 26 | // wrapper is spawned with no stdio attached so redirect exceptions via ipc 27 | var exceptionHandler = null; 28 | process.on("uncaughtException", exceptionHandler = function(err) { 29 | process.send({type: "error", error: err.stack || err.message}); 30 | process.disconnect(); 31 | process.exit(1); 32 | }); 33 | 34 | // now sit and wait for options via ipc 35 | process.on("message", function(message) { 36 | 37 | if (message.type == "init") { 38 | init(message.options); 39 | } 40 | }); 41 | 42 | var init = function(options) { 43 | 44 | // validate options 45 | if (!options.main) { 46 | process.exit(constants.exitCodes.argMainRequired[0]); 47 | } 48 | 49 | // check if main is resolvable 50 | try { 51 | require.resolve(options.main); 52 | } 53 | catch (ex) { 54 | process.exit(constants.exitCodes.mainNotFound[0]); 55 | } 56 | 57 | // rename process 58 | if (options.name) 59 | process.title = options.name; 60 | 61 | // change the file mode mask 62 | process.umask(options.umask); 63 | 64 | // change working directory 65 | try { 66 | process.chdir(options.cwd || "/"); 67 | } 68 | catch (ex) { 69 | process.exit(constants.exitCodes.chdirFailed[0]); 70 | } 71 | 72 | // change group id 73 | if (options.group) { 74 | try { 75 | process.setgid(options.group); 76 | } 77 | catch (ex) { 78 | process.exit(ex.code == "EPERM" 79 | ? constants.exitCodes.setgidNoPriv[0] 80 | : constants.exitCodes.setgidFailed[0] 81 | ); 82 | } 83 | } 84 | 85 | // change user id 86 | if (options.user) { 87 | try { 88 | process.setuid(options.user); 89 | } 90 | catch (ex) { 91 | process.exit(ex.code == "EPERM" 92 | ? constants.exitCodes.setuidNoPriv[0] 93 | : constants.exitCodes.setuidFailed[0] 94 | ); 95 | } 96 | } 97 | 98 | // make wrapper transparent 99 | process.argv = [ 100 | process.argv[0], 101 | options.main 102 | ].concat(process.argv.slice(2)); 103 | 104 | // run main module 105 | var setup = require(options.main); 106 | 107 | // pass options to exported function 108 | if (typeof setup == "function") setup(options); 109 | 110 | // stop monitoring uncaughtException 111 | process.removeListener("uncaughtException", exceptionHandler); 112 | 113 | // close IPC to parent process 114 | process.disconnect(); 115 | 116 | }; 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ======= 3 | 4 | Node module for easy creation of daemons for Node 0.8.x and above. 5 | 6 | For Node 0.6.x compatibility see daemonize https://github.com/niegowski/node-daemonize 7 | 8 | Just write your daemon as plain node.js application 9 | (like `/examples/simple/app.js`) and a simple controller with Daemonize 10 | (like `/examples/simple/ctrl.js`). 11 | 12 | 13 | Installation 14 | ============== 15 | ``` 16 | $ npm install daemonize2 17 | ``` 18 | 19 | 20 | Example 21 | ========= 22 | 23 | ``` js 24 | var daemon = require("daemonize2").setup({ 25 | main: "app.js", 26 | name: "sampleapp", 27 | pidfile: "sampleapp.pid" 28 | }); 29 | 30 | switch (process.argv[2]) { 31 | 32 | case "start": 33 | daemon.start(); 34 | break; 35 | 36 | case "stop": 37 | daemon.stop(); 38 | break; 39 | 40 | default: 41 | console.log("Usage: [start|stop]"); 42 | } 43 | ``` 44 | 45 | For more examples see `examples` folder. 46 | 47 | Documentation 48 | =============== 49 | 50 | Daemonize works like standard `require()` but loaded module is 51 | forked to work in background as a daemon. 52 | 53 | Keep in mind that `stdin`, `stdout` and `stderr` are redirected 54 | to `/dev/null` so any output from daemon won't display in console. 55 | You need to use file for logging (ie like `/examples/advanced/app.js`). 56 | 57 | Also any uncaught exception won't be displayed in the console, 58 | so `process.on("uncaughtException", ...)` should be used to 59 | redirect output to some log file. 60 | 61 | ## daemonize.setup(options) 62 | Creates new `Daemon` instance. Supported `options`: 63 | 64 | * `main` - main application module file to run as daemon (required); `string` 65 | * `name` - daemon name (default: basename of main); `string` 66 | * `pidfile` - pidfile path (default: `/var/run/[name].pid`); `string` 67 | * `user` - name or id of user (default: current); `string` 68 | * `group` - name or id of group (default: current); `string` 69 | * `umask` - file mode mask (default: 0); `number` or `string` 70 | * `silent` - disable printing info to console (default: `false`); `boolean` 71 | * `stopTimeout` - interval (ms) of daemon killing retry (default: `2s`); `number` 72 | * `args` - additional node runtime arguments, ie `--debug`; `array` or `string` 73 | * `argv` - argv for daemon (default: `process.argv.slice(2)`); `array` or `string` 74 | * `cwd` - current working directory for spawned daemon (default: `/`); `string` 75 | 76 | All paths are resolved relative to file that uses "daemonize". 77 | 78 | All commandline arguments will be passed to the child process unless 79 | overriden with `argv` option. 80 | 81 | ## Daemon 82 | Daemon control class. It references controlled daemon. 83 | 84 | ### Event: "starting" 85 | `function() { }` 86 | 87 | Emitted when `start()` is called and if daemon is not already running. 88 | 89 | ### Event: "started" 90 | `function(pid) { }` 91 | 92 | Emitted when daemon successfully started after calling `start()`. 93 | 94 | ### Event: "running" 95 | `function(pid) { }` 96 | 97 | Emitted when `start()` is called and a daemon is already running. 98 | 99 | ### Event: "stopping" 100 | `function() { }` 101 | 102 | Emitted when `stop()` or `kill()` is called and a daemon is running. 103 | 104 | ### Event: "stopped" 105 | `function(pid) { }` 106 | 107 | Emitted when daemon was successfully stopped after calling `stop()` 108 | or `kill()`. 109 | 110 | ### Event: "notrunning" 111 | `function() { }` 112 | 113 | Emitted when `stop()` or `kill()` is called and a deamon is not running. 114 | 115 | ### Event: "error" 116 | `function(error) { }` 117 | 118 | Emitted when `start()` failed. `error` is instance of `Error`. 119 | `error.message` contains information what went wrong. 120 | 121 | ### daemon.start([listener]) 122 | Start daemon asynchronously. Emits `running` in case when daemon is 123 | already running and `starting` when daemon is not running. Then emits 124 | `started` when daemon is successfully started. 125 | 126 | Optional `listener` callback is once called on `running`, `started` or `error` 127 | event. The callback gets two arguments `(err, pid)`. 128 | 129 | Emits `error` in case of any problem during daemon startup. 130 | 131 | ### daemon.stop([listener]) 132 | Asynchronously stop daemon. Sends `SIGTERM` to daemon every 2s (or time 133 | set in options). 134 | 135 | Emits `notrunning` when daemon is not running, otherwise 136 | emits `stopping` and then `stopped` when daemon successfully stopped. 137 | 138 | Optional `listener` callback is once called on `notrunning`, `stopped` or 139 | `error` event. The callback gets two arguments `(err, pid)`. 140 | 141 | ### daemon.kill([listener]) 142 | Kill daemon asynchronously. Sends `SIGTERM` and after 2s `SIGKILL` to the 143 | child if needed. Repeats sending `SIGKILL` every 2s untill daemon 144 | stops (interval can be changed in options). 145 | 146 | Emits events same as `stop()`. 147 | 148 | Optional `listener` callback is same as `stop`. 149 | 150 | ### daemon.status() 151 | Synchronously returns pid for running daemon or 0 when daemon is not running. 152 | 153 | ### daemon.sendSignal(signal) 154 | Synchronously sends `signal` to daemon and returns pid of daemon or 0 when 155 | daemon is not running. 156 | 157 | 158 | Changelog 159 | =========== 160 | 161 | Daemonize is maintained under the [Semantic Versioning] 162 | (https://github.com/niegowski/semver/blob/master/semver.md) 163 | guidelines. 164 | 165 | ### 0.4.2 - Jun 09 2013 166 | - update node version dependency 167 | 168 | ### 0.4.1 - Jun 09 2013 169 | - split `args` and `argv` on whitespaces 170 | - added `umask` option 171 | 172 | ### 0.4.0 - Jun 05 2013 173 | - added argv option 174 | 175 | ### 0.4.0-rc.6 - Nov 28 2012 176 | - args option to enable node arguments ie --debug 177 | - fix for: Wrapper seems to eat one argument 178 | 179 | ### 0.4.0-rc.5 - Aug 28 2012 180 | - Wrapper is transparent now 181 | 182 | ### 0.4.0-rc.4 - Aug 16 2012 183 | - The callback for start, stop and kill handles errors 184 | 185 | ### 0.4.0-rc.3 - Aug 14 2012 186 | - Optional callback argument for start, stop and kill 187 | 188 | ### 0.4.0-rc.2 - Jul 29 2012 189 | - Passing command line arguments to child process 190 | 191 | ### 0.4.0-rc.1 - Jul 29 2012 192 | - Daemonize forked as Daemonize2 for Node 0.8.x compatibility 193 | - Removed native module for setsid - using child_process.spawn detached 194 | - Passing options via ipc instead of command line arguments 195 | - Rethrowing wrapper exceptions via ipc 196 | 197 | ### 0.3.2 - Jul 29 2012 198 | - Daemonize is compatible only with Node 0.6.x 199 | 200 | ### 0.3.1 - Apr 2 2012 201 | 202 | ### 0.3.0 - Jan 29 2012 203 | - Daemon emits Events instead of console.log() 204 | - API change - events in place of callbacks 205 | 206 | ### 0.2.2 - Jan 27 2012 207 | - root priviledges no longer required 208 | - changed error exit codes 209 | - try to remove pidfile on daemon stop 210 | - configurable timeouts for start monitoring and killing 211 | - closing FD-s on daemon start 212 | - better examples 213 | 214 | ### 0.2.1 - Jan 26 2012 215 | - fix for calling callback in stop/kill when process is not running 216 | 217 | ### 0.2.0 - Jan 26 2012 218 | - code refactor 219 | - stop listening for uncaughtException 220 | - logfile removed 221 | 222 | ### 0.1.2 - Jan 25 2012 223 | - fixed stdout, stderr replacement 224 | - checking for daemon main module presence 225 | - signals change (added custom signals) 226 | - better log messages 227 | - gracefull terminate in example app 228 | - close logfile on process exit 229 | 230 | ### 0.1.1 - Jan 24 2012 231 | - print stacktrace for uncaughtException 232 | 233 | ### 0.1.0 - Jan 24 2012 234 | - First release 235 | 236 | 237 | License 238 | ========= 239 | 240 | (The MIT License) 241 | 242 | Copyright (c) 2012 Kuba Niegowski 243 | 244 | Permission is hereby granted, free of charge, to any person obtaining a copy 245 | of this software and associated documentation files (the "Software"), to deal 246 | in the Software without restriction, including without limitation the rights 247 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 248 | copies of the Software, and to permit persons to whom the Software is 249 | furnished to do so, subject to the following conditions: 250 | 251 | The above copyright notice and this permission notice shall be included in 252 | all copies or substantial portions of the Software. 253 | 254 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 255 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 256 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 257 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 258 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 259 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 260 | THE SOFTWARE. 261 | -------------------------------------------------------------------------------- /lib/daemonize.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Kuba Niegowski 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | "use strict"; 22 | 23 | var fs = require("fs"), 24 | path = require("path"), 25 | util = require("util"), 26 | constants = require("./constants"), 27 | spawn = require("child_process").spawn, 28 | EventEmitter = require("events").EventEmitter; 29 | 30 | 31 | exports.setup = function(options) { 32 | return new Daemon(options); 33 | }; 34 | 35 | var Daemon = function(options) { 36 | EventEmitter.call(this); 37 | 38 | if (!options.main) 39 | throw new Error("Expected 'main' option for daemonize"); 40 | 41 | var dir = path.dirname(module.parent.filename), 42 | main = path.resolve(dir, options.main), 43 | name = options.name || path.basename(main, ".js"); 44 | 45 | if (!this._isFile(main)) 46 | throw new Error("Can't find daemon main module: '" + main + "'"); 47 | 48 | // normalize options 49 | this._options = {}; 50 | 51 | // shallow copy 52 | for (var arg in options) 53 | this._options[arg] = options[arg]; 54 | 55 | this._options.main = main; 56 | this._options.name = this.name = name; 57 | 58 | this._options.pidfile = options.pidfile 59 | ? path.resolve(dir, options.pidfile) 60 | : path.join("/var/run", name + ".pid"); 61 | 62 | this._options.user = options.user || ""; 63 | this._options.group = options.group || ""; 64 | 65 | if (typeof options.umask == "undefined") 66 | this._options.umask = 0; 67 | else if (typeof options.umask == "string") 68 | this._options.umask = parseInt(options.umask); 69 | 70 | this._options.args = this._makeArray(options.args); 71 | this._options.argv = this._makeArray(options.argv || process.argv.slice(2)); 72 | 73 | this._stopTimeout = options.stopTimeout || 2000; 74 | 75 | this._childExitHandler = null; 76 | this._childDisconnectHandler = null; 77 | this._childDisconnectTimer = null; 78 | 79 | if (!options.silent) 80 | this._bindConsole(); 81 | }; 82 | util.inherits(Daemon, EventEmitter); 83 | 84 | Daemon.prototype.start = function(listener) { 85 | 86 | // make sure daemon is not running 87 | var pid = this._sendSignal(this._getpid()); 88 | 89 | if (pid) { 90 | this.emit("running", pid); 91 | if (listener) listener(null, pid); 92 | return this; 93 | } 94 | 95 | // callback for started and error 96 | if (listener) { 97 | var errorFunc, startedFunc; 98 | 99 | this.once("error", errorFunc = function(err) { 100 | this.removeListener("started", startedFunc); 101 | listener(err, 0); 102 | }.bind(this)); 103 | 104 | this.once("started", startedFunc = function(pid) { 105 | this.removeListener("error", errorFunc); 106 | listener(null, pid); 107 | }.bind(this)); 108 | } 109 | 110 | this.emit("starting"); 111 | 112 | // check whether we have right to write to pid file 113 | var err = this._savepid(""); 114 | if (err) { 115 | this.emit("error", new Error("Failed to write pidfile (" + err + ")")); 116 | return this; 117 | } 118 | 119 | // spawn child process 120 | var child = spawn(process.execPath, (this._options.args || []).concat([ 121 | __dirname + "/wrapper.js" 122 | ]).concat(this._options.argv), { 123 | env: process.env, 124 | stdio: ["ignore", "ignore", "ignore", "ipc"], 125 | detached: true 126 | } 127 | ); 128 | pid = child.pid; 129 | 130 | // save pid 131 | this._savepid(pid); 132 | 133 | // rethrow childs's exceptions 134 | child.on("message", function(msg) { 135 | if (msg.type == "error") 136 | throw new Error(msg.error); 137 | }); 138 | 139 | // wrapper.js will exit with special exit codes 140 | child.once("exit", this._childExitHandler = function(code, signal) { 141 | 142 | child.removeListener("disconnect", this._childDisconnectHandler); 143 | clearTimeout(this._childDisconnectTimer); 144 | 145 | if (code > 0) { 146 | this.emit("error", new Error( 147 | code > 1 148 | ? constants.findExitCode(code) 149 | : "Module '" + this._options.main + "' stopped unexpected" 150 | )); 151 | } else { 152 | this.emit("stopped"); 153 | } 154 | 155 | }.bind(this)); 156 | 157 | // check if it is still running when ipc closes 158 | child.once("disconnect", this._childDisconnectHandler = function() { 159 | 160 | // check it in 100ms in case this is child's exit 161 | this._childDisconnectTimer = setTimeout(function() { 162 | 163 | child.removeListener("exit", this._childExitHandler); 164 | 165 | if (this._sendSignal(pid)) { 166 | this.emit("started", pid); 167 | 168 | } else { 169 | this.emit("error", new Error("Daemon failed to start")); 170 | } 171 | 172 | }.bind(this), 100); 173 | }.bind(this)); 174 | 175 | // trigger child initialization 176 | child.send({type: "init", options: this._options}); 177 | 178 | // remove child from reference count to make parent process exit 179 | child.unref(); 180 | 181 | return this; 182 | }; 183 | 184 | Daemon.prototype.stop = function(listener, signals, timeout) { 185 | return this._kill(signals || ["SIGTERM"], timeout || 0, listener); 186 | }; 187 | 188 | Daemon.prototype.kill = function(listener, signals, timeout) { 189 | return this._kill(signals || ["SIGTERM", "SIGKILL"], timeout || 0, listener); 190 | }; 191 | 192 | Daemon.prototype.status = function() { 193 | return this._sendSignal(this._getpid()); 194 | }; 195 | 196 | Daemon.prototype.sendSignal = function(signal) { 197 | return this._sendSignal(this._getpid(), signal); 198 | }; 199 | 200 | Daemon.prototype._makeArray = function(args) { 201 | if (typeof args == "undefined") return []; 202 | if (typeof args == "string") 203 | return args.trim().split(/\s+/).filter(function(arg) { return !!arg; }); 204 | return args; 205 | }; 206 | 207 | Daemon.prototype._getpid = function() { 208 | 209 | try { 210 | return parseInt(fs.readFileSync(this._options.pidfile)); 211 | } 212 | catch (err) { 213 | } 214 | 215 | return 0; 216 | }; 217 | 218 | Daemon.prototype._savepid = function(pid) { 219 | 220 | try { 221 | fs.writeFileSync(this._options.pidfile, pid + "\n"); 222 | } 223 | catch (ex) { 224 | return ex.code; 225 | } 226 | return ""; 227 | }; 228 | 229 | Daemon.prototype._sendSignal = function(pid, signal) { 230 | 231 | if (!pid) return 0; 232 | 233 | try { 234 | process.kill(pid, signal || 0); 235 | return pid; 236 | } 237 | catch (err) { 238 | } 239 | 240 | return 0; 241 | }; 242 | 243 | Daemon.prototype._kill = function(signals, timeout, listener) { 244 | 245 | var pid = this._sendSignal(this._getpid()); 246 | 247 | if (!pid) { 248 | this.emit("notrunning"); 249 | if (listener) listener(null, 0); 250 | return this; 251 | } 252 | 253 | if (listener) { 254 | this.once("stopped", function(pid) { 255 | listener(null, pid); 256 | }); 257 | } 258 | 259 | this.emit("stopping"); 260 | 261 | this._tryKill(pid, signals, timeout, function(pid) { 262 | 263 | // try to remove pid file 264 | try { 265 | fs.unlinkSync(this._options.pidfile); 266 | } 267 | catch (ex) {} 268 | 269 | this.emit("stopped", pid); 270 | 271 | }.bind(this)); 272 | 273 | return this; 274 | }; 275 | 276 | Daemon.prototype._tryKill = function(pid, signals, timeout, callback) { 277 | 278 | if (!this._sendSignal(pid, signals.length > 1 ? signals.shift() : signals[0])) { 279 | if (callback) callback(pid); 280 | return true; 281 | } 282 | 283 | setTimeout(this._tryKill.bind(this, pid, signals, timeout, callback), timeout || this._stopTimeout); 284 | return false; 285 | }; 286 | 287 | Daemon.prototype._isFile = function(path) { 288 | 289 | try { 290 | var stat = fs.statSync(path); 291 | if (stat && !stat.isDirectory()) 292 | return true; 293 | } 294 | catch (err) { 295 | } 296 | 297 | return false; 298 | }; 299 | 300 | Daemon.prototype._bindConsole = function() { 301 | 302 | this 303 | .on("starting", function() { 304 | console.log("Starting " + this.name + " daemon..."); 305 | }) 306 | .on("started", function(pid) { 307 | console.log(this.name + " daemon started. PID: " + pid); 308 | }) 309 | .on("stopping", function() { 310 | console.log("Stopping " + this.name + " daemon..."); 311 | }) 312 | .on("stopped", function(pid) { 313 | console.log(this.name + " daemon stopped."); 314 | }) 315 | .on("running", function(pid) { 316 | console.log(this.name + " daemon already running. PID: " + pid); 317 | }) 318 | .on("notrunning", function() { 319 | console.log(this.name + " daemon is not running"); 320 | }) 321 | .on("error", function(err) { 322 | console.log(this.name + " daemon failed to start: " + err.message); 323 | }); 324 | 325 | }; 326 | --------------------------------------------------------------------------------