Can't start service with sudo since root user has no access to Ruby - ruby

tl;dr
Trying to run a service which needs ruby to run. But, Ruby is installed with RVM where the root user can't seem to access it, producting the error /usr/bin/env: ruby: No such file or directory. rvmsudo doesn't work.
Background
I have an init.d script which is supposed to start a unicorn server. I keep the script in the config directory of my rails application and symlink to it from /etc/init.d/busables_unicorn.
$ ls -l /etc/init.d/busables_unicorn
-> lrwxrwxrwx 1 root root 62 2012-01-12 15:02 busables_unicorn -> /home/dtuite/dev/rails/busables/current/config/unicorn_init.sh
This script (which is appended to the bottom) essentially just runs the following command:
$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb -E production
where $APP_ROOT is the path to the root of my rails application. Every time that command is executed in that init.d script, it is supposed to do so as the dtuite (my deploy) user. To accomplish that, I call su -c "$CMD" - dtuite rather than just $CMD.
/bin/unicorn is a "binscript" which was generated by Bundler and config/unicorn.rb contains some configuration options which are passed to it.
The unicorn binscript looks like this:
#!/usr/bin/env ruby
#
# This file was generated by Bundler.
#
# The application 'unicorn' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require 'rubygems'
require 'bundler/setup'
load Gem.bin_path('unicorn', 'unicorn')
Now, I'm trying to start my unicorn service by running:
sudo service busables_unicorn start
That however produces the error:
/usr/bin/env: ruby: No such file or directory
I believe that this is happening because I'm running the service as the root user but RVM has installed ruby under the dtuite user's home directory and the root user has no access to it.
dtuite#localhost:$ which ruby
-> /home/dtuite/.rvm/rubies/ruby-1.9.3-p0/bin/ruby
dtuite#localhost:$ su
Password:
root#localhost:$ which ruby
root#localhost:$
Question
What do I need to do to make this work?
My Setup
- ubuntu 11.10
- ruby 1.9.3p0 (2011-10-30 revision 33570) [i686-linux]
- nginx: nginx version: nginx/1.0.5
What I've tried
rvmsudo
$ rvmsudo service busables_unicorn start
/usr/bin/env: ruby: No such file or directory
rvm-auto-ruby
$ sudo service cakes_unicorn start
-> [sudo] password for dtuite:
-> -su: /home/dtuite/dev/rails/cakes/current/bin/unicorn: rvm-auto-ruby: bad interpreter: No such file or directory
This other question may help but to be honest I don't really understand it.
Appendix
The busables_unicorn script in it's entirety.
# INFO: This file is based on the example found at
# https://github.com/defunkt/unicorn/blob/master/examples/init.sh
# Modifications are courtesy of Ryan Bate's Unicorn Railscast
# Install Instructions:
# sudo ln -s full-path-to-script /etc/init.d/APP_NAME_unicorn
# Once installed, an app's unicorn can be reloaded by running
# sudo service APP_NAME_unicorn restart
#!/bin/sh
set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals
# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/dtuite/dev/rails/busables/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
# in order to access this, we need to first run
# 'bundle install --binstubs'. THis will fill our
# app/bin directory with loads of stubs for executables
# this is the command that is run when we run this script
CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
# we don't need an init config because this file does it's job
action="$1"
set -u
old_pid="$PID.oldbin"
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $old_pid && kill -$1 `cat $old_pid`
}
case $action in
start)
sig 0 && echo >&2 "Already running" && exit 0
# NOTE: We have to change all these lines.
# Otherwise, the app will run as the root user
su -c "$CMD" - dtuite
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig HUP && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
su -c "$CMD" - dtuite
;;
upgrade)
if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
then
n=$TIMEOUT
while test -s $old_pid && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $old_pid
then
echo >&2 "$old_pid still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
su -c "$CMD" - dtuite
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac

It sounds like su isn't spawning a shell that reads the profile files that normally setup the rvm environment.
I'd try changing the command you run to
source "/home/dtuite/.rvm/scripts/rvm" && $APP_ROOT/bin/unicorn...

Try adding your ruby path somewhere at the beginning of the start script, in an export statement like this:
export PATH=/home/dtuite/.rvm/rubies/ruby-1.9.3-p0/bin:$PATH

Related

Shell scripting - authenticate once for many privilege escalations

Suppose I have a script commands.sh that looks something like the following:
command1 # Must be run as root
command2 # Absolutely cannot be run as root
command3 # Should be run as unprivileged user, but can be run as root
...
...
If we run commands.sh, then command1 will complain. If we run sudo commands.sh, then command2 will complain. We could edit commands.sh to look like the following,
sudo command1
command2
command3
...
...
That is, inserting sudos in the appropriate places. With the default sudo config (15 minute authentication persistence), this works great (assuming the script takes less than 15 minutes to run). However, if the script is longer, or we have authentication persistence disabled, then I feel there's not an obvious way forward. Additionally, some systems might not even use sudo to authenticate - they may use doas or something else instead.
Is there any (POSIX-y?) way of creating a script, authenticating once at the start, and then persisting that authentication through the script when necessary, without the use of sudo's persistence?
One possibility is to require the script to be run via sudo, but then use sudo -u within the script to revert (/demote) to the original user for commands that shouldn't be run as root. It'd probably be a good idea to include some sanity-checking at the beginning of the script to make sure it's being run from the right environment:
#!/bin/bash
if [[ "$EUID" != 0 ]]; then
# The script is not running as root
echo "This script must be run via sudo (as root)" >&2
exit 1
# Optional: replace exit 1 with:
# exec sudo "$0" "$#" # Re-run this script via sudo
# exit $? # In case the exec fails
elif [[ -z "$SUDO_USER" ]]; then
# The script is running as root, but doesn't have $SUDO_USER
# (the original user who ran sudo) to revert back to for
# unprivileged operations
echo "This script must be run via sudo, not directly as root" >&2
exit 1
elif [[ "$SUDO_USER" = root || "$SUDO_UID" = 0 ]]; then
# The script is running as root via sudo, but $SUDO_USER is
# *also* root, so is not suitable to revert back to for
# unprivileged operations
echo "This script must be run via sudo, from a regular (non-root) account" >&2
exit 1
fi
command1 # Must be run as root
sudo -u "$SUDO_USER" command2 # Absolutely cannot be run as root
sudo -u "$SUDO_USER" command3 # Should be run as unprivileged user, but can be run as root

Run go app by service

In CentOS 6.8 I have a golang app , that run in command go run main.go and I need to create a system service to run it in boot like service httpd.
I know that I have to create file like /etc/rc.d/init.d/httpd But I don't know how to do it to run that command.
First, you will need to build your Go binary and put it in your path.
go install main.go
If your "main" file is called main, go install will place a binary called "main" in your path, so I suggest you rename your file to whatever you call your project/server.
mv main.go coolserver.go
go install coolserver.go
You can run coolserver to make sure everything is fine. It will if you have your $GOPATH setup properly.
Here it is an example of a init.d service called service.sh
#!/bin/sh
### BEGIN INIT INFO
# Provides: <NAME>
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: <DESCRIPTION>
### END INIT INFO
SCRIPT=<COMMAND>
FLAGS="--auth=user:password"
RUNAS=<USERNAME>
PIDFILE=/var/run/<NAME>.pid
LOGFILE=/var/log/<NAME>.log
start() {
if [ -f /var/run/$PIDNAME ] && kill -0 $(cat /var/run/$PIDNAME); then
echo 'Service already running' >&2
return 1
fi
echo 'Starting serviceā€¦' >&2
local CMD="$SCRIPT $FLAGS &> \"$LOGFILE\" & echo \$!"
su -c "$CMD" $RUNAS > "$PIDFILE"
echo 'Service started' >&2
}
stop() {
if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping serviceā€¦' >&2
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
echo 'Service stopped' >&2
}
uninstall() {
echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
local SURE
read SURE
if [ "$SURE" = "yes" ]; then
stop
rm -f "$PIDFILE"
echo "Notice: log file is not be removed: '$LOGFILE'" >&2
update-rc.d -f <NAME> remove
rm -fv "$0"
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
uninstall)
uninstall
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart|uninstall}"
esac
Copy to /etc/init.d:
cp "service.sh" "/etc/init.d/coolserver"
chmod +x /etc/init.d/coolserver
Remember to replace
<NAME> = coolserver
<DESCRIPTION> = Describe your service here (be concise)
<COMMAND> = /path/to/coolserver
<USER> = Login of the system user the script should be run as
Start and test your service and install the service to be run at boot-time:
service coolserver start
service coolserver stop
update-rc.d coolserver defaults
I assume you tried to use apache web server. Actually, Go web server is enough itself. Main purpose is to run Go web server in system service.So, you can use tmux https://tmux.github.io/ or nohup to run as system service. You can also use apache or nginx web server as proxy.

Require in Init.d script

I've done an init.d script in order to start a newrelic plugin as daemon. The problem is that when I run service rb_nr_agent start it has some errors related with "require". Output:
[root#device newrelic_rb_plugin]# /usr/local/rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- snmp (LoadError)
from /usr/local/rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from /opt/newrelic_rb_plugin/newrelic_redborder_agent:5:in `<main>'
[root#device newrelic_rb_plugin]# ./rb_nr_agent start Starting rb_nr_agent:[ OK ]
It doesn't start properly. When I run the same script but in the root path of the project it doesn't have any error and it works fine. The init.d is a copy of that one. Here you have the start option of the script:
start() {
RESULT=`ps aux | grep $executable | grep -c -v grep`
if [ "${RESULT:-null}" -ge "1" ]; then
echo "$prog is currently running"
else
echo -n "Starting $prog: "
/opt/newrelic_rb_plugin/newrelic_redborder_agent > /dev/null &
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
echo_success
else
echo_failure; failure
RETVAL=1
fi
echo
fi
return $RETVAL }
Error text suggests that you're using RVM, but it is loaded only at user login and thus is not available in init-scripts by default.
Use rvm do to run a command with rvm enabled:
/usr/local/rvm/bin/rvm ruby-2.1.2 do /opt/newrelic_rb_plugin/newrelic_redborder_agent > /dev/null &
(you may need to correct for the exact installed ruby version and gemset name, if any is used)

How to run ruby via sudo

Hi I am creating a new init script for monitoring a ruby program .
NAME=differ
FILE_PATH=/home/amer/Documents/ruby_projects/differ/
PIDFILE=/home/amer/pid/differ.pid
PID=$$
EXEC='/home/amer/.rvm/rubies/ruby-2.0.0-p247/bin/ruby main_scheduler.rb'
do_start(){
echo "started"
cd $FILE_PATH
pwd
$EXEC >> init_log/output.log &
echo $! > $PIDFILE
echo "---------"
echo `cat $PIDFILE`
echo "all are DONE "
}
do_stop(){
PID=`cat $PIDFILE`
echo $PID
if ps -p $PID ; then
kill -6 $PID
echo "it is over"
else
echo "its not running"
fi
}
case "$1" in
start)
echo $$
echo -n "Starting script differ "
do_start
;;
stop)
echo "stopping ...."
do_stop
;;
status)
PID=`cat $PIDFILE`
echo "STATUS $PID"
if ps -p $PID -f; then
echo running
else
echo not running
fi
;;
restart|reload|condrestart)
do_stop
do_start
;;
*)
echo "Usage: /etc/init.d/blah {start|stop}"
exit 1
;;
esac
exit 0
And my monit process is
check process differ with pidfile /home/amer/pid/differ.pid
if changed pid then exec "/etc/init.d/differ start"
start program = "/etc/init.d/differ start"
stop program = "/etc/init.d/differ stop"
if 5 restarts within 5 cycles then timeout
But when I execute start service in my monit the status was "Execution failed" and i checked the log file of monit it said
info : 'differ' start: /bin/bash
error : 'differ' failed to start
error : 'differ' process is not running
When I analyzed the root of the problem . the reason was monit is running as root and the script which executes ruby will be executing as sudo /etc/init.d/differ.sh start but ruby is installed only in user 'amer' . I have tried
sudo -u amer $EXEC >>init_log/output.log &
it displayed the error as
amer#amer-Inspiron-1525:~$ /home/amer/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require': cannot load such file -- bundler/setup (LoadError)
from /home/amer/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
from main_scheduler.rb:2:in `<main>'
Please help in this problem . I have two ruby versions.
/home/amer/.rvm/rubies/ruby-2.0.0-p247/bin/ruby
/home/amer/.rvm/bin/ruby
It looks like your environment is missing. Replace
sudo -u amer $EXEC >>init_log/output.log &
with
su -s /bin/bash - amer -c "$EXEC >> init_log/output.log 2>&1" &
This should setup your shell environment properly. If you ran sudo .. >> log before, the log file might be owned by root. Change that or it will fail. I also added the redirect of STDERR to STDOUT, since you are probably interested in error messages.
after a long struggle i found the solution for this problem .
Two things must be done
1) EXPORT your PATH,GEM_HOME,GEM_PATH in the script
export PATH="/home/amer/.rvm/gems/ruby-2.0.0-p247#rails329/bin:/home/amer/.rvm/gems/ruby-2.0.0-p247#global/bin:/home/amer/.rvm/rubies/ruby-2.0.0-p247/bin:/home/amer/.rvm/bin:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
export GEM_HOME=/home/amer/.rvm/gems/ruby-2.0.0-p247#rails329
export GEM_PATH=/home/amer/.rvm/gems/ruby-2.0.0-p247#rails329:/home/amer/.rvm/gems/ruby-2.0.0-p247#global
2) USE rvmsudo bundle exec ruby "filename" (use full path)
rvmsudo -u amer /home/amer/.rvm/gems/ruby-2.0.0-p247#rails329/bin/bundle exec /home/amer/.rvm/rubies/ruby-2.0.0-p247/bin/ruby main_scheduler.rb&
it worked for me . hope it does for everyone .
Here's what I do when I want to run ruby scripts in init:
I switch to super user and install rvm. This won't cause problems with your single user installation.
I put this in the init script:
/usr/local/rvm/bin/rvm-shell 'yourgemset' -c 'ruby pathtoyourscript/yourscript.rb'
Example:
/usr/local/rvm/bin/rvm-shell 'jruby-1.7.4' -c 'ruby /home/someone/service.rb'
Hint: all the necessary gems need to be installed in that gemset.
The proper way of doing all this is to create an rvm wrapper script (see example) but I find my method easier for a simple setup where there aren't many gemsets.

rc.d start does not terminate?

So I wrote the Arch Linux rc.d script for mongod daemon (following an example), but when I do:
sudo rc.d start mongod
it just gets stuck on:
:: Starting /usr/bin/mongod [BUSY]
and never transitions to "DONE" phase. Any tips?
Here is my script:
#!/bin/bash
# import predefined functions
. /etc/rc.conf
. /etc/rc.d/functions
# Point to the binary
DAEMON=/usr/bin/mongod
# Get the ARGS from the conf
. /etc/conf.d/crond
# Function to get the process id
PID=$(get_pid $DAEMON)
case "$1" in
start)
stat_busy "Starting $DAEMON"
# Check the PID exists - and if it does (returns 0) - do no run
[ -z "$PID" ] && $DAEMON $ARGS &> /dev/null
if [ $? = 0 ]; then
add_daemon $DAEMON
stat_done
else
stat_fail
exit 1
fi
;;
stop)
stat_busy "Stopping $DAEMON"
kill -HUP $PID &>/dev/null
rm_daemon $DAEMON
stat_done
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "usage: $0 {start|stop|restart}"
esac
I've looked at how apache does it, but I can't figure out what they are doing that's different. Here's a piece of their httpd script:
case "$1" in
start)
stat_busy "Starting Apache Web Server"
[ ! -d /var/run/httpd ] && install -d /var/run/httpd
if $APACHECTL start >/dev/null ; then
add_daemon $daemon_name
stat_done
else
stat_fail
exit 1
fi
;;
For one thing, you are passing an $ARGS variable that is never actually defined. You will probably want to either pass some configuration options, or the location of a mongodb.conf file using the -f or --config option, to inform the daemon of the location of your database, log file, IP bindings, etc.
The mongod defaults assume that you database location is /data/db/. If this does not exist, or the daemon does not have permissions to that location, then the init script will fail.
You should probably also run the daemon with a user account other than yourself or root (the default pacman package creates a user named mongodb), and give this user read/write access to the data path and log file.
[ -z "$PID" ] && /bin/su mongodb -c "/usr/bin/mongod --config /etc/mongodb.conf --fork" > /dev/null
I would suggest referring to the mongodb init script provided in the Arch Community package, and comparing that to what you have here. Or, install MongoDB using pacman, which sets all of this up for you.
If all else fails, add some 'echo' commands inside of your if and else blocks to track down exactly where the init script is hanging, check mongodb's logs, and report back to us.

Resources