Thoughts, ideas and solutions from a few EPM consultants.

Mono / FastCGI Startup Script

A few of us over at EPMI were previously programmers. Justin and I in particular were .NET developers. Recently we made the switch on our one of our Linux servers from using Lighttpd (lighty) to NginX as our web server and reverse proxy. If you are familiar with these two web servers you probably already know why we made the switch so I am not going to cover that here.

Upon doing so I found that NginX handeled running Mono (.NET) application differently than lighttpd. In lighttpd the Mono started, with some FastCGI configuration, automatically when the web server started. In NginX FastCGI assumes the application server, Mono in our setup, is already running. It then uses its reverse proxy feature to pass requests bound for .NET web applications to a particular IP address and port. Since NginX does not start up Mono automatically we had to find another way to start and manage the Mono process. The main goal was to find a way to automatically start Mono in the event that the server had to be rebooted. After digging round blogs on the internet I found what appeared to be a good skeleton of a Mono FastCGI start/stop script. It is a little out of date since it was written for Mono version 2 and we are now using version 4. The concepts are solid, achieve similar funtionality to Apache/Lighttpd/NginX available/enabled paradigm.

First I followed the steps outlined in the blog and created the directories /etc/mono/fcgi/apps-available and /etc/mono/fcgi/apps-enabled. Like the blog post says these directories will hold the files used to define applications.

To quote the blog post:

The intent of these directories is to put the actual files in apps-available, then symlink the ones that are enabled from apps-enabled.

Since we are using Version 4 of the Mono application server we had to break script and do things a bit different. In the blog post it talks about creating files with a single line that instruct Mono how to run particular apps. Using version 4 however Mono has implemented the use of XML files with the webapp extension. Using this method I created a file, sitename.webapp, in the apps-available folder. I then set the contents of the file to correctly identify one of our web applications.

<app>
	<web-application>
    	<name>sitename.com</name>
        <vhost>*</vhost>
        <vport>80</vport>
        <vpath>/</vpath>
        <path>/var/sites/sitename.com</path>
    </web-application>
</app>

With the file created the next step was to create a symlink to the apps-enabled folder. To enable multiple sites you would have more than one of these webapp files in the apps-available folder and for each one that needs to be enabled you would create the symlink to the apps-enabled folder.

Next I went back and kept following the directions and created a file named monoserve in the /etc/init.d folder. This file is the script that will be used to start/stop the Mono application server. Then once again I had to deviate and make some of my own modifications to the monoserve script.

#!/bin/bash
### BEGIN INIT INFO
# Provides: monoserve.sh
# Required-Start: $local_fs $syslog $remote_fs
# Required-Stop: $local_fs $syslog $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start FastCGI Mono server with hosts
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/mono
NAME=monoserver
DESC=monoserver

## Begin -- MAKE CHANGES HERE --
PROGRAM=fastcgi-mono-server4 # The program which will be started
ADDRESS=127.0.0.1 # The address on which the server will listen
PORT=9000 # The port on which the server will listen
USER=www-data # The user under which the process will run
GROUP=$USER # The group under which the process will run
LOGFILE=/var/log/mono/fastcgi.log
## End -- MAKE CHANGES HERE --

# Determine the environment
MONOSERVER=$(which $PROGRAM)
MONOSERVER_PID=""
FCGI_CONFIG_DIR=/etc/mono/fcgi/apps-enabled

# Start up the Mono server
start_up(){
	get_pid
    if [ -z "$MONOSERVER_PID" ]; then
    	start-stop-daemon -S -c $USER:$GROUP -x $MONOSERVER -- --appconfigdir $FCGI_CONFIG_DIR /socket=tcp:$ADDRESS:$PORT /logfile=$LOGFILE &
        echo "Mono FastCGI Server $PROGRAM started as $USER on $ADDRESS:$PORT"
	else
		echo "Mono FastCGI Server is already running - PID $MONOSERVER_PID"
	fi
}

# Shut down the Mono server
shut_down() {
    get_pid
    if [ -n "$MONOSERVER_PID" ]; then
		kill $MONOSERVER_PID
		echo "Mono FastCGI Server stopped"
    else
		echo "Mono FastCGI Server is not running"
	fi
}

# Refresh the PID
get_pid() {
	MONOSERVER_PID=$(ps auxf | grep $PROGRAM.exe | grep -v grep | awk '{print $2}')
}

case "$1" in
	start)
		start_up
	;;
	stop)
		shut_down
	;;
	restart|force-reload)
		shut_down
		start_up
	;;
    status)
		get_pid
		if [ -z "$MONOSERVER_PID" ]; then
			echo "Mono FastCGI Server is not running"
		else
			echo "Mono FastCGI Server is running - PID $MONOSERVER_PID"
		fi
	;;
	*)
		echo "Usage: monoserve (start|stop|restart|force-reload|status)"
	;;
esac

exit 0

To quote the blog post again:

This needs to be owned by root and be executable (“chmod +x monoserve”). You can use “update-rc.d monoserve defaults” to set this to start at boot.

Using this script we are now able to manage the Mono application server using the service command service monoserve [start|stop|restart]. One of the reasons I liked this script so much is unlike many others it runs the Mono application server as www-data as opposed to many of the other scripts I found that ran as root.

One of the gotchas to this is that we had to change permissions on the Mono log file so that it was owned by www-data but this was easily fixed by running the command chown www-data:www-data /var/log/mono/fastcgi.log. Another minor annoyance is that Mono would complain that it couldn't access the folder /var/www/.mono but this was easily fixed by running the following.

mkdir /var/www/.mono
chown -R www-data:www-data /var/www/.mono

So I know its not very EPM related but I figure its worth writing down as someone might find it useful, and who knows maybe very soon *hint hint* we may be releasing some code, Open Source, that might to run on .NET. Although I am not sure if I have tested any thing for Mono compatibility lately.