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;
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.