Julius Plenz – Blog

gitolite and interpolated paths

The Git daemon supports a feature called Interpolated Path. For example, my Git daemon is called like this:

git-daemon --user=git --group=git \
    --listen=0.0.0.0 --reuseaddr \
    --interpolated-path=/var/git/repositories/%IP%D \
    /var/git/repositories

What it does is it translates the request for a git repository before actually searching for it. When I do a git clone git://git.plenz.com/configs.git what actually happens is that the Git daemon will deliver the repository at /var/git/repository/176.9.247.89/configs.git. That is especially nice in environments where you share one git daemon for several people/projects and have sufficient IP addresses.

However, Gitolite doesn't seem to have a config option for this. Now what I did was patch Gitolite so that it'll prepend the repository name with the IP of the network interface the call came through.

This information is available from the SSH_CONNECTION environment variable.

(The patch to gl-compile-conf is needed so that Gitolite will create a projects.list per virtual host. That way, you can use different configuration files for each CGit instance, according to your VHost.)

diff --git a/src/gl-auth-command b/src/gl-auth-command
index 61b2f5a..a8d1976 100755
--- a/src/gl-auth-command
+++ b/src/gl-auth-command
@@ -124,6 +124,12 @@ unless ( $verb and ( $verb eq 'git-init' or $verb =~ $R_COMMANDS or $verb =~ $W_
     exit 0;
 }

+# prepend host's ip address
+# SSH_CONNECTION looks like this: 92.225.139.246 41714 176.9.34.52 22
+#                                 from-ip        port  to-ip       port
+my $connected_via = (split " " => $ENV{"SSH_CONNECTION"})[2] // "";
+$repo = $connected_via . "/" . $repo;
+
 # some final sanity checks
 die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/;
 die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./;
diff --git a/src/gl-compile-conf b/src/gl-compile-conf
index 2d4110f..6f8f0d7 100755
--- a/src/gl-compile-conf
+++ b/src/gl-compile-conf
@@ -577,6 +577,22 @@ unless ($GL_NO_DAEMON_NO_GITWEB) {
     }
     close $projlist_fh;
     rename "$PROJECTS_LIST.$$", $PROJECTS_LIST;
+
+    # vhost stuff
+    my %vhost = ();
+    for my $proj (sort keys %projlist) {
+        my ($ip, $repo) = split '/' => $proj => 2;
+        $vhost{$ip} //= [];
+        push @{$vhost{$ip}} => $repo;
+    }
+    for my $v (keys %vhost) {
+        my $v_file = "$REPO_BASE/$v/projects.list";
+        my $v_fh = wrap_open( ">", $v_file . ".$$");
+        print $v_fh $_ . "\n" for @{$vhost{$v}};
+        close $v_fh;
+        rename $v_file . ".$$" => $v_file;
+    }
 }

 # ----------------------------------------------------------------------------

With this patch applied, I can git push git.plenz.com:config.git and it will end up pushing to 176.9.247.89/configs.git, fully transparent to the user.

N.B.: This strictly is a "works for me" solution. It's not very clean – but I don't plan on properly implementing a config setting. As I said, it works for me. ;-)

Update 2014-09-10: I upgraded to Gitolite v3.6, and it’s different there. There is a functionality called “triggers” now, which you can activate in the .gitolite.rc like such: Uncomment the key LOCAL_CODE => "$ENV{HOME}/local", and insert these two trigger actions:

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

Then, place this code under ~/local/lib/Gitolite/Triggers/VHost.pm (you’ll need to create that directory first):

package Gitolite::Triggers::VHost;

use strict;
use warnings;

use File::Slurp qw(read_file write_file);

use Gitolite::Rc;

sub input {
    return unless $ENV{SSH_ORIGINAL_COMMAND};
    return unless $ENV{SSH_CONNECTION};

    my $dstip = (split " " => $ENV{"SSH_CONNECTION"})[2] // "";
    return unless $dstip;
    return if $dstip eq "127.0.0.1";

    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
    if($ENV{SSH_ORIGINAL_COMMAND} =~ m/(?<cmd>$git_commands) '\/?(?<repo>\S+)'$/) {
        $ENV{SSH_ORIGINAL_COMMAND} = "$+{cmd} '$dstip/$+{repo}'";
    }
}

sub post_compile {
    my %vhost = ();
    my @projlist = read_file("$ENV{HOME}/projects.list");
    for my $proj (sort @projlist) {
        my ($ip, $repo) = split '/' => $proj => 2;
        $vhost{$ip} //= [];
        push @{$vhost{$ip}} => $repo;
    }
    for my $v (keys %vhost) {
        write_file("$rc{GL_REPO_BASE}/$v/projects.list",
                   { atomic => 1 }, @{$vhost{$v}});
    }
}

1;

posted 2011-09-25 tagged git and gitolite

Upgrading to Gitolite and CGit

I finally found some time to update the git version used on my server. It's running a 1.7.1 now, which is just slightly better than the previous 1.5 version. ;-)

Debian package maintainers bother me again, though. The simply erase the git user and put a gitdaemon user in it's place, without asking for confirmation. Will probably happen with the next upgrade, too...

Now, having a recent git version enables me to set up gitolite instead of the old gitosis. Setup is easy, straight-forward and described in many places.

I used this script to convert my existing gitosis.conf to match gitolite's syntax; I noticed a major hiccup though: setting a description automatically enables gitweb access. I had to read the source to find this out, because I couldn't remember it from the docs and it didn't once cross my mind that you could actually want the behaviour to be like that. – As in, you could just describe a project without wanting to make it public – no?

However, with gitolite, I can finally use CGit too (because only gitolie produces a repositories.list which is in the format that CGit understands, ie. without description appended to the lines).

So now the repository page is all new and shiny. See, for example, this side-by-side diff. Nice, and faster than Gitweb.

Integrating CGit with Lighttpd was another two-hour fiddle-try-fail-repeat session. I finally came up with this snippet:

$HTTP["host"] =~ "^git\.(plenz\.com|feh\.name)(:\d+)?$" {
  alias.url = (
    "/cgit.css" => "/usr/share/cgit/cgit.css",
    "/cgit.png" => "/usr/share/cgit/cgit.png",
    "/cgit.cgi" => "/usr/lib/cgi-bin/cgit.cgi",
    "/"         => "/usr/lib/cgi-bin/cgit.cgi",
  )
  cgi.assign = ( ".cgi" => "" )
  url.rewrite-once = (
    "^/cgit\.(css|png)" => "$0",
    "^/.+" => "/cgit.cgi$0"
  )
}

It's important here that you noop-rewrite the CSS and PNG file to make Lighty expand the alias. Also, rewriting to an absolute filepath won't work; that's why the cgit.cgi aliases have to appear twice.

posted 2011-01-31 tagged git, cgit, gitolite and lighttpd