Setting-up Trac

by Sebastien Mirolo on Sun, 2 Oct 2011

I needed to setup a forum for developers on a recent project. That included a source control repository (git), a wiki, a blog, a buildbot and an issue tracking system. To provide the last components I decided to setup trac and a few trac plug-ins.

Install

I realized later that initialization of a trac environment often sets valid defaults for all plug-ins are already on the local system. Some plug-ins are available, as trac, through the package manager (aptitude search 'trac-.*') while others can be installed through python setup tools (easy_install) or finally a source archive. I have installed the following plug-ins for

# Base servers
$ apt-get install trac nginx
# Authentication
$ apt-get install trac-accountmanager
$ find /usr/lib -name '*acct_mgr*'
/usr/lib/pymodules/python2.7/acct_mgr
$ easy_install http://trac-hacks.org/svn/noanonymousplugin/0.11
...
Installed /usr/local/lib/python2.7/dist-packages/TracNoAnonymous-2.4-py2.7.egg
$ apt-get install pwauth
$ curl -O TracPwAuth-1.0.tar.gz
$ tar zxvf TracPwAuth-1.0.tar.gz
$ cd TracPwAuth-1.0 && python ./setup.py install
...
Installed /usr/local/lib/python2.7/dist-packages/TracPwAuth-1.0-py2.7.egg
# Source repository
$ apt-get install trac-git
$ easy_install http://trac-hacks.org/svn/bittenforgitplugin/0.11/0.6b2
...
Installing bitten-slave script to /usr/local/bin
Installed /usr/local/lib/python2.7/dist-packages/Bitten-0.6b2-py2.7.egg
$ wget http://git.mortis.eu/git/codetags.git/snapshot/0.3.tar.gz
$ tar zxvf 0.3.tar.gz
$ cd codetags-0.3-fb76322 && python ./setup.py install
...
Installed /usr/local/lib/python2.7/dist-packages/codetags-0.3-py2.7.egg
$ easy_install http://trac-hacks.org/svn/revtreeplugin/0.11/
...
Installed /usr/local/lib/python2.7/dist-packages/TracRevtreePlugin-0.6.3dev_r5601-py2.7.egg
$ easy_install http://trac-hacks.org/svn/reposearchplugin/0.11/
...
Installing update-index script to /usr/local/bin
Installed /usr/local/lib/python2.7/dist-packages/tracreposearch-0.2-py2.7.egg
# tracking metrics
$ easy_install http://trac-hacks.org/svn/tagsplugin/tags/0.6
$ easy_install --always-unzip http://trac-hacks.org/svn/fullblogplugin/0.11
$ git clone https://github.com/mrjbq7/tracstats.git
$ cd tracstats && python ./setup.py install
...
Installed /usr/local/lib/python2.7/dist-packages/TracStats-0.4-py2.7.egg
$ easy_install http://trac-hacks.org/svn/icalviewplugin/0.11/
...
Installed /usr/local/lib/python2.7/dist-packages/icalview-0.4-py2.7.egg
$ easy_install http://trac-hacks.org/svn/timingandestimationplugin/branches/trac0.11
...
Installed /usr/local/lib/python2.7/dist-packages/timingandestimationplugin-0.9.8-py2.7.egg
$ easy_install http://trac-hacks.org/svn/scrumburndownplugin/trunk/
...
Installed /usr/local/lib/python2.7/dist-packages/TracBurndown-1.9.2-py2.7.egg
$ easy_install http://trac-hacks.org/svn/tasklistplugin/trunk/
...
# Custom design
$ easy_install http://trac-hacks.org/svn/themeengineplugin/0.11/
...
Installed /usr/local/lib/python2.7/dist-packages/TracThemeEngine-2.0.1-py2.7.egg
$ easy_install http://trac-hacks.org/svn/randomincludeplugin/0.11/
...
Installed /usr/local/lib/python2.7/dist-packages/TracRandomInclude-0.1-py2.7.egg

Some of these commands installed trac 0.12 in /usr/local/bin/trac-admin and /usr/local/bin/tracd. That created a lot of incompatibilities and problems later on so I deleted them in order to stick with the Ubuntu 11.04 packaged trac 0.11 version.

Create repository

$ vi testme.py
$ sudo -u www-data vi testme.py
$ less testme.py
$ sudo -u www-data git add testme.py
$ sudo -u www-data git commit -m 'FIXME codetag'

It is now time to create the trac environment.

$ man trac-admin
$ trac-admin help
$ mkdir /var/www/trac
$ trac-admin /var/www/trac initenv testproj sqlite:db/trac.db git /var/www/reps/testproj/.git

And turn on logging in order to debug configuration issues along the way.

$ diff -u trac.ini.prev trac.ini
[logging]
log_level = DEBUG
- log_type = none
+ log_type = file

Authentication

Since the information on the trac site is confidential, I decided to setup it behind https. A lot of the documentation dealing with trac and authentication rely on web server authentication. That is unfortunate because that pops up an authentication dialog box. Most sites that require authentication today land on a login page and I wanted the same functionality for the trac site. Fortunately there is the wonderful AccountManager plug-in to do that. I still had to prevent unauthenticated access to the trac site and the NoAnonymous plug-in enabled that requirement.

Last, I needed to choose a password store to check username/password against. I picked TracPwAuth to authenticate against the unix /etc/passwd file and avoid password stores proliferation.

Trac has a built-in http server that makes it straightforwad to start serving trac pages after the daemon is running.

$ tracd -d --port 8000 /var/www/trac
$ tracd -s --port 8000 /var/www/trac
$ curl -O http://localhost:8000/trac

Unfortunately trac does not have a built-in https server so we will need to rely on a more complete web server in front of it. I picked nginx. I also decided to setup nginx/trac through a fastcgi interface instead of a proxy forward from nginx to trac, a choice I might well revert in the future.

I disabled the default site in nginx, created a trac site configuration and enabled it.

$ cat /etc/nginx/sites-available/trac
server {
        listen       443;
        server_name  domainname;

        ssl                  on;
        ssl_certificate      /etc/ssl/certs/domainname.pem;
        ssl_certificate_key  /etc/ssl/private/domainname.key;

        ssl_session_timeout  5m;

        ssl_protocols  SSLv3 TLSv1;
        ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
        ssl_prefer_server_ciphers   on;

        if ($uri ~ ^/(.*)) {
             set $path_info /$1;
        }

        #if ($request_uri ~ /login) {
        #     break;
        #}

        #if ($remote_user ~ ^$) {
        #     set $path_info /login;
        #     rewrite ^ /login redirect;
        #}

        # You can copy this whole location to ``location [/some/prefix]/login``
        # and remove the auth entries below if you want Trac to enforce
        # authorization where appropriate instead of needing to authenticate
        # for accessing the whole site.
        # (Or ``location /some/prefix``.)
        location / {
            #auth_basic            "trac realm";
            #auth_basic_user_file /home/trac/htpasswd;

            # socket address
            fastcgi_pass   unix:/var/www/tracenv/run/instance.sock;

            # python - wsgi specific
            fastcgi_param HTTPS on;

            ## WSGI REQUIRED VARIABLES
            # WSGI application name - trac instance prefix.
            # (Or ``fastcgi_param  SCRIPT_NAME  /some/prefix``.)
            fastcgi_param  SCRIPT_NAME        "";
            fastcgi_param  PATH_INFO          $path_info;

            ## WSGI NEEDED VARIABLES - trac warns about them
            fastcgi_param  REQUEST_METHOD     $request_method;
            fastcgi_param  SERVER_NAME        $server_name;
            fastcgi_param  SERVER_PORT        $server_port;
            fastcgi_param  SERVER_PROTOCOL    $server_protocol;
            fastcgi_param  QUERY_STRING       $query_string;

            # for authentication to work
            fastcgi_param  AUTH_USER          $remote_user;
            fastcgi_param  REMOTE_USER        $remote_user;

            # for ip to work
            fastcgi_param REMOTE_ADDR         $remote_addr;

            # For attchments to work
            fastcgi_param    CONTENT_TYPE     $content_type;
            fastcgi_param    CONTENT_LENGTH   $content_length;
        }
    }
$ cd /etc/nginx/sites-enabled
$ rm default
$ ln -s ../sites-available/trac

There is a script in /usr/lib/python2.7/dist-packages/trac/admin/templates/ called deploy_trac.fcgi. It contains a few template variables that need to be instantiated and looks quite different from the ones written up on the official trac wiki. I finally decided to copy/paste the one from the wiki into a local /var/www/trac/trac.fcgi file.

#!/usr/bin/env python
import os
sockaddr = '/var/www/trac/run/instance.sock'
os.environ['TRAC_ENV'] = '/var/www/trac'

try:
     from trac.web.main import dispatch_request
     import trac.web._fcgi

     fcgiserv = trac.web._fcgi.WSGIServer(dispatch_request, 
          bindAddress = sockaddr, umask = 7)
     fcgiserv.run()

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()

both nginx and trac.fcgi need access to the socket file. Nginx is running as the www-data user. It is possible to run trac as a different user by setting the following permissions.

$ mkdir -p /var/www/trac/run
$ chgrp www-data run
$ chmod g+ws run
$ chgrp www-data trac.fcgi
$ chmod g+s trac.fcgi

Later though using two different users will prevent authentication. If /usr/sbin/pwauth returns error code "50" (STATUS_INT_USER) when run as the trac user, it means pwauth was compiled without that user defined in SERVER_UIDS (config.h). At this point, I prefer to use apt-get to install pwauth instead of recompiling it from source, so I run nginx and trac as the www-data user, updating files permissions to reflect that.

$ sudo chown -R www-data:www-data /var/www/trac
$ diff -u trac.ini.prev trac.ini
[account-manager]
+password_store = PwAuthStore

[components]
+trac.web.auth.LoginModule = disabled
+acct_mgr.web_ui.LoginModule = enabled
+acct_mgr.web_ui.RegistrationModule = disabled
+pwauth.* = enabled
+noanonymous.* = enabled

Note: If you prefer the popup approach and handle all authentication through nginx, checkout the httpAuth plugin.

Nginx as a proxy to trac http server

The fastcgi approach worked fine but I still decided to use the http proxy setup after all because it requires less configuration steps. I also found an init.d script for tracd which proved valuable to start and stop tracd as a service. Here is the nginx configuration for the site:

upstream proxy_trac {
          server  127.0.0.1:8000;
  }

server {
          listen          80;
          server_name     domainname;

          location / {
              rewrite     ^/(.*)$ https://domainname/$1 redirect;
          }
  }

server {
        listen               443;
        server_name          domainname;

        ssl                  on;
        ssl_certificate      /etc/ssl/certs/domainname.pem;
        ssl_certificate_key  /etc/ssl/private/domainname.key;

        ssl_session_timeout  5m;

        ssl_protocols  SSLv3 TLSv1;
        ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
        ssl_prefer_server_ciphers   on;

        # it makes sense to serve static resources through Nginx
        location /chrome/ {
             alias /var/www/htdocs/;
        }

        location / {
                  proxy_pass       http://proxy_trac;
                  proxy_redirect   default;
                  proxy_set_header Host $host;
        }
  }

The site stopped to respond to request anymore as soon as I enabled the noanonymous plug-in. As it runs out something is creating an http redirect that required me to open port 80 in the firewall and add the appropriate redirects in the nginx config file.

PwAuth means trac contributors have a unix account on the server machine. As a result if you want to modify a password you will need to run passwd from a unix shell on the machine, in turn that might require to give all contributors ssh login to the machine.

What I really wanted in a single authentication directory for users, the ability for them to change their passwords but do not grant shell access to all users. I thought setting-up an LDAP password store would be great. They are a lot of LDAP plugins, each of them somewhat forked from each other. None-of-them seem fully implemented. It just became such a nightmare that I decided to switch to redmine on the production site.

Browsing the git repository

The issue when you use the local package manager to install plug-ins is that you have no idea where files are copied. That makes things a little tricky when you need to enable components based on pathnames. To finally write the correct line to enable git source browser, I had to rely on inspecting the .deb package as such.

$ aptitude download trac-git
$ dpkg -c trac-git_0.0.20100513-2ubuntu1_all.deb
...
/usr/lib/python2.7/dist-packages/tracext/git/__init__.py
$ diff -u trac.ini.prev trac.ini
+[code-tags]
+scan_files = *.html, *.js, *.py
+scan_folders = /*
+tags = XXX, TODO, FIXME, BUG

[components]
+tracext.git.* = enabled
+codetags.* = enabled
+revtree.* = enabled
+tracreposearch.* = enabled

+[repo-search]
+include =*.html:*.js:*.py
+exclude = *.pyc:*.png:*.jpg:*.gif:*/README

[revtree]
+#contexts = changeset, browser

[trac]
# See http://trac.edgewall.org/wiki/TracIni
-base_url =
+base_url = http://domainname/trac
-mainnav = wiki,timeline,roadmap,browser,tickets,newticket,search
+mainnav = wiki,timeline,roadmap,browser,revtree,tickets,newticket,search
repository_dir = /var/www/reps/testproj/.git

The database needs to be upgraded (codetags)

tracking metrics

The Agile trac plugin looked interesting but requires a patched trac. The patch to the ubuntu installed version is pretty huge. Browsing through the Agile trac website I did not notice enough compelling arguments to apply such an intrusive patch so I skipped this plug-in for now.

$ find /usr/lib -name '*trac*'
...
/usr/lib/python2.7/dist-packages/trac
...
$ svn co http://svn.agile-trac.org/BRANCH/AGILE-TRAC/SOURCE/0.11/REL/patch/trac/ trac
$ diff -ru /usr/lib/python2.7/dist-packages/trac trac

The ScrumBurndown plugin requires TimingAndEstimation plug-in so I installed and configured this one as well.

$ diff -u trac.ini.prev trac.ini
[components]
+tractags.* = enabled
+tracfullblog.* = enabled
+tracstats.* = enabled
+timingandestimationplugin.* = enabled
+burndown.* = enabled
+tasklist.* = enabled

+[icalendar]
+dtstart = my_custom_dtstart_field
+duration = my_custom_duration_field
+short_date_format = %d/%m/%Y;%Y-%m-%d
+date_time_format = %d/%m/%Y %H:%M;%Y-%m-%d %H:%M

+[ticket-custom]
+my_custom_dtstart_field = text
+my_custom_dtstart_field.label = Planned Date
+my_custom_duration_field = text
+my_custom_duration_field.label = Duration
+action_item = text
+action_item.label = Action Item

[trac]
-mainnav = wiki,timeline,roadmap,browser,revtree,tickets,newticket,search
+mainnav = wiki,blog,timeline,roadmap,browser,revtree,tickets,newticket,search

Permissions need to be set appropriately for buttons to show up in the menubar.

$ trac-admin /var/www/trac permission list
$ trac-admin /var/www/trac permission add anonymous STATS_VIEW

Custom design

I tried to install GoogleCodeTheme but the repository is empty. Downloading the archive creates a zip file that seems invalid. Finally I gave up on it and tried the GameDev theme instead.

$ easy_install http://trac-hacks.org/svn/gamedevtheme/0.11/
...
Installed /usr/local/lib/python2.7/dist-packages/TracGamedevTheme-2.0-py2.7.egg
$ diff -u trac.ini.prev trac.ini
[components]
+themeengine.* = enabled
+gamedevtheme.* = enabled

[theme]
-theme = default
+theme = Gamedev

Conclusion

Plug-in architectures are great but it seems trac went a little overboard as most of the expected functionality comes in the form of plug-ins more or less complex to configure.

In the end I decided to settle on using redmine because most of what I am looking for comes pre-packaged in-the-box (except changing LDAP password). How I set up redmine is the subject of a further post.

by Sebastien Mirolo on Sun, 2 Oct 2011


Bring fully-featured SaaS products to production faster.

Follow us on