2

I recently moved a home-grown service I had running on a virtual machine (Debian 7.8) to a physical computer (Debian 8.5) and after the move, my init script stopped working. I understand there were some major changes to the init process etc. between Wheezy and Jessie, but so far as I can tell, the changes should still be compatible with my init script. I'm pretty sure I 'LSBized' the script per the directions on the Debian Wiki and stayed pretty close to the old skeleton example anyway.

I did a lot of bumping around, trying to figure out what was going on and I'm pretty sure that the script (usually?) is crashing - or at least stopping - while sourcing the functions at /lib/lsb/init-functions. I have no idea why that would happen.

After coming to this conclusion, I trimmed down the init script to a (pretty clumsy) shorter version that does not use the init-functions; it just forks a bash process into a sub-sub shell so it will be orphaned. That seems to work a bit better but it still has occasional issues with the new services set-up. I'm less interested in fixing this duct-tape-and-bailing-wire solution as I am figuring out why my more standard init script is misbehaved.

Any ideas?

Here's my old init script:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          myd
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Initscript for myService daemon
### END INIT INFO

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="myService daemon"
NAME=myd
DAEMON=/home/uname/myService/bin/$NAME
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
    # Return
    #   0 if daemon has been started
    #   1 if daemon was already running
    #   2 if daemon could not be started
    start-stop-daemon --start --quiet \
        --background \
        --pidfile $PIDFILE \
        --make-pidfile \
        --user uname \
        --chuid uname \
                --startas /bin/bash \
        --test \
        -- -c "exec $DAEMON >> /.myd/var/init.log 2>&1" \
        || return 1
    start-stop-daemon --start --quiet \
        --background \
        --pidfile $PIDFILE \
        --make-pidfile \
        --user uname \
        --chuid uname \
                --startas /bin/bash \
        -- -c "exec $DAEMON >> /home/uname/.myd/var/init.log 2>&1" \
                || return 2
    # Add code here, if necessary, that waits for the process to be ready
    # to handle requests from services started subsequently which depend
    # on this one.  As a last resort, sleep for some time.
    return 0
}

#
# Function that stops the daemon/service
#
do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
    #   other if a failure occurred
    start-stop-daemon --stop --quiet \
        --retry=TERM/30/KILL/5 \
        --pidfile $PIDFILE \
        --name $NAME
    RETVAL="$?"
    [ "$RETVAL" = 2 ] && return 2
    # Wait for children to finish too if this is a daemon that forks
    # and if the daemon is only ever run from this initscript.
    # If the above conditions are not satisfied then add some other code
    # that waits for the process to drop all resources that could be
    # needed by services started subsequently.  A last resort is to
    # sleep for some time.
    start-stop-daemon --stop --quiet --oknodo \
        --retry=0/30/KILL/5 \
        --exec $DAEMON
    [ "$?" = 2 ] && return 2

    # Many daemons don't delete their pidfiles when they exit.
    rm -f $PIDFILE
    return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
    #
    # If the daemon can reload its configuration without
    # restarting (for example, when it is sent a SIGHUP),
    # then implement that here.
    #
    start-stop-daemon --stop --signal 1 --quiet \
        --pidfile $PIDFILE \
        --name $NAME
    return 0
}

case "$1" in
  start)
    log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    case "$?" in
        0|1) log_end_msg 0
        ;;
        2) log_end_msg 1
        ;;
    esac
    ;;
  stop)
    log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    case "$?" in
        0|1) log_end_msg 0
        ;;
        2) log_end_msg 1
        ;;
    esac
    ;;
  status)
    status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
    ;;
  #reload|force-reload)
    #
    # If do_reload() is not implemented then leave this commented out
    # and leave 'force-reload' as an alias for 'restart'.
    #
    #log_daemon_msg "Reloading $DESC" "$NAME"
    #do_reload
    #log_end_msg $?
    #;;
  restart|force-reload)
    #
    # If the "reload" option is implemented then remove the
    # 'force-reload' alias
    #
    log_daemon_msg "Restarting $DESC" "$NAME"
    do_stop
    case "$?" in
      0|1)
        do_start
        case "$?" in
            0) log_end_msg 0 ;;
            1) log_end_msg 1 ;; # Old process is still running
            *) log_end_msg 1 ;; # Failed to start
        esac
        ;;
      *)
        # Failed to stop
        log_end_msg 1
        ;;
    esac
    ;;
  *)
    #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
    exit 3
    ;;
esac

:

And my new (clumsy) script that (mostly) works:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          myd
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Initscript for myService daemon
### END INIT INFO

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="myService daemon"
NAME=myd
DAEMON=/home/uname/myService/bin/$NAME
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

#
# Function that starts the daemon/service
#
do_start()
{
    (
        runuser -l uname "$DAEMON" >/dev/null 2>&1 &
        echo "$!" > "$PIDFILE"
    )
    return 0
}

#
# Function that stops the daemon/service
#
do_stop()
{
    PID="$(cat $PIDFILE)"
    kill "$PID" >/dev/null 2>&1

    #Give myd 5 seconds for an orderly shutdown
    for i in $(seq 1 5); do
        sleep 1
        if [ ! kill -0 "$PID" >/dev/null 2>&1 ]; then
            rm "$PIDFILE"
            return 0;
        fi
    done

    #No more playing nice: KILL THE DAEMON
    if [ kill -0 "$PID" >/dev/null 2>&1 ]; then
        echo "Orderly shutdown failed, sending kill signal"
        kill -9 "$PID" >/dev/null 2>&1
    fi

    #Give myd 5 more seconds then fail
    for i in $(seq 1 5); do
        sleep 1
        if [ ! kill -0 "$PID" >/dev/null 2>&1 ]; then
            rm "$PIDFILE"
            return 0;
        fi
    done

    #FAILED
    echo "Failed to kill $DESC $NAME"
    return 1
}

case "$1" in
  start)
    echo "Starting $DESC $NAME" >&2
    do_start
    ;;
  stop)
    echo "Stopping $DESC $NAME" >&2
    do_stop
    ;;
  restart|force-reload)
    #
    # If the "reload" option is implemented then remove the
    # 'force-reload' alias
    #
    echo "Restarting $DESC $NAME" >&2
    do_stop
    do_start
    ;;
  *)
    #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
    exit 3
    ;;
esac

:

As an aside, after much googling and head-scratching, I'm still not very aware of the new system for dependency booting etc. Can somebody point me in the direction of some (current) documentation for this stuff so I can self educate a little better? I hate being in the dark about the systems I'm working with.

Thanks!

1 Answers1

2

You should probably consider converting to a native systemd Unit as your init script is fairly typical, and you could for example entirely remove the stop code and let systemd's default mechanism work for you, since you have a PIDFILE where it can check that the daemon is running and so knows how to kill it (see man systemd.kill).

systemd has a large learning curve, but you can start with this blog on converting to systemd by the man himself, Lennart Poettering.

Normally, your init script should continue to work, due to the built-in systemd compatibility. See man systemd-sysv-generator and look for the systemd Unit file wrapper for your myd script in /run/systemd/generator.late/myd.service.

Check on the status and log of your script with

sudo systemctl status myd

and stop and start it similarly. Note the gotcha I described in this answer where giving a second start to something systemd thinks is already started will have no effect.

See this wiki for converting System V or upstart scripts like yours to native systemd Units.

meuh
  • 51,383
  • These are great resources! I'll set this up and test as soon as possible then select this as the answer once I get something up and running. So this is where everything is moving to? Interesting... – Justin Frahm Sep 06 '16 at 17:26