A vhosting git setup with gitolite and gitweb

Sat, 16 May 2015 18:52:03 +0100
git, gitolite, gitweb

Since gitorious' shutdown I decided it was time to start hosting my own git repositories for my own little projects (although the company which took over gitorious has a Free software offering it seems that their hosted offering is based on the proprietary version, and in any case once bitten, twice shy and all that).

After a bit of investigation I settled on using gitolite and gitweb. I did consider (and even had a vague preference for) cgit but it wasn't available in Wheezy (even backports, and the backport looked tricky) and I haven't upgraded my VPS yet. I may reconsider cgit this once I switch to Jessie.

The only wrinkle was that my VPS is shared with a friend and I didn't want to completely take over the gitolite and gitweb namespaces in case he ever wanted to setup git.hisdomain.com, so I needed something which was at least somewhat compatible with vhosting. gitolite doesn't appear to support such things out of the box but I found an interesting/useful post from Julius Plenz which included sufficient inspiration that I thought I knew what to do.

After a bit of trial and error here is what I ended up with:

Install gitolite

The gitolite website has plenty of documentation on configuring gitolite. But since gitolite is in Debian its even more trivial than even the quick install makes out.

I decided to use the newer gitolite3 package from wheezy-backports instead of the gitolite (v2) package from Wheezy. I already had backports enabled so this was just:

# apt-get install gitolite3/wheezy-backports

I accepted the defaults and gave it the public half of the ssh key which I had created to be used as the gitolite admin key.

By default this added a user gitolite3 with a home directory of /var/lib/gitolite3. Since they username forms part of the URL used to access the repositories I want it to include the 3, so I edited /etc/passwd, /etc/groups, /etc/shadow and /etc/gshadow to say just gitolite but leaving the home directory as gitolite3.

Now I could clone the gitolite-admin repo and begin to configure things.

Add my user

This was simple as dropping the public half into the gitolite-admin repo as keydir/ijc.pub, then git add, commit and push.

Setup vhosting

Between the gitolite docs and Julius' blog post I had a pretty good idea what I wanted to do here.

I wasn't too worried about making the vhost transparent from the developer's (ssh:// URL) point of view, just from the gitweb and git clone side. So I decided to adapt things to use a simple $VHOST/$REPO.git schema.

I created /var/lib/gitolite3/local/lib/Gitolite/Triggers/VHost.pm containing:

package Gitolite::Triggers::VHost;

use strict;
use warnings;

use File::Slurp qw(read_file write_file);

sub post_compile {
    my %vhost = ();
    my @projlist = read_file("$ENV{HOME}/projects.list");
    for my $proj (sort @projlist) {
        $proj =~ m,^([^/\.]*\.[^/]*)/(.*)$, or next;
        my ($host, $repo) = ($1,$2);
        $vhost{$host} //= [];
        push @{$vhost{$host}} => $repo;
    }
    for my $v (keys %vhost) {
        write_file("$ENV{HOME}/projects.$v.list",
                   { atomic => 1 }, join("\n",@{$vhost{$v}}));
    }
}
1;

I then edited /var/lib/gitolite3/.gitolite.rc and ensured it contained:

LOCAL_CODE                =>  "$ENV{HOME}/local",

POST_COMPILE => [ 'VHost::post_compile', ],

(The first I had to uncomment, the second to add).

All this trigger does is take the global projects.list, in which gitolite will list any repo which is configured to be accessible via gitweb, and split it into several vhost specific lists.

Create first repository

Now that the basics were in place I could create my first repository (for hosting qcontrol).

In the gitolite-admin repository I edited conf/gitolite.conf and added:

repo hellion.org.uk/qcontrol
    RW+     =   ijc

After adding, committing and pushing I now have "/var/lib/gitolite3/projects.list" containing:

hellion.org.uk/qcontrol.git
testing.git

(the testing.git repository is configured by default) and /var/lib/gitolite3/projects.hellion.org.uk.list containing just:

qcontrol.git

For cloning the URL is:

gitolite@${VPSNAME}:hellion.org.uk/qcontrol.git

which is rather verbose (${VPSNAME} is quote long in my case too), so to simplify things I added to my .ssh/config:

Host gitolite
Hostname ${VPSNAME}
User gitolite
IdentityFile ~/.ssh/id_rsa_gitolite

so I can instead use:

gitolite:hellion.org.uk/qcontrol.git

which is a bit less of a mouthful and almost readable.

Configure gitweb (http:// URL browsing)

Following the documentation's advice I edited /var/lib/gitolite3/.gitolite.rc to set:

UMASK                           =>  0027,

and then:

$ chmod -R g+rX /var/lib/gitolite3/repositories/*

Which arranges that members of the gitolite group can read anything under /var/lib/gitolite3/repositories/*.

Then:

# adduser www-data gitolite

This adds the user www-data to the gitolite group so it can take advantage of those relaxed permissions. I'm not super happy about this but since gitweb runs as www-data:www-data this seems to be the recommended way of doing things. I'm consoling myself with the fact that I don't plan on hosting anything sensitive... I also arranged things such that members of the groups can only list the contents of directories from the vhost directory down by setting g=x not g=rx on higher level directories. Potentially sensitive files do not have group permissions at all either.

Next I created /etc/apache2/gitolite-gitweb.conf:

die unless $ENV{GIT_PROJECT_ROOT};
$ENV{GIT_PROJECT_ROOT} =~ m,^.*/([^/]+)$,;
our $gitolite_vhost = $1;
our $projectroot = $ENV{GIT_PROJECT_ROOT};
our $projects_list = "/var/lib/gitolite3/projects.${gitolite_vhost}.list";
our @git_base_url_list = ("http://git.${gitolite_vhost}");

This extracts the vhost name from ${GIT_PROJECT_ROOT} (it must be the last element) and uses it to select the appropriate vhost specific projects.list.

Then I added a new vhost to my apache2 configuration:

<VirtualHost 212.110.190.137:80 [2001:41c8:1:628a::89]:80>
        ServerName git.hellion.org.uk
        SetEnv GIT_PROJECT_ROOT /var/lib/gitolite3/repositories/hellion.org.uk
        SetEnv GITWEB_CONFIG /etc/apache2/gitolite-gitweb.conf
        Alias /static /usr/share/gitweb/static
        ScriptAlias / /usr/share/gitweb/gitweb.cgi/
</VirtualHost>

This configures git.hellion.org.uk (don't forget to update DNS too) and sets the appropriate environment variables to find the custom gitolite-gitweb.conf and the project root.

Next I edited /var/lib/gitolite3/.gitolite.rc again to set:

GIT_CONFIG_KEYS                 => 'gitweb\.(owner|description|category)',

Now I can edit the repo configuration to be:

repo hellion.org.uk/qcontrol
    owner   =   Ian Campbell
    desc    =   qcontrol
    RW+     =   ijc
    R       =   gitweb

That R permission for the gitweb pseudo-user causes the repo to be listed in the global projects.list and the trigger which we've added causes it to be listed in projects.hellion.org.uk.list, which is where our custom gitolite-gitweb.conf will look.

Setting GIT_CONFIG_KEYS allows those options (owner and desc are syntactic sugar for two of them) to be set here and propagated to the actual repo.

Configure git-http-backend (http:// URL cloning)

After all that this was pretty simple. I just added this to my vhost before the ScriptAlias / /usr/share/gitweb/gitweb.cgi/ line:

        ScriptAliasMatch \
                "(?x)^/(.*/(HEAD | \
                                info/refs | \
                                objects/(info/[^/]+ | \
                                         [0-9a-f]{2}/[0-9a-f]{38} | \
                                         pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
                                git-(upload|receive)-pack))$" \
                /usr/lib/git-core/git-http-backend/$1

This (which I stole straight from the git-http-backend(1) manpage causes anything which git-http-backend should deal with to be sent there and everything else to be sent to gitweb.

Having done that access is enabled by editing the repo configuration one last time to be:

repo hellion.org.uk/qcontrol
    owner   =   Ian Campbell
    desc    =   qcontrol
    RW+     =   ijc
    R       =   gitweb daemon

Adding R permissions for daemon causes gitolite to drop a stamp file in the repository which tells git-http-backend that it should export it.

Configure git daemon (git:// URL cloning)

I actually didn't bother with this, git http-backend supports the smart HTTP mode which should be as efficient as the git protocol. Given that I couldn't see any reason to run another network facing daemon on my VPS.

FWIW it looks like vhosting could have been achieved by using the --interpolated-path option.

Conclusion

There's quite a few moving parts, but they all seems to fit together quite nicely. In the end apart from adding www-data to the gitolite group I'm pretty happy with how things ended up.