Starting service as non root user - bash

Can someone help us understand how to properly start our programs service as the services user (marty for example).
We're using init.d to start our process (java application), but when the system(s) boot (Ubuntu and Debian) because the service script is run as root, we're having problems where the application is starting as root too and the PID file is being created by root which is messing things up.
We tried using sudo, but this is not a great solution as we dont want the sudo process running too with our application as a child process plus we need this to work on other systems that may not have sudo. Please help.

In the init script, you can check the $UID of the calling user.
If it is root, you can run the service with "runuser". If it is marty - run it directly, if it is another user - exit with error for example.
Here's some example bash (untested):
start() {
if [ $UID -eq 0 ]; then
runuser -s /bin/bash marty -c "$DAEMON start $DAEMONOPTS"
elif [ "$USER" = "marty" ]; then
$DAEMON start $DAEMONOPTS
else
print "Please run me with root or marty."
exit 2
fi
}
Same for stop and any other functions as required.
Feel free to modify the runuser command as necessary, maybe you won't need the shell for example.

Use start-stop-daemon which accepts a user name and an executable as parameters.

Related

using environment variables in service script

I am facing an issue using environment variables in my service script.
In my services script, i am using an environmental variable i.e. INSTALL_DIR whose value may vary on different system. I have to get the installation directory from $INSTALL_DIR and then i have to start the service. when i am running the service script the environment variable is not sourced at all.
Is it possible to source the installation directory from INSTALL_DIR environment variable. another option i can think is dynamically creating the service script using INSTALL_DIR environment variable.
echo "INSTALL DIR: ${INSTALL_DIR}"
name=`basename $0`
pid_file="/var/run/$name.pid"
get_pid() {
cat "$pid_file"
}
is_running() {
[ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
}
Start()
{
echo "Starting Application"
if is_running; then
echo "[`get_pid`] Already Started"
else
if [ -z "$user" ]; then
nohup $INSTALL_DIR/bin/application 2>&1 &
else
nohup sudo -u "$user" $cmd 1> $INSTALL_DIR/bin/application 2>&1 &
fi
echo $! > "$pid_file"
if ! is_running; then
echo "Unable to start, see logs"
exit 1
fi
echo "[`get_pid`] Started"
fi
}
I am trying to run the application using following command
service application start
In my services script ... I have to get the installation directory from $INSTALL_DIR and then i have to start the service.
Your question isn't really about shell scripting, but about your system's startup. Unfortunately that process varies by Linux distribution, and tends to be poorly documented.
For example, man service says, service runs a System V init script or upstart job in as predictable an environment as possible, removing most environment variables and with the current working directory set to /., but man upstart says:
$ man -k upstart
upstart: nothing appropriate.
Not only that, but the service manpage specifically lists the environment variables a script will start with. Needless to say, yours isn't among them.
The traditional approach to parameterizing startup scripts is to put the information in a known file, normally in /etc, and reference that file in the script. In your case, you could do something like:
INSTALL_DIR=$(cat /etc/my-install-dir.cfg)
and then proceed accordingly.
There might be ways to coerce your startup to support other environment variables. But, sooner or later, the information you need has to be stored somewhere on the filesystem. It seems to me the simplest approach is to reserve a filename to hold that information, and read that file directly.
Use this below code in your script.
if [[ -z "${INSTALL_DIR}" ]]; then
echo "INSTALL_DIR is undefined"
else
INSTALL_DIR=<<your installation directory>>
fi

Can Cron Jobs Use Gnome-Open?

I am running Ubuntu 11.10 (Unity interface) and I created a Bash script that uses 'gnome-open' to open a series of web pages I use every morning. When I manually execute the script in the Terminal, the bash script works just fine. Here's a sample of the script (it's all the same so I've shortened it):
#!/bin/bash
gnome-open 'https://docs.google.com';
gnome-open 'https://mail.google.com';
Since it seemed to be working well, I added a job to my crontab (mine, not root's) to execute every weekday at a specific time.
Here's the crontab entry:
30 10 * * 1,2,3,4,5 ~/bin/webcheck.sh
The problem is this error gets returned for every single 'gnome-open' command in the bash script:
GConf-WARNING **: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: No D-BUS daemon running
Error: no display specified
I did some searching to try and figure this out. The first thing I tried was relaunching the daemon using SIGHUP:
killall -s SIGHUP gconfd-2
That didn't work so I tried launching the dbus-daemon using this code from the manpage for dbus-launch:
## test for an existing bus daemon, just to be safe
if test -z "$DBUS_SESSION_BUS_ADDRESS" ; then
## if not found, launch a new one
eval `dbus-launch --sh-syntax --exit-with-session`
echo "D-Bus per-session daemon address is: $DBUS_SESSION_BUS_ADDRESS"
fi
But that didn't do anything.
I tried adding simply 'dbus-launch' at the top of my bash script and that didn't work either.
I also tried editing the crontab to include the path to Bash, because I saw that suggestion on another thread but that didn't work.
Any ideas on how I can get this up and running?
Here is how the problem was solved. It turns out the issue was primarily caused by Bash not having access to an X window session (or at least that's how I understood it). So my problem was solved by editing my crontab like so:
30 10 * * 1,2,3,4,5 export DISPLAY=:0 && ~/bin/webcheck.sh
The "export DISPLAY=:0" statement told cron which display to use. I found the answer on this archived Ubuntu forum after searching for "no display specified" or something like that:
http://ubuntuforums.org/archive/index.php/t-105250.html
So now, whenever I'm logged in, exactly at 10:30 my system will automatically launch a series of webpages that I need to look at every day. Saves me having to go through the arduous process of typing in my three-letter alias every time :)
Glad you asked!
It depends on when it is run.
If the Gnome GDM Greeter is live, you can use the DBUS session from the logon dialog, if you will. You can, e.g., use this to send notifications to the logon screen, if no-one is logged in:
function do_notification
{
for pid in $(pgrep gnome-session); do
unset COOKIE
COOKIE="$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$pid/environ|cut -d= -f2-)"
GNUSER="$(ps --no-heading -o uname $pid)"
echo "Notifying user $GNUSER (gnome-session $pid) with '$#'"
sudo -u "$GNUSER" DBUS_SESSION_BUS_ADDRESS="$COOKIE" /usr/bin/notify-send -c "From CRON:" "$#"
done
unset COOKIE
}
As you can see the above code simply runs the same command (notify-send) on all available gnome-sessions, when called like:
do_notification "I wanted to let you guys know"
You can probably pick this apart and put it to use for your own purposes.

Bash script to kill and restart Hudson

I am a novice at Bash scripting but I'm a quick learner. Usually. I'm trying to write a script to kill and restart an instance of Hudson--it needs to be restarted to pick up changes in environment variables. What I have so far:
#!/bin/bash
h=`pgrep -f hudson`
if test "$h" != ""; then
kill $h
while [ "$h" != "" ]; do
sleep 1
unset h
h=`pgrep -f hudson`
done
fi
java -jar ~/hudson/hudson.war &
The script correctly determines the running Hudson instance's PID and kills it. However, it just waits after the "kill" line and doesn't proceed. If I hit a key there it completes killing the process and exits the script, never even getting to the while loop. Clearly I'm missing something about how the process should be killed. It's not that the Hudson process is hung and not responding to "kill"; it exits normally, just not until I intervene.
I'm also sure this could be much more efficient but right now I would just like to understand where I'm going wrong.
Thanks in advance.
This represents some straightforward improvements to your script:
#!/bin/bash
h=$(pgrep -f hudson) # $() is preferred over backticks
if [[ -n $h ]]; then # this checks whether a variable is non-empty
kill $h
while [[ -n $h ]]; do
sleep 1
h=$(pgrep -f hudson) # it's usually unnecessary to unset a variable before you set it
done
fi
java -jar ~/hudson/hudson.war &
However, it's likely that this is all you need (or use the provided facility that mrooney referred to):
while pkill hudson; do sleep 1; done
java -jar ~/hudson/hudson.war &
How about being nice to Hudson and let it shut down itself. I found the following statement in the Hudson forum:
I added http://server/hudson/exit to
1.161. Accessing this URL will shutdown the VM that runs Hudson.
You can call the URL with wget. You can still kill Hudson if it doesn't shut down in an appropriate time.
EDIT: I just stumbled over another thread, with interesting restart options. It uses commands of the build in Winstone server. Not sure if it will pick up changes to environment variables.
If you are using Hudson via an RPM, it comes with an init script already. If not, I'd take a look at them and see if you can base your script off of them: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main/rpm/SOURCES/ (guest//guest).

Can a bash script tell if it's being run via cron?

Not having much luck Googling this question and I thought about posting it on SF, but it actually seems like a development question. If not, please feel free to migrate.
So, I have a script that runs via cron every morning at about 3 am. I also run the same scripts manually sometimes. The problem is that every time I run my script manually and it fails, it sends me an e-mail; even though I can look at the output and view the error in the console.
Is there a way for the bash script to tell that it's being run through cron (perhaps by using whoami) and only send the e-mail if so? I'd love to stop receiving emails when I'm doing my testing...
you can try "tty" to see if it's run by a terminal or not. that won't tell you that it's specifically run by cron, but you can tell if its "not a user as a prompt".
you can also get your parent-pid and follow it up the tree to look for cron, though that's a little heavy-handed.
I had a similar issue. I solved it with checking if stdout was a TTY. This is a check to see if you script runs in interactive mode:
if [ -t 1 ] ; then
echo "interacive mode";
else
#send mail
fi
I got this from: How to detect if my shell script is running through a pipe?
The -t test return true if file descriptor is open and refers to a terminal. '1' is stdout.
Here's two different options for you:
Take the emailing out of your script/program and let cron handle it. If you set the MAILTO variable in your crontab, cron will send anything printed out to that email address. eg:
MAILTO=youremail#example.com
# run five minutes after midnight, every day
5 0 * * * $HOME/bin/daily.job
Set an environment variable in your crontab that is used to determine if running under cron. eg:
THIS_IS_CRON=1
# run five minutes after midnight, every day
5 0 * * * $HOME/bin/daily.job
and in your script something like
if [ -n "$THIS_IS_CRON" ]; then echo "I'm running in cron"; else echo "I'm not running in cron"; fi
Why not have a command line argument that is -t for testing or -c for cron.
Or better yet:
-e=email#address.com
If it's not specified, don't send an email.
I know the question is old, but I just came across the same problem. This was my solution:
CRON=$(pstree -s $$ | grep -q cron && echo true || echo false)
then test with
if $CRON
then
echo "Being run by cron"
else
echo "Not being run by cron"
fi
same idea as the one that #eruciform mentioned - follows your PID up the process tree checking for cron.
Note: This solution only works specifically for cron, unlike some of the other solutions, which work anytime the script is being run non-interactively.
What works for me is to check $TERM. Under cron it's "dumb" but under a shell it's something else. Use the set command in your terminal, then in a cron-script and check it out
if [ "dumb" == "$TERM" ]
then
echo "cron"
else
echo "term"
fi
I'd like to suggest a new answer to this highly-voted question. This works only on systemd systems with loginctl (e.g. Ubuntu 14.10+, RHEL/CentOS 7+) but is able to give a much more authoritative answer than previously presented solutions.
service=$(loginctl --property=Service show-session $(</proc/self/sessionid))
if [[ ${service#*=} == 'crond' ]]; then
echo "running in cron"
fi
To summarize: when used with systemd, crond (like sshd and others) creates a new session when it starts a job for a user. This session has an ID that is unique for the entire uptime of the machine. Each session has some properties, one of which is the name of the service that started it. loginctl can tell us the value of this property, which will be "crond" if and only if the session was actually started by crond.
Advantages over using environment variables:
No need to modify cron entries to add special invocations or environment variables
No possibility of an intermediate process modifying environment variables to create a false positive or false negative
Advantages over testing for tty:
No false positives in pipelines, startup scripts, etc
Advantages over checking the process tree:
No false positives from processes that also have crond in their name
No false negatives if the script is disowned
Many of the commands used in prior posts are not available on every system (pstree, loginctl, tty). This was the only thing that worked for me on a ten years old BusyBox/OpenWrt router that I'm currently using as a blacklist DNS server. It runs a script with an auto-update feature. Running from crontab, it sends an email out.
[ -z "$TERM" ] || [ "$TERM" = "dumb" ] && echo 'Crontab' || echo 'Interactive'
In an interactive shell the $TERM-variable returns the value vt102 for me. I included the check for "dumb" since #edoceo mentioned it worked for him. I didn't use '==' since it's not completely portable.
I also liked the idea from Tal, but also see the risk of having undefined returns. I ended up with a slightly modified version, which seems to work very smooth in my opinion:
CRON="$( pstree -s $$ | grep -c cron )"
So you can check for $CRON being 1 or 0 at any time.

Tell if a user has SUed in a shell script?

I have a script which executes a git-pull when I log in. The problem is, if I su to a different user and preserve my environment with an su -lp, the script gets run again and usually gets messed up for various reasons because I'm the wrong user. Is there a way to determine in a shell script whether or not I'm currently SUing? I'm looking for a way that doesn't involve hard coding my username into the script, which is my current solution. I use Bash and ZSH as shells.
You could use the output of the who command with the id command:
WHO=`who am i | sed -e 's/ .*//'`
ID_WHO=`id -u $WHO`
ID=`id -u`
if [[ "$ID" = "$ID_WHO" ]]
then
echo "Not su"
else
echo "Is su"
fi
if test "$(id -u)" = "0";
: # commands executed for root
else
: # commands executed for non root
fi
If you are changing user identities with an suid executable, your real and effective user id will be different. But if use use su (or sudo), they'll both be set to the new user. This means that commands that call getuid() or geteuid() won't be useful.
A better method is to check who owns the terminal the script is being run on. This obviously won't work if the process has detached from it's terminal, but unless the script is being run by a daemon, this is unlikely. Try stat -c %U $(tty). I believe who am i will do the same thing on most Unix-like OSes as well.
You can use "$UID" environment variable.
If its value is ZERO, then the user has SUDOed.. Bcos root as $UID==0
Well.... on linux, if I su to another user the process su is in the new user's process list.
sudo... doesn't leave such pleasant things for you.
I'm using zsh... but I don't think anything in this is shell specific.
if:
%ps | grep " su$"
returns anything, then you're running in an su'd shell.
Note: there is a space before su$ in that to exclude command simply ending in su. Doesn't guard against any custom program/script called su, though.

Resources