[telepathy-ashes/master] Created well-behaved daemon script.

David Laban david.laban at collabora.co.uk
Thu Nov 12 09:15:10 PST 2009


Note that this script won't restart the bot when it dies.
You'll have to wait for that.
---
 ashes/tools/echo.init      |  154 ++++++++++++++++++++++++++++++++++++++
 ashes/tools/echo_daemon.py |  177 ++++++++++++++++++++++++++++++++++++++++++++
 ashes/tools/groups.py      |    2 +
 3 files changed, 333 insertions(+), 0 deletions(-)
 create mode 100755 ashes/tools/echo.init
 create mode 100644 ashes/tools/echo_daemon.py

diff --git a/ashes/tools/echo.init b/ashes/tools/echo.init
new file mode 100755
index 0000000..49c8a76
--- /dev/null
+++ b/ashes/tools/echo.init
@@ -0,0 +1,154 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          echobot
+# Required-Start:    $remote_fs $syslog $network
+# Required-Stop:     $remote_fs $syslog $network
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Telepathy Echo Bot
+# Description:       This service runs the echo service as the echobot user.
+#                    Configs are placed in ~echobot.
+### END INIT INFO
+
+# Author: David Laban <david.laban at collabora.co.uk>
+#
+# Please remove the "Author" lines above and replace them
+# with your own name if you copy and modify this script.
+
+# 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="Telepathy Echo Bot"
+HOME=/home/alsuren
+ACCOUNT=ashes.echo123 at jabber.org
+NAME=echo
+DAEMON=/usr/bin/python
+DAEMON_ARGS="${HOME}/src/telepathy-ashes/ashes/tools/echo_daemon.py \
+${HOME}/data/${ACCOUNT}.account"
+PIDFILE=/tmp/${ACCOUNT}.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /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 --chuid echobot --start --quiet --pidfile $PIDFILE \
+		--exec $DAEMON --test \
+		|| return 1
+	start-stop-daemon --chuid echobot --start --quiet --pidfile $PIDFILE \
+                --exec $DAEMON -- $DAEMON_ARGS \
+		|| 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.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#    if daemon could not be stopped
+	#   other if a failure occurred
+	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile=$PIDFILE --exec $DAEMON
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	[ "$?" = 2 ] && return 2
+	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)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  status)
+       status_of_proc -p $PIDFILE "$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
+
+:
+
diff --git a/ashes/tools/echo_daemon.py b/ashes/tools/echo_daemon.py
new file mode 100644
index 0000000..313ca48
--- /dev/null
+++ b/ashes/tools/echo_daemon.py
@@ -0,0 +1,177 @@
+#!/usr/bin/python
+"""
+This script is designed to be the daemon that is run using start-stop-daemon in
+an init script.
+
+Requirements
+=============
+
+* It must start a dbus-daemon on startup, and kill it when killed.
+* It must start up gabble and put its logs in a unique place per session
+* It must start up a single echo bot instance, and restart it whenever it gets
+disconnected
+* It must put itself into the background, because start-stop-daemon --background
+is supposed to be a "last resort".
+
+Method
+=======
+
+Launch dbus-daemon and parse the output to get its address and PID to put in env.
+
+Launch gabble and keep hold of its pid so we can kill it later.
+
+Launch echo bot similarly.
+
+Put ourselves into the background.
+
+Catch KILL (or specify a signal name to start-stop-daemon --stop --retry) and
+shut down the dbus-daemon + gabble + echo bot instance.
+
+"""
+from __future__ import with_statement
+
+import os
+import sys
+import copy
+import time
+import grp
+import signal
+import daemon
+import lockfile
+import subprocess
+from subprocess import Popen, PIPE, STDOUT, CalledProcessError
+
+import account
+from helper_functions import between
+
+subprocesses = {}
+
+THIS_DIR = os.path.abspath(os.path.dirname(__file__))
+DATE = time.strftime("%Y.%m.%d-%H.%M.%S")
+HOME = os.environ['HOME']
+
+def shell_eval(command):
+    """
+    Like $(command) or `command` in bash.
+    Also checks the exit status in the same way as check_call
+    """
+    shell = Popen(command, shell=True, stdout=PIPE)
+    stdout, stderr_ = shell.communicate()
+    if shell.returncode != 0:
+        raise CalledProcessError(shell.returncode, command, env=os.environ)
+    return stdout
+
+def construct_env(account_file):
+    """
+    Since options are passed to telepathy components in environment variables,
+    we can create a connection-manager agnostic set of environment variables.
+    """
+    env = copy.copy(os.environ)
+    date = DATE
+    home = os.environ["HOME"]
+    account_name = ACCOUNT_NAME
+    for component_name in "gabble butterfly haze idle".split():
+        NAME = component_name.upper() # See what I did there?
+        env[NAME+"_DEBUG"] = "all"
+        env[NAME+"_PERSIST"] = "1"
+        env[NAME+"_LOGFILE"] = "%(home)s/log/%(component_name)s-%(account_name)s-%(date)s.log" % locals()
+    # Special case for gabble with loudmouth.
+    env["LM_DEBUG"] = "net"
+    # Special case to enable the !version command of echo bot.
+    command = "aptitude search --disable-columns -F '%p-%v' 'telepathy|farsight'\
+                | egrep -v '<none>|dbg|doc|dev'"
+    env["TELEPATHY_VERSIONS"] = shell_eval(command)
+    env["ECHO_GIT_URL"] = shell_eval("git remote show -n origin | grep URL | sed 's/ *URL: *//'")
+    env["ECHO_GIT_REVISION"] = shell_eval("git rev-list HEAD^..HEAD")
+    return env
+
+def initial_program_setup(account_file):
+    os.chdir(THIS_DIR)
+    os.environ.update(construct_env(account_file))
+    dbus_launch = Popen(['/usr/bin/dbus-launch'], stdout=PIPE)
+    for line in dbus_launch.communicate()[0].strip().split('\n'):
+        key, eq_, value = line.partition('=')
+        os.environ[key] = value
+    gabble_env = copy.copy(os.environ)
+    # Gabble doesn't parse command line args, but adding them makes it easier
+    # to identify our process using pgrep -fl gabble
+    gabble = Popen(['/usr/lib/telepathy/telepathy-gabble',
+                      'echo_daemon', DATE],
+                   env=os.environ)
+
+    subprocesses['gabble'] = gabble
+
+def program_cleanup(signum, stack_):
+    for process in subprocesses.values():
+        try:
+            os.kill(process.pid, signal.SIGTERM)
+        except OSError:
+            print "process not killed."
+    if os.environ.get("DBUS_SESSION_BUS_PID", None):
+        os.kill(int(os.environ["DBUS_SESSION_BUS_PID"]),  signal.SIGTERM)
+        del os.environ["DBUS_SESSION_BUS_PID"]
+    os.remove(PIDFILENAME)
+    exit(-signum)
+
+
+def do_daemon(account_file):
+    global PIDFILENAME
+    global ACCOUNT_NAME
+    account_name = between(account_file, '/', '.account')
+    ACCOUNT_NAME = account_name
+    PIDFILENAME = '/tmp/%s.pid' % account_name
+
+    initial_program_setup(account_file)
+    
+    echo_bot = Popen(['/usr/bin/python',
+                      os.path.join(THIS_DIR, 'echo_bot.py'),
+                      '.*', '.*', account_file],
+                     env=os.environ,
+                     stdout=PIPE, stderr=STDOUT)
+    subprocesses['echo_bot'] = echo_bot
+    
+    echobot_logfile = open("%s/log/echobot-%s-%s.log" %
+                        (HOME, ACCOUNT_NAME, DATE), 'w')
+
+    # Don't go into the background until we're sure we're alive.
+    forward_stream_until(echo_bot.stdout, echobot_logfile, "List Handled:")
+
+    # Now we can go into the background.
+    context = daemon.DaemonContext(
+        working_directory='/home/echobot',
+        detach_process=True,
+        files_preserve=[sys.stdout, sys.stderr, echobot_logfile, echo_bot.stdout],
+        stdout=sys.stdout,
+        stderr=sys.stderr,
+        )
+
+    context.signal_map = {
+        signal.SIGTERM: program_cleanup,
+        signal.SIGINT: program_cleanup,
+        signal.SIGHUP: 'terminate',
+        #signal.SIGUSR1: reload_program_config,
+        }
+    with context:
+        pidfile = open(PIDFILENAME, 'w')
+        pidfile.write("%s\n" % os.getpid ())
+        pidfile.close()
+        # Block until echo bot closes stdout.
+        forward_stream(echo_bot.stdout, echobot_logfile)
+        program_cleanup(0, None)
+
+def forward_stream_until(from_stream, to_stream, stop):
+    """Copies data from from_stream into to_stream until stop appears in the stream"""
+    assert '\n' not in stop # Can't handle things split over multiple lines easily.
+    line = 1
+    while line:
+        line = from_stream.readline()
+        to_stream.write(line)
+        to_stream.flush()
+        if stop and stop in line:
+            return
+
+def forward_stream(from_stream, to_stream):
+    forward_stream_until(from_stream, to_stream, '')
+
+if __name__ == "__main__":
+    do_daemon(*sys.argv[1:])
diff --git a/ashes/tools/groups.py b/ashes/tools/groups.py
index 6d04655..e252ca5 100644
--- a/ashes/tools/groups.py
+++ b/ashes/tools/groups.py
@@ -1,3 +1,4 @@
+import sys
 
 import telepathy
 
@@ -19,6 +20,7 @@ class MemberAcceptor(ChannelListener):
         requested = properties['org.freedesktop.Telepathy.Channel.Requested']
         self.id = properties['org.freedesktop.Telepathy.Channel.TargetID']
         print 'List Handled:', self.id
+        sys.stdout.flush()
         get_all_properties(channel,
             "org.freedesktop.Telepathy.Channel.Interface.Group",
             self.group_properties_callback)
-- 
1.5.6.5




More information about the telepathy-commits mailing list