wiki:Howto/TracWSGIUbuntuRoot

Trac + WSGI on Ubuntu in the Root

As at the time of writing (2008-04-14) Trac 0.11b is not available in the Ubuntu repositories. I've packaged it and  made it available from my PPA.

I use  mod_wsgi (rather than mod_python) with apache 2.2 and I've packaged that, too (available from my PPA).

These are the instructions for installing and configuring Trac 0.11+ with mod_wsgi apache2.2 in Ubuntu 7.10 Gutsy. Apache 2.2 is configured for virtual hosting and is managed using Webmin and Virtualmin. Virtualmin creates each virtual domain as a user with files in /home/user. In my case Trac is being retro-fitted so it must co-operate nicely with existing directories and files on the web server. That requires some careful use of mod_rewrite RewriteRules since I want Trac to respond at the root of the virtual host (e.g.  http://localhost/).

Note: I've used $TRAC_USER to represent the linux user account in all the shell commands here. That makes it easy for you to copy-and-paste these instructions and to include them in scripts. Just remember to set $TRAC_USER first

export TRAC_USER="user"

Add the repository

Add to /etc/apt/sources.list:

Gutsy

deb http://ppa.launchpad.net/intuitivenipple/ubuntu gutsy main
deb-src http://ppa.launchpad.net/intuitivenipple/ubuntu gutsy main

Hardy

deb http://ppa.launchpad.net/intuitivenipple/ubuntu hardy main
deb-src http://ppa.launchpad.net/intuitivenipple/ubuntu hardy main

Update apt's local package list:

sudo apt-get update

Install Packages

We need to install the packages Trac 0.11+ depends on as well as Trac itself.

sudo apt-get install python-pygments python-genshi trac

SVN

Even if planning on using another repository (i.e. git) it's easiest to install SVN first just to ensure the Trac installation works.

Prepare SVN Directories

Create a skeleton template directory for SVN that can be reused as an empty import each time an SVN repository is created:

sudo mkdir -p /etc/svn/skel/branches /etc/svn/skel/tags /etc/svn/skel/trunk

Create SVN repository

These instructions are geared towards hosting multiple SVN repositories. You can repeat these steps for each repository, replacing $TRAC_USER with the account you are configuring.

svnadmin create /home/$TRAC_USER/svn
svn import /etc/svn/skel file:///home/$TRAC_USER/svn -m "Create repository"

Note: the three contiguous forward-slashes in the URL. Read them as schema (file://) + path (/home/$TRAC_USER/svn).

Set the correct permissions:

find /home/$TRAC_USER/svn -type f -exec chmod 660 {} \;
find /home/$TRAC_USER/svn -type d -exec chmod 2770 {} \;
chown -R $TRAC_USER:www-data /home/$TRAC_USER/svn

Prepare Database

If you are using the default Trac choice of SQLite you can skip this step. If you want to use MySQL read on.

In a Virtualmin installation virtual domains each have a MySQL database with a username and password that is the same as the user account. You only need to create the database manually if there isn't one already or you want the user's Trac environment to use a dedicated database rather than keep all the user's tables in the same database.

Install MySQL and Python bindings

Usually MySQL will already be installed, but if not:

sudo apt-get install mysql-server

The Python binding:

sudo apt-get install python-mysqldb

Create a database and set permissions

Note: replace user here with whatever $TRAC_USER is set to.

Log into the database server using an account with full permissions:

mysql -u root -p
Password:

mysql> CREATE DATABASE user_trac;
mysql> GRANT ALL ON user_trac.* TO 'user'@'localhost' IDENTIFIED BY 'password';
mysql> quit

The Trac database connection parameter for this example would be mysql://user:password@localhost/user_trac

Create Trac environment

Because this is a virtual hosting configuration all user-related data is stored under the user's home directory. We'll create the Trac environment:

trac-admin /home/$TRAC_USER/trac initenv "Example Project" \
 mysql://$TRAC_USER:password@localhost/${TRAC_USER}_trac svn /home/$TRAC_USER/svn

If everything has gone well the Trac environment will be configured.

Install mod_wsgi

If you've enabled my PPA in your apt's sources.list this should be quick:

sudo apt-get install libapache2-mod-wsgi

The package will enable the module in apache's /etc/apache2/mods-enabled/ directory and restart the server. If you have problems that seem to indicate mod_wsgi isn't responding try fully stopping and restarting the server manually:

sudo /etc/init.d/apache2 stop
sudo /etc/init.d/apache2 start

Configure Apache Virtual Host

Confirm mod_wsgi is enabled

ls /etc/apache2/mods-enabled/wsgi*
/etc/apache2/mods-enabled/wsgi.conf  /etc/apache2/mods-enabled/wsgi.load

Create a Trac WSGI wrapper

Create an apache configuration directory and somewhere for mod_wsgi to extract eggs in the Trac installation:

mkdir /home/$TRAC_USER/trac/apache /home/$TRAC_USER/trac/eggs

Copy the following python script to the file /home/$TRAC_USER/trac/apache/trac.wsgi

# limit per-process stack size (linux default is usually 8MB) to help Virtual Private servers with limited memory allowances
import thread
thread.stack_size(524288)

import sys
sys.stdout = sys.stderr

import os
os.environ['TRAC_ENV'] = '/home/user/trac'
os.environ['PYTHON_EGG_CACHE'] = '/home/user/trac/eggs'

import trac.web.main
_application = trac.web.main.dispatch_request

def application(environ, start_response):
        # place-holder in case pre-Trac processing is required
        return _application(environ, start_response)

Note: replace user with whatever your $TRAC_USER is set to

This is a WSGI wrapper script that calls Trac. In the application() function you can do some pre-Trac processing of CGI environmental variables, debug logging, or anything else you might need.

Check permissions

Because a process with the web-server credentials is going to need access to the Trac directories and files you need to ensure the permissions are correct.

sudo chown -R $TRAC_USER:www-data /home/$TRAC_USER/trac 
sudo find /home/$TRAC_USER/trac -type d -exec chmod 770 {} \;
sudo find /home/$TRAC_USER/trac -type f -exec chmod 660 {} \;

You might be able to tighten these up since the server doesn't need write-access to every file.

Configure the VirtualHost

A standard installation of apache 2 has a per-site configuration file in /etc/apache2/sites-available/ and for each enabled site a symbolic link in /etc/apache2/sites-enabled/.

Edit the site's configuration file with your favourite editor or if using Virtualmin select the domain then choose Services > Configure Website and in the Virtual Server group choose Edit Directives.

Add these sections to the file.

The Trac WSGI wrapper:

WSGIScriptAlias /trac /home/user/trac/apache/trac.wsgi
<Directory /home/user/trac/apache>
    WSGIApplicationGroup %{GLOBAL}
    Order deny,allow
    Allow from all
</Directory>

Note: although it is set to match a URI beginning with /trac a RewriteRule will ensure that it is mapped to the root ( / ) of the domain.

To provide authentication for the login scripts:

<Location /trac/login>
AuthName "Trac"
AuthType Basic
AuthUserFile /home/user/.trac-htpasswd
require valid-user
</location>

and on the server create the password file with the initial user. You'll be prompted for the user's password:

htpasswd -c /home/$TRAC_USER/.trac-htpasswd $TRAC_USER

Note: don't use the -c option again or you'll overwrite the existing file with a newly created one!

And now the complicated bit - the mod_rewrite rules:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteLog "/home/user/logs/rewrite.log"
RewriteLogLevel 9

# Don't let DirectoryIndex mess with Trac installed in the root
RewriteCond %{REQUEST_URI} ^/$
RewriteRule . /trac [QSA,PT,L]

# Don't let Trac handle existing directories, files or aliases
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/(awstats|cgi-bin/)
# prepend /trac to URI and append Query String, Pass-Through to xxxAlias directives, Last rule.
RewriteRule ^(.*)$ /trac$1 [QSA,PT,L]
</IfModule>

Note: RewriteLogLevel is set to 9, the DEBUG level, in case of problems. Once you're happy set the level to 0.
Note: Don't forget to replace user with whatever your $TRAC_USER value is

The RewriteCond group is responsible for ensuring:

  • The request isn't an existing directory
  • The request isn't an existing file
  • The request isn't an existing ScriptAlias or Alias (alter these to suit the VirtualHost)

Configure the Locale

This is an important step. Because apache is running with the non-interactive user account www-data it doesn't have it's own locale setting. As a result the default locale "C" of GNU libc is used, which is normally U.S.A. date styles mm/dd/yyyy. This affects any dates Trac displays. To set the correct locale decide on the local name and then add to the VirtualHost:

SetEnv trac.locale en_GB.UTF-8

Save the file and apply the changes (restart apache)

If using Virtualmin simply press the button-link in the top-right of the page that says "Apply Changes". Manually you can use apache2ctl:

sudo apache2ctl restart

or you can use the init script:

sudo /etc/init.d/apache2 restart

Test and Debug

At this point when you access the site root you should see the default Trac front page:

http://localhost/

If you get a server error check the logs:

  1. Apache VirtualHost error log (/home/$TRAC_USER/logs/error.log)
  2. Apache mod_rewrite log (/home/$TRAC_USER/logs/rewrite.log)
  3. Trac log (/home/$TRAC_USER/trac/log/trac.log)
  4. Apache server log (/var/log/apache2/error.log)

If needed add debug messages to the WSGI wrapper script (/home/$TRAC_USER/trac/apache/trac.wsgi):

def application(environ, start_response):
        # place-holder in case pre-Trac processing is required
        print >> environ['wsgi.errors'], "*** new request ***"
        for key, value in environ.items():
                print >> environ['wsgi.errors'], "%s=%s" % (key, value)

        return _application(environ, start_response)

and look in /home/$TRAC_USER/logs/error.log:

tail -n 100 /home/$TRAC_USER/logs/error.log | sed 's/^.*\[client .*\..*\..*\..*\] \(.*\), referer.*/\1/'

*** new request ***
mod_wsgi.reload_mechanism=0
mod_wsgi.listener_port=80
HTTP_REFERER=http://localhost/chrome/common/css/trac.css
mod_wsgi.listener_host=
SERVER_SOFTWARE=Apache/2.2.4 (Ubuntu) mod_python/3.3.1 Python/2.5.1 proxy_html/2.5 mod_wsgi/2.0
SCRIPT_NAME=/trac
HTTP_IF_MODIFIED_SINCE=Fri, 15 Feb 2008 21:07:20 GMT
SERVER_SIGNATURE=<address>Apache/2.2.4 (Ubuntu) mod_python/3.3.1 Python/2.5.1 proxy_html/2.5 mod_wsgi/2.0 Server at localhost Port 80</address>
REQUEST_METHOD=GET
PATH_INFO=/chrome/common/extlink.gif
SERVER_PROTOCOL=HTTP/1.1
QUERY_STRING=
PATH=/usr/local/bin:/usr/bin:/bin
HTTP_ACCEPT_CHARSET=ISO-8859-1,utf-8;q=0.7,*;q=0.7
HTTP_USER_AGENT=Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.8.1.13) Gecko/20080325 Ubuntu/7.10 (gutsy) Firefox/2.0.0.13
HTTP_CONNECTION=keep-alive
HTTP_COOKIE=trac_form_token=e687a532d832295948b8e634; trac_auth=94fe5618847a9d983e514a93d4becd6c
SERVER_NAME=localhost
REMOTE_ADDR=127.0.0.1
wsgi.url_scheme=http
PATH_TRANSLATED=/home/user/trac/apache/trac.wsgi/chrome/common/extlink.gif
SERVER_PORT=80
wsgi.multiprocess=True
SERVER_ADDR=127.0.0.1
DOCUMENT_ROOT=/home/user/public_html
mod_wsgi.process_group=
SCRIPT_FILENAME=/home/user/trac/apache/trac.wsgi
SERVER_ADMIN=webmaster@domain.net
SCRIPT_URI=http://localhost/chrome/common/extlink.gif
wsgi.input=<mod_wsgi.Input object at 0x2aaaac21f4f0>
HTTP_HOST=localhost
SCRIPT_URL=/chrome/common/extlink.gif
wsgi.multithread=True
mod_wsgi.callable_object=application
HTTP_CACHE_CONTROL=max-age=0
REQUEST_URI=/chrome/common/extlink.gif
HTTP_ACCEPT=image/png,*/*;q=0.5
wsgi.version=(1, 0)
GATEWAY_INTERFACE=CGI/1.1
wsgi.run_once=False
wsgi.errors=<mod_wsgi.Log object at 0x166efc0>
REMOTE_PORT=51423
HTTP_ACCEPT_LANGUAGE=en-gb,en;q=0.5
mod_wsgi.application_group=
mod_wsgi.script_reloading=1
wsgi.file_wrapper=<built-in method file_wrapper of mod_wsgi.Adapter object at 0x1625cd8>
HTTP_ACCEPT_ENCODING=gzip,deflate
HTTP_KEEP_ALIVE=300