node-windows
Advanced tools
Comparing version 0.1.12 to 0.1.13
@@ -101,14 +101,31 @@ /** | ||
get: function(){ | ||
var wrapperArgs = [ | ||
'--file', this.script, | ||
'--log', this.name + ' ' + 'wrapper', | ||
'--grow', this.grow, | ||
'--wait', this.wait, | ||
'--maxrestarts', this.maxRestarts, | ||
'--abortonerror', (this.abortOnError==true?'y':'n'), | ||
'--stopparentfirst', this.stopparentfirst | ||
]; | ||
if (this.maxRetries!==null) | ||
{ | ||
wrapperArgs.push('--maxretries'); | ||
wrapperArgs.push(this.maxRetries); | ||
} | ||
return require('./winsw').generateXml({ | ||
name: this.name, | ||
id: this._exe, | ||
script: '"'+wrapper+'" -f "'+this.script+'" -l "'+this.name+'" -g '+this.grow | ||
+' -w '+this.wait+(this.maxRetries!==null?' -m '+this.maxRetries:'') | ||
+' -r '+this.maxRestarts+' -a '+(this.abortOnError==true?'y':'n') | ||
+ (config.cwd ? ' -d "'+config.cwd+'"' : ''), | ||
nodeOptions: '--harmony', | ||
script: wrapper, | ||
wrapperArgs: wrapperArgs, | ||
description: this.description, | ||
logpath: this.logpath, | ||
env: config.env, | ||
flags: config.flags, | ||
execPath: this.execPath | ||
logOnAs: this.logOnAs, | ||
workingdirectory: this.workingdirectory, | ||
stopparentfirst: this.stopparentfirst, | ||
stoptimeout: this.stoptimeout | ||
}); | ||
@@ -136,2 +153,25 @@ } | ||
}, | ||
/** | ||
* @cfg {Boolean} [stopparentfirst=false] | ||
* Allow the service to shutdown cleanly. | ||
*/ | ||
stopparentfirst: { | ||
enumerable: true, | ||
writable: false, | ||
configurable: false, | ||
value: config.stopparentfirst | ||
}, | ||
/** | ||
* @cfg {Number} [stoptimeout=30] | ||
* How long to wait in seconds before force killing the application. | ||
* This only takes effect when stopparentfirst is enabled. | ||
*/ | ||
stoptimeout: { | ||
enumerable: true, | ||
writable: false, | ||
configurable: false, | ||
value: config.stoptimeout || 30 | ||
}, | ||
@@ -275,30 +315,30 @@ /** | ||
/** | ||
* @property {Object} [user] | ||
* If you need to specify a specific user or particular credentials to manage a service, the following | ||
* attributes may be helpful. | ||
* | ||
* The `user` attribute is an object with three keys: `domain`,`account`, and `password`. | ||
* This can be used to identify which user the service library should use to perform system commands. | ||
* By default, the domain is set to the local computer name, but it can be overridden with an Active Directory | ||
* or LDAP domain. For example: | ||
* | ||
* **app.js** | ||
* | ||
* var Service = require('node-windows').Service; | ||
* | ||
* // Create a new service object | ||
* var svc = new Service({ | ||
* name:'Hello World', | ||
* script: require('path').join(__dirname,'helloworld.js') | ||
* }); | ||
* | ||
* svc.user.domain = 'mydomain.local'; | ||
* svc.user.account = 'username'; | ||
* svc.user.password = 'password'; | ||
* ... | ||
* | ||
* Both the account and password must be explicitly defined if you want the service module to | ||
* run commands as a specific user. By default, it will run using the user account that launched | ||
* the process (i.e. who launched `node app.js`). | ||
*/ | ||
* @property {Object} [user] | ||
* If you need to specify a specific user or particular credentials to manage a service, the following | ||
* attributes may be helpful. | ||
* | ||
* The `user` attribute is an object with three keys: `domain`,`account`, and `password`. | ||
* This can be used to identify which user the service library should use to perform system commands. | ||
* By default, the domain is set to the local computer name, but it can be overridden with an Active Directory | ||
* or LDAP domain. For example: | ||
* | ||
* **app.js** | ||
* | ||
* var Service = require('node-windows').Service; | ||
* | ||
* // Create a new service object | ||
* var svc = new Service({ | ||
* name:'Hello World', | ||
* script: require('path').join(__dirname,'helloworld.js') | ||
* }); | ||
* | ||
* svc.user.domain = 'mydomain.local'; | ||
* svc.user.account = 'username'; | ||
* svc.user.password = 'password'; | ||
* ... | ||
* | ||
* Both the account and password must be explicitly defined if you want the service module to | ||
* run commands as a specific user. By default, it will run using the user account that launched | ||
* the process (i.e. who launched `node app.js`). | ||
*/ | ||
user: { | ||
@@ -314,3 +354,57 @@ enumerable: false, | ||
}, | ||
/** | ||
* @property {Object} [logOnAs] | ||
* If you need to specify a specific user or particular credentials for the service log on as once installed, the following | ||
* attributes may be helpful. | ||
* | ||
* The `logOnAs` attribute is an object with four keys: `domain`,`account`, `password`, and `mungeCredentialsAfterInstall`. | ||
* This can be used to identify which user the service should run as once installed. | ||
* | ||
* If no account and password is specified, the logOnAs property is not used and the service will run as the "Local System" account. | ||
* If account and password is specified, but domain is not specified then the domain is set to the local computer name, but it can be overridden with an Active Directory | ||
* or LDAP domain. For example: | ||
* | ||
* **app.js** | ||
* | ||
* var Service = require('node-windows').Service; | ||
* | ||
* // Create a new service object | ||
* var svc = new Service({ | ||
* name:'Hello World', | ||
* script: require('path').join(__dirname,'helloworld.js') | ||
* }); | ||
* | ||
* svc.logOnAs.domain = 'mydomain.local'; | ||
* svc.logOnAs.account = 'username'; | ||
* svc.logOnAs.password = 'password'; | ||
* ... | ||
* | ||
* Both the account and password must be explicitly defined if you want the service to log on as that user, | ||
* otherwise the Local System account will be used. | ||
*/ | ||
logOnAs: { | ||
enumerable: false, | ||
writable: true, | ||
configurable: false, | ||
value: { | ||
account: null, | ||
password: null, | ||
domain: process.env.COMPUTERNAME, | ||
mungeCredentialsAfterInstall: true | ||
} | ||
}, | ||
/** | ||
* @property {String} [workingdirectory] | ||
* The full path to the working directory that the service process | ||
* should launch from. If this is omitted, it will default to the | ||
* current processes working directory. | ||
*/ | ||
workingdirectory: { | ||
enumerable: false, | ||
writable: true, | ||
configurable: false, | ||
value: process.cwd() | ||
}, | ||
// Optionally provide a sudo password. | ||
@@ -497,4 +591,5 @@ sudo: { | ||
// Remove the executable | ||
// Remove the executable and executable .NET runtime config file | ||
rm(me.id+'.exe'); | ||
rm(me.id+'.exe.config'); | ||
@@ -501,0 +596,0 @@ // Remove all other files |
@@ -19,3 +19,3 @@ /** | ||
if (require('os').platform().indexOf('win32') < 0){ | ||
throw 'ngn-windows is only supported on Windows.'; | ||
throw 'node-windows is only supported on Windows.'; | ||
} | ||
@@ -22,0 +22,0 @@ |
147
lib/winsw.js
@@ -11,3 +11,4 @@ module.exports = { | ||
* - *name* The descriptive name of the service. | ||
* - *script* The absolute path of the node.js script. i.e. `C:\path\to\myService.js` | ||
* - *script* The absolute path of the node.js server script. i.e. in this case | ||
* it's the wrapper script, not the user's server script. | ||
* | ||
@@ -17,3 +18,4 @@ * Optional attributes include | ||
* - *description* The description that shows up in the service manager. | ||
* - *flags* Any flags that should be passed to node. Defaults to `--harmony` to add ES6 support. | ||
* - *nodeOptions* Array or space separated string of node options (e.g. '--harmony') | ||
* - *wrapperArgs* additional arguments to pass to wrapper script to control restarts, etc. | ||
* - *logmode* Valid values include `rotate` (default), `reset` (clear log), `roll` (move to .old), and `append`. | ||
@@ -24,53 +26,109 @@ * - *logpath* The absolute path to the directory where logs should be stored. Defaults to the current directory. | ||
* environment variables to pass to the process. The object might look like `{name:'HOME',value:'c:\Windows'}`. | ||
* - *logOnAs* A key/value object that contains the service logon credentials. | ||
* The object might look like `{account:'user', password:'pwd', domain:'MYDOMAIN'} | ||
* If this is not included or does not have all 3 members set then it is not used. | ||
* - *workingdirectory* optional working directory that service should run in. | ||
* If this is not included, the current working directory of the install process | ||
* is used. | ||
*/ | ||
generateXml: function(config){ | ||
generateXml: function(config) | ||
{ | ||
var xml; | ||
// Set default values | ||
config = config || {}; | ||
config.description = config.description || ''; | ||
config.flags = config.flags || '--harmony'; | ||
config.logmode = 'rotate'; | ||
config.execPath = config.execPath || process.execPath; | ||
// add multiple "tag" items to the xml | ||
// if input is an array, add each element of the array, if it's a string, | ||
// split it around splitter and add each one | ||
// if input is null or undefined do nothing | ||
function multi(tag, input, splitter) | ||
{ | ||
// do nothing if no input | ||
if (input===undefined || input===null) { | ||
return; | ||
} | ||
// Initial template | ||
var xml = '<service><id>' | ||
+config.id | ||
+'</id><name>' | ||
+config.name | ||
+'</name><description>' | ||
+config.description | ||
+'</description><executable>' + config.execPath + '</executable><arguments>' | ||
+config.flags | ||
+' '+config.script | ||
+'</arguments><logmode>' | ||
+config.logmode | ||
+'</logmode>'; | ||
if (!(input instanceof Array)) { | ||
input = input.split(splitter||','); | ||
} | ||
input.forEach(function(val) { | ||
var ele={}; | ||
ele[tag]=String(val).trim(); | ||
xml.push(ele); | ||
}); | ||
} | ||
// Make sure required configuration items are present | ||
if (!config || !config.id || !config.name || !config.script) | ||
{ | ||
throw "WINSW must be configured with a minimum of id, name and script"; | ||
} | ||
// create json template of xml | ||
// only the portion of the xml inside the top level 'service' tag | ||
xml = [ | ||
{id: config.id}, | ||
{name: config.name}, | ||
{description: config.description||''}, | ||
{executable: process.execPath}, | ||
{logmode: config.logmode||'rotate'} | ||
]; | ||
multi('argument',config.nodeOptions, ' '); | ||
xml.push({argument:config.script.trim()}); | ||
multi('argument',config.wrapperArgs,' '); | ||
// Optionally add log path | ||
if (config.logpath) { | ||
xml += '<logpath>'+config.logpath+'</logpath>'; | ||
xml.push({logpath : config.logpath}); | ||
} | ||
// Optionally add service dependencies | ||
if (config.dependencies){ | ||
config.dependencies = (config.dependencies instanceof Array == true) ? config.dependencies : config.dependencies.split(','); | ||
config.dependencies.forEach(function(dep){ | ||
xml += '<depend>'+dep.trim()+'</depend>'; | ||
}); | ||
// Optionally add stopparentprocessfirst | ||
if (config.stopparentfirst) { | ||
xml.push({stopparentprocessfirst: config.stopparentfirst}); | ||
} | ||
// Optionally set the stoptimeout | ||
if (config.stoptimeout) { | ||
xml.push({stoptimeout: config.stoptimeout + 'sec'}); | ||
} | ||
// Optionally add service dependencies | ||
multi('depend',config.dependencies); | ||
// Optionally add environment values | ||
if (config.env){ | ||
config.env = (config.env instanceof Array == true) ? config.env : [config.env]; | ||
if (config.env) { | ||
config.env = (config.env instanceof Array == true) ? | ||
config.env : [config.env]; | ||
config.env.forEach(function(env){ | ||
xml += '<env name="'+env.name+'" value="'+env.value+'" />'; | ||
xml.push({env: {_attr: {name:env.name, value:env.value}}}); | ||
}); | ||
} | ||
xml += "</service>"; | ||
// optionally set the service logon credentials | ||
if (config.logOnAs && config.logOnAs.account && config.logOnAs.password && | ||
config.logOnAs.domain) | ||
{ | ||
xml.push({ | ||
serviceaccount: [ | ||
{domain: config.logOnAs.domain}, | ||
{user: config.logOnAs.account}, | ||
{password: config.logOnAs.password} | ||
] | ||
}); | ||
} | ||
return xml; | ||
// if no working directory specified, use current working directory | ||
// that this process was launched with | ||
xml.push({workingdirectory: config.workingdirectory || process.cwd()}); | ||
// indent resultant xml with tabs, and use windows newlines for extra readability | ||
return require('xml')({service:xml}, {indent: '\t'}).replace(/\n/g,'\r\n'); | ||
}, | ||
/** | ||
* Copy install version of winsw.exe to specific renamed version according to | ||
* the service id. Also copy .exe.config file that allows it to run under | ||
* .NET 4+ runtime on newer versions of windows. | ||
* (see https://github.com/kohsuke/winsw#net-runtime-40) | ||
* | ||
* @method createExe | ||
@@ -85,6 +143,6 @@ * Create the executable | ||
*/ | ||
createExe: function(name,dir,callback){ | ||
createExe: function(name,dir,callback) { | ||
var fs = require('fs'), p = require('path'); | ||
if (typeof dir === 'function'){ | ||
if (typeof dir === 'function') { | ||
callback = dir; | ||
@@ -96,12 +154,13 @@ dir = null; | ||
var origin = p.join(__dirname,'..','bin','winsw','x'+(require('os').arch().indexOf('64')>0 ? '64':'86'),'winsw.exe'), | ||
dest = p.join(dir,name.replace(/[^\w]/gi,'').toLowerCase()+'.exe'), | ||
data = fs.readFileSync(origin,{encoding:'binary'}); | ||
var exeOrigin = p.join(__dirname,'..','bin','winsw','winsw.exe'), | ||
cfgOrigin = p.join(__dirname,'..','bin','winsw','winsw.exe.config'), | ||
exeDest = p.join(dir,name.replace(/[^\w]/gi,'').toLowerCase()+'.exe'), | ||
cfgDest = p.join(dir,name.replace(/[^\w]/gi,'').toLowerCase()+'.exe.config'), | ||
exeData = fs.readFileSync(exeOrigin,{encoding:'binary'}), | ||
cfgData = fs.readFileSync(cfgOrigin,{encoding:'binary'}); | ||
fs.writeFileSync(dest,data,{encoding:'binary'}); | ||
fs.writeFileSync(exeDest,exeData,{encoding:'binary'}); | ||
fs.writeFileSync(cfgDest,cfgData,{encoding:'binary'}); | ||
callback && callback(); | ||
//require('child_process').exec('Icacls "'+dest+'" /grant Everyone:(F)',callback) | ||
//require('child_process').exec('copy "'+origin+'" /y /v /b "'+dest+'" /b',callback); | ||
} | ||
} | ||
} |
@@ -52,2 +52,8 @@ // Handle input parameters | ||
}) | ||
.default('stopparentfirst', 'no') | ||
.alias('s', 'stopparentfirst') | ||
.describe('stopparentfirst', 'Allow the script to exit using a shutdown message.') | ||
.check(function(argv){ | ||
return ['y','n','yes','no'].indexOf(argv.a.trim().toLowerCase()) >= 0; | ||
}) | ||
.argv, | ||
@@ -81,3 +87,3 @@ log = new Logger(argv.e == undefined ? argv.l : {source:argv.l,eventlog:argv.e}), | ||
// Hack to force the wrapper process to stay open by launching a ghost socket server | ||
var server = require('net').createServer().listen(0, '127.0.0.1'); | ||
var server = require('net').createServer().listen(); | ||
@@ -88,4 +94,4 @@ /** | ||
*/ | ||
var monitor = function(exit){ | ||
if(!child.pid||exit){ | ||
var monitor = function() { | ||
if(!child || !child.pid) { | ||
@@ -107,6 +113,7 @@ // If the number of periodic starts exceeds the max, kill the process | ||
} else { | ||
launch(); | ||
launch('warn', 'Restarted ' + wait + ' msecs after unexpected exit; attempts = ' + attempts); | ||
} | ||
},wait); | ||
} else { | ||
// reset attempts and wait time | ||
attempts = 0; | ||
@@ -121,10 +128,16 @@ wait = argv.w * 1000; | ||
* A method to start a process. | ||
* logLevel - optional logging level (must be the name of a function the the Logger object) | ||
* msg - optional msg to log | ||
*/ | ||
var launch = function(){ | ||
var launch = function(logLevel, msg) { | ||
if (forcekill){ | ||
if (forcekill) { | ||
log.info("Process killed"); | ||
return; | ||
} | ||
log.info('Starting '+argv.f); | ||
//log.info('Starting '+argv.f); | ||
if (logLevel && msg) { | ||
log[logLevel](msg); | ||
} | ||
@@ -144,2 +157,3 @@ // Set the start time if it's null | ||
if (argv.d) opts.cwd = argv.d; | ||
if (argv.s) opts.detached = true; | ||
child = fork(script,opts); | ||
@@ -156,6 +170,9 @@ | ||
//server.unref(); | ||
} else if (forcekill) { | ||
process.exit(); | ||
} | ||
child = null; | ||
// Monitor the process | ||
monitor(true); | ||
monitor(); | ||
}); | ||
@@ -166,11 +183,10 @@ }; | ||
forcekill = true; | ||
child.kill(); | ||
/*if (child.pid) { | ||
require('child_process').exec('taskkill /F /PID '+child.pid,function(){ | ||
process.exit(0); | ||
}); | ||
}*/ | ||
if (argv.s) { | ||
child.send('shutdown'); | ||
} else { | ||
child.kill(); | ||
} | ||
} | ||
process.on('exit',killkid); | ||
process.on('exit', killkid); | ||
process.on("SIGINT", killkid); | ||
@@ -180,2 +196,2 @@ process.on("SIGTERM", killkid); | ||
// Launch the process | ||
launch(); | ||
launch('info', 'Starting ' + argv.f); |
{ | ||
"name": "node-windows", | ||
"version": "0.1.12", | ||
"version": "0.1.13", | ||
"description": "Support for Windows services, event logging, UAC, and several helper methods for interacting with the OS.", | ||
@@ -25,3 +25,4 @@ "keywords": [ | ||
"dependencies": { | ||
"optimist": "~0.6.0" | ||
"optimist": "~0.6.0", | ||
"xml": "0.0.12" | ||
}, | ||
@@ -28,0 +29,0 @@ "readmeFilename": "README.md", |
@@ -508,2 +508,2 @@ # Update (5/21/15) | ||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 2 instances in 1 package
1414
8
174757
2
508
8
+ Addedxml@0.0.12
+ Addedxml@0.0.12(transitive)