[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: init script config files



Joey Hess wrote:
> 
> tony mancill wrote:
> > While we're discussing this, I'd like to hear comments on the idea of
> > using an /etc/rc.config.d/$package scheme, like that in HP-UX.  This file
[...]
>
> I'll try to summarize the rest of the thread:
> 
> 1. The files should include nothing but simple shell variables, to prevent
> 2. If we move to this type of file, we have to worry about what happens if a
>    user modified it, it is a conffile, and a new version of a package adds a
>    variable to it; the user meanwhile has no such variable in their modified
>    file, and things die a horrible flaming death (eg, rm -rf /$NEWVAR)

There should not be any problems like this.  [Suggestion below]

> Anyway, I've crossposted to debian-policy, because the policy manual
> doesn't currently mention this at all, and I think it should. I think we
> should come up with a policy proposal integrating the ideas from the
> thread. I'll try to work something up tonight.

I've taken an interest in this as well.  I rough-drafted a system last
night that is probably different from the tradional debian way, but as I
find 'the debain way' often confusing, I'll just proceed.

My idea is to have a script, '/etc/init.d/defaults', which every init
script sources.  'defaults' will read the default settings and define
common functions for the scripts to use.  Here is a rough draft of the
idea:

/etc/init.d/defaults:
----------------------------------------
PACKAGE=$1
shift

ECHO=echo

# Ensure proper usage
if [ "X$PACKAGE" = "X" ] ; then
        echo "Invalid use of configuration file"
        return 100
fi

# Some settings must be present
if [ ! -r /etc/init.d/defaults.d/$PACKAGE ] ; then
        $ECHO "Package configuration not found."
        return 101
fi

# Read the settings
. /etc/init.d/defaults.d/$PACKAGE $@

# And any user-defined overrides
if [ -r /etc/init.d/defaults.d/${PACKAGE}.user ] ; then
        . /etc/init.d/defaults.d/${PACKAGE}.user $@
fi

# This is where execution ends; functions follow
# start the program
start()
{       DAEMON=$1
        shift

        # Make sure we can execute the program
        if [ ! -x "$DAEMON" ] ; then
                $ECHO "Daemon removed?"
                return -1        # removed, not purged
                # returning error because the init script is
                # no longer user-editable & should be removed
        fi

        # Make sure we can write the pid
	(echo test > /var/state/pid/$PACKAGE) >/dev/null 2>&1
        if [ -f /var/state/pid/$PACKAGE ] ; then
        	writeable=`cat /var/state/pid/$PACKAGE`
        else
                writeable="no"
        fi
        if [ "X$writeable" != "Xtest" ] ; then
                $ECHO "Cannot create state file"
                return 1
        fi

        # Inform the user that $NAME is starting
        if [ "X$NAME" != "X" ] ; then
                $ECHO "Starting $NAME [from $PACKAGE]..."
        fi

        # Time to run it; we can handle forking, non-forking
        if [ $FORKING ] ; then
                $DAEMON $@
                error=$?
                pid=$!
        else
                # this probably can't happen
                ($DAEMON $@) &
                pid=$!
                sleep $SLEEP # time to start or exit
                error=$?
        fi

        echo $pid > /var/state/pid/$PACKAGE

        $ECHO "Done"
        return $error
}
# kill via HUP
hup()
{       generic_kill HUP $1 $HUP
}
# kill via USR1
usr1()
{       generic_kill USR1 $1 $USR1
}
# the actual kill call
generic_kill()
{       if [ ! -r /var/state/pid/$PACKAGE ] ; then
                return 1        # program not running
        fi

        # Get the pid -- will need to be smarter
        pid=`cat /var/state/pid/$PACKAGE`

        # Inform the user
        if [ "X$NAME" != "X" -a "X$3" != "X" ] ; then
                $ECHO $3...
        fi

        # kill the pid
        kill -$1 $2
        return 0
}
----------------------------------------

The calling script, '/etc/init.d/foo':
----------------------------------------
#!/bin/bash

. /etc/init.d/defaults foo

case "$1" in
        start)  start /usr/bin/bar $OPTIONS;;
        stop)   hup bar;;
        reload) usr1 bar;;
        *) echo "Usage: $0 (start|stop|reload)"
           exit 1;;
esac
----------------------------------------

The config file, /etc/init.d/defaults.d/foo:
----------------------------------------
# This is just user information
NAME="Fancy, fancy, FuBar"
# These are required
FORKING=1       # Must be 1 or 0 (may not be possible)
# These are the messages corresponding to each signal
HUP="Stopping $NAME"
USR1="Reloading the FuBar database"
# Anything else
OPTIONS=""
----------------------------------------

The user configuration file:
/etc/init.d/defaults.d/foo.user
----------------------------------------
OPTIONS="--port=16"
----------------------------------------

Now, as I said, this is just what I sketched together briefly last
night.  If it works, I think it would work very, very well.  User
overrides are not part of the package and will not be overwritten.  The
init scripts can be removed since they should not change.  New variables
are always declared in the defaults config file.  Old variables are
simply not used.

It does have limitations: Putting the pid into $PACKAGE means only one
pid can be saved, but multiple programs can be run.  It needs to be
saved into either `basename $DAEMON` or $PACKAGE/`basename $DAEMON`. 
PID handling is generally poor in what is listed above - it should
fallback to also scan for daemon-created pid files, use the pidof
function, and using ps as a last resort.  I try to grab the pid simply
because I feel it is the ideal way to do it -- always retain control.

It is designed to be able to return consistent error codes.  The return
code from 'start' is the return code of the program being run.  The
script will have to massage this as necessary to support fancy error
reporting.

I have $ECHO sprinkled throughout - this just means it should be
optional whether anything gets displayed.  You can place an "ECHO=echo"
in to see them, or "echo=/bin/false" (or whatever) to not see them.

I wanted the ability to run non-forking programs, but I don't see an
obvious way to do this.  Maybe start-stop-daemon can handle it; dunno.

It's not a cure-all.  Networking and other complicated scripts will
still have to be hand crafted.

A basic overview is simply this:

All scripts include the defaults file, and give the package name when
doing so.  The script will make sure the the environment for running the
package is sane, and provide functionality to start any programs it
needs (start) and to send them various signals (hup, usr1, etc).  It
loads options that ship with the package (in /etc/init.d/defaults.d),
then overwrites them with any user-defined options (same directory, same
name with a '.user' extension).  Packages should not include sample
user-defined files; rather, they should clearly state how to create a
user file in the default file, warning the user not to edit the init
script or the shipped defaults file.  init scripts should be able to be
very clear and consice, and the entire process not at all confusing. 
Additionally, these init scripts will no longer need to be defined as
'conffiles', as those are the '.user' files, and can all be safely (in
theory) removed.

I hope this is flexible and expandible enough to handle the majority
(~80%) of the init scripts out there.  If there are any glaring
omissions, I'd like to hear about them, or really any feedback in
general.  Again, I am not a developer so I can only contribute code, but
I'd like to see something like this adopted if other find it reasonable.

Christopher



Reply to: