Running cgit under nginx

Like many in the open source community, I’ve developed a bit of an addiction to git. I use git for keeping track of class notes, homework, this blog, nearly everything we do on the OSU Open Source Lab Systems team and, of course, coding. I have hosted a number of repositories (mostly for long dead programming projects on GitHub but while it has a lot of great features, sometimes I just want a level of control that github just doesn’t offer.

But when you do want to share your git repositories, you probably want a way to browse them via the web. And there just aren’t that many options. Gitweb is probably the most popular and runs on kernel.org, with cgit in second. Of the two I prefer cgit because it is light and fast with excellent caching. But both cgit and GitWeb are CGI based, and nginx doesn’t support CGI.

The solution I came up with is to route requests through a little program called fcgiwrap. To begin with, you’ll need to download and install it.

git clone git://github.com/gnosek/fcgiwrap.git
cd fcgiwrap
make
make install

However fcgiwrap alone can’t handle FastCGI requests, so we need to set upsomething like spawn-fcgi. At least on Gentoo this is fairly easy.

echo "www-servers/spawn-fcgi" >> /etc.portage/package.keywords
emerge -av spawn-fcgi
ln -s spawn-fcgi /etc/init.d/spawn-fcgi.cgit
cp /etc/conf.d/spawn-fcgi /etc/conf.d/spawn-fcgi.cgit

Because you might want to do this process for multiple CGI scripts the init script is designed to be symlinked for each instance. Now you need to edit /etc/conf.d/spawn-fcgi.cgit to look something like this

# I used a unix socket since nginx and cgit are on the same machine
FCGI_SOCKET=/var/run/spawn-fcgi/code_russellhaering_com

# Again, I don't need these
FCGI_ADDRESS=
FCGI_PORT=

# Tell spawn-fcgi what program to execute
FCGI_PROGRAM=/usr/local/bin/fcgiwrap

# You don't really need to change any of these
FCGI_CHILDREN=1
FCGI_CHROOT=
FCGI_CHDIR=

# You probably don't want cgit running as root
FCGI_USER=nginx
FCGI_GROUP=nginx

# I'm not sure if this is really essential
ALLOWED_ENV="PATH"

I found that I had to ‘chown -R nginx:nginx /var/run/spawn-fcgi since both programs accessing the socket will be running as user nginx.

Of course you’ll also need cgit itself

wget http://hjemli.net/git/cgit/snapshot/cgit-stable.tar.gz
tar -xf cgit-stable.tar.gz
make get-git && make
# Create a web root
mkdir -p /var/www/code.russellhaering.com/htdocs
# And copy over the important stuff
cp cgit /var/www/code.russellhaering.com/htdocs/
cp cgit.css /var/www/code.russellhaering.com/htdocs/
cp cgit.png /var/www/code.russellhaering.com/htdocs/

Now edit /etc/cgitrc

# enable caching of up to 1000 output entries
cache-size=1000

# page title for the root page (repo listing)
root-title=Russell's Git Repositories

# description for the root page
root-desc=

# link to css file
css=/cgit.css

# link to logo file
logo=/cgit.png

# root path for cached output
cache-root=/var/cache/cgit

# time-to-live settings: specify how long (in minutes) different pages should
# be cached. specify 0 for instant expiration and -1 for immortal pages

# ttl for root page (repo listing)
cache-root-ttl=0

# ttl for repo summary page
cache-repo-ttl=0

# ttl for other dynamic pages
cache-dynamic-ttl=0

# ttl for static pages (addressed by SHA-1)
cache-static-ttl=0

# allow download of tar.gz and tar.bz2 files
snapshots=tar.gz tar.bz2

include=/etc/cgitrc.local

Notice how I left caching disabled. Thats because I’m going to use the built in nginx fastcgi caching. You’ll also need to edit /etc/cgitrc.local - this is what I have right now.

repo.url=russellhaering.com
repo.desc=Hyde configuration for russellhaering.com
repo.path=/var/lib/git/russellhaering.com.git
repo.owner=russell_h
repo.clone-url=git://code.russellhaering.com/russellhaering.com

And of course you’ll also need to set up nginx itself.

I added a line like this to the ‘http’ section of my nginx.conf

# Use up to 10 Megabytes for the 'code' cache, keep inactive cache around for 1h
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=code:10m inactive=1h max_size=100m;

You may need to create and set appropriate permissions on that cache directory

And added a ‘server’ section like this

server {
	listen 80;
	server_name code.russellhaering.com;

	# Serve static files
	location ~* ^.+\.(css|png|ico)$ {
		root /var/www/code.russellhaering.com/htdocs;
		expires 30d;
	}

	location / {
		rewrite ^/$         /cgit   permanent;
		rewrite ^/cgit/$    /cgit   permanent;

		fastcgi_cache      code;
		fastcgi_cache_valid 200 5m;
		fastcgi_cache_use_stale off;

		fastcgi_pass            unix:/var/run/spawn-fcgi/code_russellhaering_com-1;
		fastcgi_read_timeout    5m;
		fastcgi_index   /;

		fastcgi_param    DOCUMENT_ROOT    /var/www/code.russellhaering.com/htdocs;
		fastcgi_param    SCRIPT_FILENAME  /var/www/code.russellhaering.com/htdocs/cgit;
		fastcgi_param    QUERY_STRING  $query_string;
		fastcgi_param    REQUEST_METHOD  $request_method;
		fastcgi_param    CONTENT_TYPE  $content_type;
		fastcgi_param    CONTENT_LENGTH  $content_length;
		fastcgi_param    GATEWAY_INTERFACE  CGI/1.1;
		fastcgi_param    SERVER_SOFTWARE  nginx;
		fastcgi_param    SCRIPT_NAME  $fastcgi_script_name;
		fastcgi_param    REQUEST_URI  $request_uri;
		fastcgi_param    DOCUMENT_URI  $document_uri;
		fastcgi_param    DOCUMENT_ROOT  $document_root;
		fastcgi_param    SERVER_PROTOCOL  $server_protocol;
		fastcgi_param    REMOTE_ADDR  $remote_addr;
		fastcgi_param    REMOTE_PORT  $remote_port;
		fastcgi_param    SERVER_ADDR  $server_addr;
		fastcgi_param    SERVER_PORT  $server_port;
		fastcgi_param    SERVER_NAME  $server_name;
	}

	access_log /var/log/nginx/code.russellhaering.com/transfer/access_log combined;
	error_log /var/log/nginx/code.russellhaering.com/error/error_log warn;
}

At this point you should be set to go, excepting any permissions, etc that I neglected to mention. Go ahead and try it out

/etc/init.d/spawn-fcgi.cgit start
/etc/init.d/nginx restart

I suppose I should mention that I am seeing an issue that I think is related to the caching, where every so often when you click a link in cgit the address in the address bar updates but the page doesn’t actually change. Clicking the link again has always fixed the problem. I looked in the logs and didn’t see anything unusual, so it may be an issue in Chromium, but for the moment I’m content to leave it. If you happen to know what this is about and want to share, please do.