ldx's blog

Posted v 15 január 2012

Nginx and git via HTTP

Nginx is a high-performance HTTP server and proxy that has been gaining a lot of traction recently. It's easy to set up, can handle a large number of clients with minimal resources, modular and supports SSL/TLS. Dynamic web content can be served via FastCGI to Nginx.

Git uses WebDAV for pushing via HTTP(S). Even though Nginx handles WebDAV requests, not all DAV commands are implemented in its DAV module. Locking and PROPFIND are missing in particular, and since these commands are used by Git when pushing via HTTP, pushing to a plain Nginx server is not possible at the moment.

One solution is to run Apache on a different port and proxy Git requests to it. However, Apache is such a resource hog that if a lightweight solution is desired it's out of question. Here comes PyWebDAV, a standalone WebDAV server written in Python, into the picture.

PyWedDAV is available in both Ubuntu and Debian, so just install it via apt-get:

$ sudo apt-get install python-webdav

The actual binary for running the server will be at /usr/bin/davserver. The packages have no init script, but it's easy to create one, or you can just shove it at the end of /etc/rc.local:

/usr/bin/sudo -u www-data /usr/bin/davserver -D /var/www/git -n -P 8008 -i 0 -J -d start
exit 0

This will start davserver on port 8008 running as user www-data.

It's now only a matter of making Nginx proxy git requests to PyWebDAV:

server {
    ...
    location /git {
        proxy_pass       http://localhost:8008/;
        proxy_redirect   http://localhost:8008 http://git.mysite.com;
    }
}

This assumes you store your git repositories under /var/www/git.

However, trying to git push to this server reveals something is still not working as expected:

$ git push
error: cannot lock existing info/refs
error: failed to push to 'http://git.mysite.com/git/foobar.git'

Even though davserver returns 200 for a LOCK operation, Git checks that the lock timeout is indeed correct in the reply. PyWebDAV by default uses 'Seconds-Infinite' for timeouts, but Git expects a numerical timeout value. This can be fixed in /usr/share/pyshared/DAV/locks.py, just change 'Infinite' at its two occurences to some sane default value. Here's a diff to make it easier for the lazy:

--- /usr/lib/python2.6/dist-packages/DAV/locks.py       2010-04-15 11:25:35.000000000 +0200
+++ locks.py    2012-01-15 11:05:18.406041553 +0100
@@ -169,7 +169,7 @@
                     token = tokenFinder(listitem)
                     if token and self._l_hasLock(token):
                         lock = self._l_getLock(token)
-                        timeout = self.headers.get('Timeout', 'Infinite')
+                        timeout = self.headers.get('Timeout', '3600')
                         lock.setTimeout(timeout) # automatically refreshes
                         found = 1

@@ -188,7 +188,7 @@
     """ Lock with support for exclusive write locks. Some code taken from
     webdav.LockItem from the Zope project. """

-    def __init__(self, uri, creator, lockowner, depth=0, timeout='Infinite',
+    def __init__(self, uri, creator, lockowner, depth=0, timeout='3600',
                     locktype='write', lockscope='exclusive', token=None, **kw):

         self.uri = uri

Just save it as locks.diff, apply the patch with patch -p0 < locks.diff, restart davserver and now git push should be working fine.

Category: development, git, linux, nginx

Comments