For a project that I started working on with a friend back in November, we needed to use a project management system for bugs and progress tracking, and we decided to try Trac an open project management system with svn integration written in python.
I run nginx to do all of my web serving needs, so I followed the instructions given for nginx configuration for Trac, and I was a little disappointed to find that the FastCGI script given didn't do any normal server things, like fork a child process to run in the background. I also wanted to integrate the FastCGI server with the service management tools available in CentOS, so I set to work.
First, I modified the nginx-specific FastCGI script to accept a number of command line arguments, allowing me to use the same script to run multiple track servers for multiple different projects. In total, it accepts a project path, a user, a unix socket to listen for fcgi connections, and a location to store the server process pid. Then, I added the child fork to allow me to run it as a service. Here is the version that I use now:
#!/usr/bin/env python
from optparse import OptionParser
import os
import pwd
parser = OptionParser()
parser.add_option("-s", "--sock", dest="sockaddr", help="Use a specific unix socket", default="/var/trac/run/instance.sock")
parser.add_option("-u", "--user", dest="user", help="Run FCGI process as a specific user", default="nginx")
parser.add_option("-t", "--trac", dest="trac_env", help="Trac environment path to use", default="/var/trac/project")
parser.add_option("-p", "--pid", dest="pidfile", help="Location to store pid file", default="/var/run/trac.fcgi.pid")
(options, args) = parser.parse_args()
os.environ['TRAC_ENV'] = options.trac_env
try:
from trac.web.main import dispatch_request
import trac.web._fcgi
pid = os.fork()
if pid == 0:
os.setuid(pwd.getpwnam(options.user)[2])
fcgiserv = trac.web._fcgi.WSGIServer(dispatch_request, bindAddress = options.sockaddr, umask = 7)
fcgiserv.run()
else:
os.system("echo " + str(pid) + " > " + options.pidfile)
except OSError, oe:
raise
except SystemExit:
raise
except Exception, e:
print 'Content-Type: text/plain\r\n\r\n',
print 'Oops...'
print
print 'Trac detected an internal error:'
print
print e
print
import traceback
import StringIO
tb = StringIO.StringIO()
traceback.print_exc(file=tb)
print tb.getvalue()
With a service-capable FastCGI script, now I needed to create an /etc/init.d
script to control the
process. Because I am running CentOS, I also wanted the script to be compatible with chkconfig
so
that it can be used with the service utility. This was pretty simple, as I just copied an existing
init script and ripped out most of its guts, replacing with a minimal set of controls: start, stop,
and restart. Here's the full init script, which I saved as /etc/init.d/trac-fcgi
:
#!/bin/sh
#
# chkconfig: - 85 15
# description: Runs trac's fcgi script as a daemon
# processname: trac.fcgi
# pidfile: /var/run/trac.fcgi.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
tracfcgi="/var/trac/trac.fcgi"
prog=tracfcgi)
pidfile="/var/run/${prog}.pid"
start() {
echo -n $"Starting $prog: "
daemon $tracfcgi
retval=$?
echo
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc -p $pidfile $prog
retval=$?
echo
return $retval
}
restart() {
stop
start
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart)
$1
;;
status|status_q)
rh_$1
;;
condrestart|try-restart)
rh_status_q || exit 7
restart
;;
*)
echo $"Usage: $0 {start|stop|status|restart}"
exit 2
esac
Finally, the new init script is added with a simple call to chkconfig:
# chkconfig --add trac-fcgi
If you want to run multiple trac servers with different configurations, you will need to create a
copy of the init.d script per configuration and add them using command line arguments on the line
that reads daemon $tracfcgi
after the $tracfcgi
variable. Then, add
each configuration's script to chkconfig using the same method as before.