171 lines
6.1 KiB
Perl
Executable file
171 lines
6.1 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
|
|
# terminology:
|
|
# native repo: a repo for which we are the master; pushes happen here
|
|
# authkeys: shorthand for ~/.ssh/authorized_keys
|
|
|
|
# this is invoked in one of two ways:
|
|
|
|
# (1) locally, from a shell script or command line
|
|
|
|
# (2) from a remote server, via authkeys, with one argument (the name of the
|
|
# sending server), similar to what happens with normal users and the
|
|
# 'gl-auth-command' program. SSH_ORIGINAL_COMMAND will then contain the
|
|
# actual command that the remote sent.
|
|
#
|
|
# Currently, these commands are (a) 'info', (b) 'git-receive-pack' when a
|
|
# mirror push is *received* by a slave, (c) 'request-push' sent by a slave
|
|
# (possibly via an ADC) when the slave finds itself out of sync, (d) a
|
|
# redirected push, from a user pushing to a slave, which is represented not by
|
|
# a command per se but by starting with "USER=..."
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# this section of code snarfed from gl-auth-command
|
|
BEGIN {
|
|
$0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2;
|
|
}
|
|
|
|
use lib $ENV{GL_BINDIR};
|
|
|
|
use gitolite_rc;
|
|
use gitolite_env;
|
|
use gitolite;
|
|
|
|
setup_environment();
|
|
die "fatal: GL_HOSTNAME not set in rc; mirroring disabled\n" unless $GL_HOSTNAME;
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
die "please read the gitolite mirroring documentation; this program is too
|
|
critical for you to just run it based on a 'usage' message.\n" if not @ARGV or $ARGV[0] eq '-h';
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# deal with local invocations first
|
|
|
|
# on the "master", run from a shell, for one specific repo, with an optional
|
|
# list of slaves, like so:
|
|
# gl-mirror-shell request-push some-repo [optional list of slaves/keys]
|
|
if ( ($ARGV[0] || '') eq 'request-push' and not $ENV{SSH_ORIGINAL_COMMAND} ) {
|
|
shift;
|
|
my $repo = shift or die "fatal: missing reponame\n";
|
|
-d "$REPO_BASE/$repo.git" or die "fatal: no such repo?\n";
|
|
|
|
# this is the default argument if no slave list or key is supplied
|
|
@ARGV = ('gitolite.mirror.slaves') unless @ARGV;
|
|
|
|
my @slaves = ();
|
|
my %seen = ();
|
|
# each argument in @ARGV is either a slave name, or a gitolite mirroring
|
|
# key to be replaced with its value, split into a list of slaves
|
|
while (@ARGV) {
|
|
$a = shift @ARGV;
|
|
if ($a =~ /^gitolite\.mirror\.[\w.-]+$/) {
|
|
my @values = split(' ', `git config --file $REPO_BASE/$repo.git/config --get $a` || '');
|
|
unshift @ARGV, @values;
|
|
} else {
|
|
push @slaves, $a unless $seen{$a}++;
|
|
}
|
|
}
|
|
|
|
exit 1 unless @slaves;
|
|
# we don't want to complain louder than that because the most common
|
|
# use of this script on the master server is via cron, run against
|
|
# *all* known repos without checking their individual key values
|
|
|
|
print STDERR "info: mirror-push $repo ", join(" ", @slaves), "\n";
|
|
system("gl-mirror-push", $repo, @slaves);
|
|
|
|
exit 0;
|
|
}
|
|
|
|
# ----------
|
|
|
|
# now the remote invocations; log it, then get the sender name
|
|
my $sender = shift;
|
|
$ENV{GL_USER} ||= "host:$sender";
|
|
# default SSH_ORIGINAL_COMMAND is 'info', as usual
|
|
$ENV{SSH_ORIGINAL_COMMAND} ||= 'info';
|
|
# and it's too long to bloody type...
|
|
my $soc = $ENV{SSH_ORIGINAL_COMMAND};
|
|
log_it();
|
|
|
|
# ----------
|
|
|
|
# our famous 'info' command
|
|
if ($soc eq 'info') {
|
|
print STDERR "Hello $sender, I am $GL_HOSTNAME\n";
|
|
|
|
exit;
|
|
}
|
|
|
|
# ----------
|
|
|
|
# when running on the "slave", we have to "receive" the `git push --mirror`
|
|
# from a master. Check that the repo is indeed a slave and the sender is the
|
|
# correct master before allowing the push.
|
|
|
|
if ($soc =~ /^git-receive-pack '(\S+)'$/) {
|
|
my $repo = $1;
|
|
die "fatal: invalid characters in $repo\n" unless $repo =~ $REPONAME_PATT;
|
|
my $mm = mirror_mode($repo);
|
|
|
|
# reminder: we're not going through the slave-side gl-auth-command. This
|
|
# is a server-to-server transaction, with an authenticated sender.
|
|
# Authorisation consists of checking to make sure our config says this
|
|
# sender is indeed the master for this repo
|
|
die "$ABRT fatal: $GL_HOSTNAME <==//== $sender mirror-push rejected: $repo is $mm\n" unless $mm eq "slave of $sender";
|
|
print STDERR "$GL_HOSTNAME <=== ($repo) ==== $sender\n";
|
|
|
|
$ENV{GL_BYPASS_UPDATE_HOOK} = 1;
|
|
$ENV{GL_REPO} = $repo;
|
|
# replace the repo path with the full path and hand off to git-shell
|
|
$soc =~ s(')('$ENV{GL_REPO_BASE_ABS}/);
|
|
exec("git", "shell", "-c", $soc);
|
|
}
|
|
|
|
# ----------
|
|
|
|
# a slave may have found itself out of sync (perhaps the network was down at
|
|
# the time of the last push to the master), and now wants to request a sync.
|
|
# This is similar to the "local invocation" described above, but we check the
|
|
# sender name against gitolite.mirror.slaves to prevent some random slave from
|
|
# asking for a repo it should not be having!
|
|
|
|
if ($soc =~ /^request-push (\S+)$/) {
|
|
my $repo = $1;
|
|
die "fatal: invalid characters in $repo\n" unless $repo =~ $REPONAME_PATT;
|
|
die "$ABRT fatal: $GL_HOSTNAME ==//==> $sender refused: not in slave list\n" unless mirror_listslaves($repo) =~ /(^|\s)$sender(\s|$)/;
|
|
print STDERR "$GL_HOSTNAME ==== ($repo) ===> $sender\n";
|
|
# just one sender, and we've checked that he is "on the list". Foreground...
|
|
system("$ENV{GL_BINDIR}/gl-mirror-push", $repo, "-fg", $sender);
|
|
|
|
exit;
|
|
}
|
|
|
|
# ----------
|
|
|
|
# experimental feature...
|
|
|
|
# when running on the "master", receive a redirected push from a slave. This
|
|
# is disabled by default and needs to be explicitly enabled on both the master
|
|
# and the slave. SEE DOCUMENTATION FOR CAVEATS AND CAUTIONS.
|
|
|
|
if ($soc =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/) {
|
|
|
|
my $user = $1;
|
|
$ENV{SSH_ORIGINAL_COMMAND} = $2;
|
|
my $repo = $3;
|
|
die "fatal: invalid characters in $user\n" unless $user =~ $USERNAME_PATT;
|
|
die "fatal: invalid characters in $repo\n" unless $repo =~ $REPONAME_PATT;
|
|
die "$ABRT fatal: $GL_HOSTNAME <==//== $sender redirected push rejected\n" unless mirror_redirectOK($repo, $sender);
|
|
print STDERR "$GL_HOSTNAME <=== $user ($repo) ==== $sender\n";
|
|
|
|
my $pgm = $0;
|
|
$pgm =~ s([^/]+$)(gl-auth-command);
|
|
|
|
exec($pgm, $user);
|
|
}
|