Skip to content

Commit

Permalink
Experimental run as user support for init.d scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
Arvind Agarwal authored and Arvind Agarwal committed Jun 9, 2015
1 parent 536f4af commit 58f0800
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -23,3 +23,5 @@ build/Release
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules

.idea
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -109,6 +109,7 @@ forever-service version 0.x.x
-u --applyUlimits Apply increased ulimits in supported environment
-r --runAsUser [user] *Experimental* Run service as a specific user, defaults to root (No ubuntu support yet)
```

Delete service
Expand Down Expand Up @@ -148,7 +149,7 @@ $ sudo forever-service install test --script main.js
* Custom options for forever

```
$ sudo forever-service install test -f " -watchDirectory /your/watch/directory -w"
$ sudo forever-service install test -f " --watchDirectory /your/watch/directory -w"
```


Expand Down
71 changes: 70 additions & 1 deletion bin/forever-service
@@ -1,7 +1,9 @@
#!/usr/bin/env node
"use strict";

var installer = require('../lib/installer');
var platforms = require('../lib/platforms');
var path = require('path');

platforms.get(function(err, platform){
console.log('forever-service version '+require('../package.json').version+'\n');
Expand Down Expand Up @@ -34,6 +36,7 @@ platforms.get(function(err, platform){
.option('--logrotateCompress', 'Enable compression for logrotate\n')
.option('-p --foreverPath [value]','Path for forever cli e.g. /usr/local/bin,\n by default forever cli is searched in system Path variable\n')
.option('-u --applyUlimits','Apply increased ulimits in supported environment\n')
.option('-r --runAsUser [user]','*Experimental* Run service as a specific user, defaults to root (No ubuntu support yet)\n')
.description(
'Install node script (defaults to app.js in current directory) as service via forever\n\n'
)
Expand All @@ -58,6 +61,16 @@ platforms.get(function(err, platform){
if(ctx.envVars){
//Split at space, but ignore space inside quotes..
ctx.envVarsArray = ctx.envVars.match(/(?:[^\s"']+|["'][^"']*["'])+/g);
ctx.envVarsNameValueArray=[];
for(var evi in ctx.envVarsArray){
var ev = ctx.envVarsArray[evi];
var evp = ev.split("=");
if(evp.length != 2){
console.log("Invalid env variable "+ev);
process.exit(1);
}
ctx.envVarsNameValueArray.push(evp);
}
}
if(options.scriptOptions) ctx.scriptOptions = options.scriptOptions;
if(options.minUptime) ctx.minUptime = options.minUptime;
Expand All @@ -70,12 +83,33 @@ platforms.get(function(err, platform){
if(options.logrotateMax) ctx.logrotateMax = options.logrotateMax;
if(options.logrotateCompress) ctx.logrotateCompress = 'compress';
if(options.applyUlimits) ctx.applyUlimits = true;
ctx.foreverPath=path.dirname(getCmdPath('forever'));

if(options.foreverPath) {
var fp = options.foreverPath;
fp = fp.trim();
if(!fp.match(/\/$/)) fp = fp+'/';
ctx.foreverPath = fp;
} else ctx.foreverPath='';
} else {
if(!ctx.foreverPath){
console.error("forever not found, forever must be installed using 'npm install -g forever'");
process.exit(1);
} else {
ctx.foreverPath+="/";
}
}

if(options.runAsUser){
ctx.runAsUser = options.runAsUser;
} else ctx.runAsUser = null;

if(getCmdPath('runuser')){
ctx.suprog="runuser";
} else ctx.suprog="su";



ctx.foreverRoot = getForeverRoot(ctx.runAsUser);

if( !installer.validateScriptName(ctx.script) ){
console.error(ctx.script+' not found');
Expand Down Expand Up @@ -147,3 +181,38 @@ platforms.get(function(err, platform){

});


function getForeverRoot(user){
if(process.getuid() != 0 ){
console.log("forever-service must be run as root");
process.exit(1);
}

var r;
if(user){
r = shell.exec('su - '+user+' -c '+__dirname+'"/get-forever-config"',{silent:true});
}
else
{
return process.env.FOREVER_ROOT || path.join(process.env.HOME || process.env.USERPROFILE || '/root', '.forever');
//r= shell.exec(__dirname+'"/get-forever-config"',{silent:true});
}
if(r.code != 0){
console.log("Unable to get forever root ["+r.code+"]");
console.error(r.output);
process.exit(1);
} else return r.output.trim();
}


function getCmdPath(cmd){
var r=shell.exec('whereis '+cmd, {silent: true});
var re = new RegExp(cmd+":","g");
if(r.output) {
var p = r.output.replace(re, "").trim();
//console.log(cmd + 'path is ' + p);
return p;
} else {
console.log(cmd + 'path not found');
}
}
4 changes: 4 additions & 0 deletions bin/get-forever-config
@@ -0,0 +1,4 @@
#!/usr/bin/env node
var path = require('path');

console.log((process.env.FOREVER_ROOT || path.join(process.env.HOME || process.env.USERPROFILE || '/root', '.forever')));
5 changes: 3 additions & 2 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "forever-service",
"version": "0.4.7",
"version": "0.5.0",
"preferGlobal": "true",
"description": "Provision node script as a service via forever, allowing it to automatically start on boot, working across various Linux distros and OS",
"main": "lib/api.js",
Expand Down Expand Up @@ -42,7 +42,8 @@
"ubuntu"
],
"bin": {
"forever-service": "./bin/forever-service"
"forever-service": "./bin/forever-service",
"get-forever-config": "./bin/get-forever-config"
},
"author": "Arvind Agarwal",
"bugs": {
Expand Down
60 changes: 52 additions & 8 deletions templates/sysvinit/initd.template
Expand Up @@ -24,14 +24,13 @@
# Working Directory {{cwd}}

#Setup Environment variables (if any)
{%- if envVarsArray|default(false) %}
{%- for v in envVarsArray %}
{% if envVarsArray|default(false) %}
{% for v in envVarsArray %}
export {{ v }}
{%- endfor %}
{%- endif %}
{% endfor %}
{% endif %}

# Check if any of $pid (could be plural) are running
PIDFILE="/var/run/{{service}}.pid"
LOGFILE="/var/log/{{service}}.log"
{% if osflavor === 'centos' %}
LOCKFILE="/var/lock/subsys/{{service}}"
Expand Down Expand Up @@ -73,16 +72,45 @@ checkpidexists() {
start() {
#this is to ensure forever is able to find out the correct root every time
export FOREVER_ROOT={{foreverRoot}}


{% if runAsUser %}
STATUS=$({{suprog}} - {{runAsUser}} -c "export FOREVER_ROOT={{foreverRoot}};{% if envVarsNameValueArray|default(false) %}{% for v in envVarsNameValueArray %}export {{ v[0] }}=$(printf "%q" {{v[1]}});{% endfor %}{% endif %}
{{foreverPath}}forever --plain list | sed 's/data:\(\s*\[[0-9]*\]\s*\({{service}}\)\s.*\)/\2-status:\1/;tx;d;:x'")
{% else %}
STATUS=$({{foreverPath}}forever --plain list | sed 's/data:\(\s*\[[0-9]*\]\s*\({{service}}\)\s.*\)/\2-status:\1/;tx;d;:x')
{% endif %}
if ! [ -z "$STATUS" ]; then
echo "Service {{service}} already running"
return 0
fi

echo "Starting {{service}}"



{% if runAsUser %}
touch $LOGFILE
chown {{runAsUser}} $LOGFILE

{{suprog}} - {{runAsUser}} -c "\
export FOREVER_ROOT={{foreverRoot}};\
{% if envVarsNameValueArray|default(false) %}{% for v in envVarsNameValueArray %}export {{ v[0] }}=$(printf "%q" {{v[1]}});\{% endfor %}{% else %}\{% endif %}
cd {{cwd}};\{% if applyUlimits %}
ulimit -f unlimited;\
ulimit -t unlimited;\
ulimit -v unlimited;\
ulimit -n 64000;\
ulimit -m unlimited;\
ulimit -u 32000;\{% endif %}
{{foreverPath}}forever \
-a \
-l $LOGFILE \
--minUptime $MIN_UPTIME \
--spinSleepTime $SPIN_SLEEP_TIME \
--killSignal $KILL_SIGNAL \
{{foreverOptions|default('')}} \
--uid {{service}} \
start {{script|default('app.js')}} {{scriptOptions|default('')}}" 2>&1 >/dev/null
{% else %}
# move to the directory from where the inital forever script was launched so that even if it is relative it works as expected
cd {{cwd}}

Expand All @@ -96,7 +124,6 @@ start() {
{% endif %}

{{foreverPath}}forever \
--pidFile $PIDFILE \
-a \
-l $LOGFILE \
--minUptime $MIN_UPTIME \
Expand All @@ -105,6 +132,7 @@ start() {
{{foreverOptions|default('')}} \
--uid {{service}} \
start {{script|default('app.js')}} {{scriptOptions|default('')}} 2>&1 >/dev/null
{% endif %}
RETVAL=$?

[ $RETVAL = 0 ] && touch $LOCKFILE
Expand All @@ -117,14 +145,22 @@ stop() {

echo -n "Shutting down {{service}}: "

{% if runAsUser %}
STATUS=$({{suprog}} - {{runAsUser}} -c "export FOREVER_ROOT={{foreverRoot}};{% if envVarsNameValueArray|default(false) %}{% for v in envVarsNameValueArray %}export {{ v[0] }}=$(printf "%q" {{v[1]}});{% endfor %}{% endif %}{{foreverPath}}forever --plain list | sed 's/data:\(\s*\[[0-9]*\]\s*\({{service}}\)\s.*\)/\2-status:\1/;tx;d;:x'")
{% else %}
STATUS=$({{foreverPath}}forever --plain list | sed 's/data:\(\s*\[[0-9]*\]\s*\({{service}}\)\s.*\)/\2-status:\1/;tx;d;:x')
{% endif %}
if [ -z "$STATUS" ]; then
echo "Not running"
return 0
fi

# PID=$(<$PIDFILE) - Changed to detection based on actual PID from forever, sicne due to watchDirectory pid could dynamically change
{% if runAsUser %}
PID=$({{suprog}} - {{runAsUser}} -c "export FOREVER_ROOT={{foreverRoot}};{% if envVarsNameValueArray|default(false) %}{% for v in envVarsNameValueArray %}export {{ v[0] }}=$(printf "%q" {{v[1]}});{% endfor %}{% endif %}{{foreverPath}}forever --plain list | sed -n -e '/data:\s*\[[0-9]*\]\s\({{service}}\)\s/p' | awk '{print $7}'")
{% else %}
PID=$({{foreverPath}}forever --plain list | sed -n -e '/data:\s*\[[0-9]*\]\s\({{service}}\)\s/p' | awk '{print $7}')
{% endif %}

if [ -z "$PID" ]; then
echo "Could not get pid"
Expand All @@ -134,7 +170,11 @@ stop() {
#run in background, since recent changes in forever, now blocks stop call with SIGTERM is finished
#but we want to wait till some time and forcibly kill after elapsed time
#without background script, we could be waiting forever
{% if runAsUser %}
{{suprog}} - {{runAsUser}} -c "{{foreverPath}}forever stop {{service}} 2>&1 >/dev/null &"
{% else %}
{{foreverPath}}forever stop {{service}} 2>&1 >/dev/null &
{% endif %}

CURRENTWAITTIME=$KILLWAITTIME
# wait for some time before forcefully killing the process
Expand Down Expand Up @@ -170,7 +210,11 @@ status() {
#this is to ensure forever is able to find out the correct root every time
export FOREVER_ROOT={{foreverRoot}}

{% if runAsUser %}
STATUS=$({{suprog}} - {{runAsUser}} -c "export FOREVER_ROOT={{foreverRoot}};{% if envVarsNameValueArray|default(false) %}{% for v in envVarsNameValueArray %}export {{ v[0] }}=$(printf "%q" {{v[1]}});{% endfor %}{% endif %}{{foreverPath}}forever --plain list | sed 's/data:\(\s*\[[0-9]*\]\s*\({{service}}\)\s.*\)/\2-status:\1/;tx;d;:x'")
{% else %}
STATUS=$({{foreverPath}}forever --plain list | sed 's/data:\(\s*\[[0-9]*\]\s*\({{service}}\)\s.*\)/\2-status:\1/;tx;d;:x')
{% endif %}
if [ -z "$STATUS" ]; then
echo "{{service}} is not running"
RETVAL=3
Expand Down

0 comments on commit 58f0800

Please sign in to comment.