From the Terminal

Making Any Terminal Command Into a Background Service Daemon

This guide will show you to make a bash script that is self aware about it's location on the file system and therefore able to be used as a global terminal command from any current directory.

It will have the ability to take arguments as commands and have the ability to act on those commands with a set of basic daemon related functions such as start, stop, restart, status, version, and help.

The bash script will also have the ability to save a .pid file, read from the file, and use the file as necessary to send basic signals to the background running process. The script will collect any output from the running process to a log which you can monitor with tail.

First thing's first. Let's create our bash script. Use touch to create a new plaintext file.

user@machine:~# touch servicedaemon

Don't forget to give the file executable permission with this command.

user@machine:~# chmod +x servicedaemon

Now open the file in your favorite text editor and lets start putting our script together. It might be beneficial for you to leave a terminal open so you can play with your script as we go along.

#!/bin/bash

The script must contain this on the first line to tell bash what script interpreter to use. In this case we're using bash of course.

Next we setup a way to handle arguments for our command. In this case we want to show usage information when someone doesn't provide any arguments but also provide basic arguments that are known to most people like -h and -v for help and version information.

#!/bin/bash

# source: https://stackoverflow.com/questions/59895/getting-the-source-directory-of-a-bash-script-from-within
# this detects the real location of the script even if it's linked
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $DIR

me="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")";

pidfile="$me.pid"

executable="myservice.sh"

function loadenv() {
    if [ -f ".env" ]
    then 
        envtype=$(cat .env)
        envfile="conf/$envtype"

        if [ -f $envfile ]
        then
            echo "Loading environment from $envfile"
            . $envfile
        else
            echo "$envfile not found."
        fi
    else
        echo 'Environment type definition not found. Attempting production.'
        if [ -f 'conf/production' ]
        then
            . conf/production
        else 
            echo 'Production environment definition not found.'
        fi
    fi
}

function status() {
    if [ -f $pidfile ]
    then 
        PID=`cat $pidfile`
        if ps -p $PID > /dev/null
        then
            echo "$me is running on $PID"
        else
            echo "$me is not running"
        fi
    else
        echo 'PID file not found.'
    fi
}

function start() {
    loadenv
    log=$DIR/$me.log

    if [ -f $pidfile ]; then 
        echo "PID file found: " && cat $pidfile && exit 0
    fi

    daemon() {
        echo "Starting $me"
        $executable >> $log 2>&1 &
        pid="$!"
        echo $pid > $pidfile
    }
    daemon
}

function stop() {
    echo 'Stopping $me'
    PID=`cat $pidfile`

    if ps -p $PID > /dev/null
    then
        kill `cat $pidfile`
    fi
    rm $pidfile
}

function version() {
    echo '0.0.1'
    exit
}



function usage() {
    echo "Usage: $me [status|start|reload|stop]" 1>&2;
    exit;
}

case $1 in
    status)
        status
        exit
    ;;
    start)
        start
        exit
    ;;
    reload)
        stop
        start
        exit
    ;;
    stop)
        stop
        exit 
    ;;
    -h|-\?|--help)
        usage
        exit
    ;;
    -v|--version)
        version
        exit
    ;;
    *)
        usage
        exit
    ;;
esac
shift

At this point you can run ./servicedaemon from terminal and see the usage information pop up.

user@machine:~$ ./servicedaemon 
Usage: servicedaemon [status|start|reload|stop]
user@machine:~$ ./servicedaemon status
PID file not found.
user@machine:~$ ./servicedaemon -v
0.0.1
user@machine:~$ ./servicedaemon -h
Usage: servicedaemon [status|start|reload|stop]

Now just replace executable on line 18 with what you want to run. A log of any output will be placed in the same folder with the name of the executable file with a log extension. You can tail it to see output. A PID file will be created in the same folder.