[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