Catalyst::Wiki
/deployment/fcgi-init
Managing FastCGI Processes with an init script
While there are arguments for managing your FastCGI processes with a services manager like daemontools or perhaps upstart, there's a certain familiarity and simplicity to init scripts.
Wait, did I say simplicity? Maybe not... simple init scripts have a tendency to blow up in lots of interesting ways. Below is a not-so-simple script that I've developed for my own deployments that covers a lot of the bases and seems to work well, together with notes on usage.
Requirements
This script depends on... well not Debian exactly, but start-stop-daemon
, which is rarely seen outside of Debian-like systems. It also depends on an LSB-compliant system. If you come up with a port to a system that does things differently, go ahead and post it below in its own section. Most importantly, it depends on FCGI::ProcManager
0.18, as previous versions don't handle being daemonized properly.
Good Stuff
This script supports:
- Running the app setuid/setgid as its own user as a system startup script
- Or running the app as the invoking user for personal project type deals.
local::lib
- Best FHS practices for the pidfile location
- Daemonizing the app instead of letting FCGI::ProcManager because FCGI::ProcManager does it wrong
- Running the app through
perl -c
on restart to avoid embarrassment.
Configuration
- Set
APPNAME
to the Perl class name of your application. - Set
APPDIR
to your app's root (or wherever you want it to start off chdir'd to) - Set
PROCS
to the number of FastCGI child processes to run. - Set
SOCKET
to the same thing it's set to in your webserver config. TCP and Unix sockets will work. - Set
USER
andGROUP
to have the application run setuid/setgid. LeaveUSER
unset (USER=
) if you're not running as root, to run the app as the current user. - Only set
PIDSUFFIX
if you're running more than one app with the sameAPPNAME
on a machine; you can use it to make sure that the instances get separate pidfiles. - Set
LOCALLIB
if you want to use alocal::lib
directory (this is better than setting it in the_fastcgi.pl
script because the compile check on restart will work). Leave it blank if you're not usinglocal::lib
.
Note: If you have a slow server, the script may report that it has failed to start the daemon, even though it does eventually start successfully. This is because the part of the script that checks if the daemon is running gives up too soon. To fix this, just increase the delay in the _start()
function from sleep 1
to sleep 2
(or greater).
The Repository
The latest version of this script can be found at https://github.com/arodland/cat-fcgi-init.
The Script
#!/bin/sh
# Start a Catalyst app under FastCGI
# Copyright (c) 2009-2010, Andrew Rodland
# See LICENSE for redistribution conditions.
### BEGIN INIT INFO
# Provides: webapp
# Required-Start: $local_fs $network $named
# Required-Stop: $local_fs $network $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: A Catalyst Application
### END INIT INFO
. /lib/lsb/init-functions
APPNAME=MyApp
APPDIR=/home/myapp/MyApp
UNIXNAME=$(echo $APPNAME | perl -pe 's/::/_/;$_=lc')
PROCS=5
SOCKET=127.0.0.1:3001
# Leave these unset and we won't try to setuid/setgid.
USER=myapp
GROUP=myapp
# Set this if you have more than one instance of the app and you don't want
# them to step on each other's pidfile.
PIDSUFFIX=
# local::lib path, if you want to use it.
LOCALLIB=
if [ -f "/etc/default/"$UNIXNAME ]; then
. "/etc/default/"$UNIXNAME
fi
if [ $(id -u) -eq 0 ] ; then
PIDDIR=/var/run/$UNIXNAME
mkdir $PIDDIR >/dev/null 2>&1
chown $USER:$GROUP $PIDDIR
chmod 775 $PIDDIR
else
PIDDIR=/tmp
fi
PIDFILE=$PIDDIR/$UNIXNAME${PIDSUFFIX:+"-$PIDSUFFIX"}.pid
if [ -n "$LOCALLIB" ] ; then
eval `perl -I"$LOCALLIB/lib/perl5" -Mlocal::lib="$LOCALLIB"`
fi
check_running() {
[ -s $PIDFILE ] && kill -0 $(cat $PIDFILE) >/dev/null 2>&1
}
check_compile() {
if [ -n "$USER" ] ; then
if su $USER -c "cd $APPDIR ; perl -Ilib -M$APPNAME -ce1" ; then
return 0
fi
return 1
else
if ( cd $APPDIR ; perl -Ilib -M$APPNAME -ce1 ) ; then
return 0
fi
return 1
fi
}
_start() {
start-stop-daemon --start --quiet --pidfile $PIDFILE --chdir $APPDIR \
${USER:+"--chuid"} $USER ${GROUP:+"--group"} $GROUP --background \
--startas $APPDIR/script/${UNIXNAME}_fastcgi.pl -- \
-n $PROCS -l $SOCKET -p $PIDFILE
for i in 1 2 3 4 5 6 7 8 9 10; do
sleep 1
if check_running ; then
return 0
fi
done
return 1
}
start() {
log_daemon_msg "Starting $APPNAME" $UNIXNAME
if check_running; then
log_progress_msg "already running"
log_end_msg 0
exit 0
fi
rm -f $PIDFILE 2>/dev/null
_start
log_end_msg $?
return $?
}
_stop() {
start-stop-daemon --stop --user $USER --quiet --oknodo --pidfile $PIDFILE \
--retry TERM/5/TERM/30/KILL/30 \
|| log_failure_message "It won't die!"
}
stop() {
log_daemon_msg "Stopping $APPNAME" $UNIXNAME
_stop
log_end_msg $?
return $?
}
restart() {
log_daemon_msg "Restarting $APPNAME" $UNIXNAME
check_compile && _stop && _start
log_end_msg $?
return $?
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
restart|force-reload)
restart
;;
check|check-compile)
check_compile
;;
*)
echo $"Usage: $0 {start|stop|restart|check}"
exit 1
esac
exit $?