(new mirroring) bulk of the changes are here:
- post-receive now just calls mirror-push - mirror-push is a medium complex shell script (all that backgrounding etc., can't be done so easily in God's first language!) - mirror-shell is now a perl program that does a few different things (receive mirror-pushes, command line re-sync, re-sync requests from a slave, etc) - auth-command changes to reject/redirect non-native pushes
This commit is contained in:
parent
15db108e45
commit
68b45e1616
4 changed files with 243 additions and 45 deletions
|
@ -8,19 +8,15 @@
|
|||
# if you don't do this, git-shell sometimes dies of a signal 13 (SIGPIPE)
|
||||
[ -t 0 ] || cat >/dev/null
|
||||
|
||||
if [ -n "$GL_SLAVES" ]
|
||||
then
|
||||
for mirror in $GL_SLAVES
|
||||
do
|
||||
if git push --mirror $mirror:$GL_REPO.git
|
||||
then
|
||||
:
|
||||
else
|
||||
ssh $mirror mkdir -p $GL_REPO.git
|
||||
ssh $mirror git init --bare $GL_REPO.git
|
||||
ssh $mirror "cd $GL_REPO.git; git config receive.fsckObjects true"
|
||||
git push --mirror $mirror:$GL_REPO.git ||
|
||||
echo "WARNING: mirror push to $mirror failed"
|
||||
fi
|
||||
done
|
||||
fi >&2
|
||||
# even slaves have post-receive hooks, but due to the way the push happens, we
|
||||
# don't have GL_REPO set. So we detect that generic situation and bail...
|
||||
[ -n "$GL_BYPASS_UPDATE_HOOK" ] && exit 0
|
||||
# CAUTION: this means that a server-side push (bypassing gitolite) will not be
|
||||
# mirrored automatically because (a) we don't know GL_REPO (we can deduce it
|
||||
# but we won't!), and (b) we can't distinguish easily between that and this
|
||||
# case (the slave receiving a mirror push case)
|
||||
|
||||
[ -z "$GL_REPO" ] && { echo $0: GL_REPO not set -- this is BAD >&2; exit 1; }
|
||||
[ -z "$GL_BINDIR" ] && { echo $0: GL_BINDIR not set -- this is BAD >&2; exit 1; }
|
||||
|
||||
$GL_BINDIR/gl-mirror-push $GL_REPO
|
||||
|
|
|
@ -93,10 +93,6 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) {
|
|||
$ENV{SSH_ORIGINAL_COMMAND} = 'info';
|
||||
}
|
||||
|
||||
# slave mode should not do much
|
||||
die "server is in slave mode; you can only fetch\n"
|
||||
if ($GL_SLAVE_MODE and $ENV{SSH_ORIGINAL_COMMAND} !~ /^(info|expand|get|git-upload-)/);
|
||||
|
||||
# admin defined commands; please see doc/admin-defined-commands.mkd
|
||||
if ($GL_ADC_PATH and -d $GL_ADC_PATH) {
|
||||
try_adc(); # if it succeeds, this also 'exec's out
|
||||
|
@ -139,6 +135,18 @@ $ENV{GL_REPO}=$repo;
|
|||
# the real git commands (git-receive-pack, etc...)
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# we know the user and repo; we just need to know what perm he's trying for
|
||||
# (aa == attempted access; setting this makes some later logic simpler)
|
||||
my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W');
|
||||
|
||||
# writes may get redirected under certain conditions
|
||||
if ( $GL_HOSTNAME and $aa eq 'W' and mirror_mode($repo) =~ /^slave of (\S+)/ ) {
|
||||
my $master = $1;
|
||||
die "$ABRT $GL_HOSTNAME not the master, please push to $master\n" unless mirror_redirectOK($repo, $GL_HOSTNAME);
|
||||
print STDERR "$GL_HOSTNAME ==== $user ($repo) ===> $master\n";
|
||||
exec("ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}");
|
||||
}
|
||||
|
||||
# first level permissions check
|
||||
|
||||
my ($perm, $creator, $wild);
|
||||
|
@ -150,9 +158,6 @@ if ( $GL_ALL_READ_ALL and $verb =~ $R_COMMANDS and -d "$REPO_BASE/$repo.git") {
|
|||
# it was missing, and you have create perms, so create it
|
||||
new_wild_repo($repo, $user) if ($perm =~ /C/);
|
||||
|
||||
# we know the user and repo; we just need to know what perm he's trying for
|
||||
# (aa == attempted access)
|
||||
my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W');
|
||||
die "$aa access for $repo DENIED to $user
|
||||
(Or there may be no repository at the given path. Did you spell it correctly?)\n" unless $perm =~ /$aa/;
|
||||
|
||||
|
|
88
src/gl-mirror-push
Executable file
88
src/gl-mirror-push
Executable file
|
@ -0,0 +1,88 @@
|
|||
#!/bin/sh
|
||||
|
||||
# arguments: reponame, list of slaves
|
||||
|
||||
# optional flag after reponame: "-fg" to run in foreground. This is only
|
||||
# going to be given by one specific invocation, and if given will only work
|
||||
# for one slave.
|
||||
|
||||
# if list of slaves not given, get it from '...slaves' config
|
||||
|
||||
die() { echo gl-mirror-push${hn:+ on $hn}: "$@" >&2; exit 1; }
|
||||
get_rc_val() { ${0%/*}/gl-query-rc $1; }
|
||||
|
||||
# ----------
|
||||
|
||||
# is mirroring even enabled?
|
||||
hn=`get_rc_val GL_HOSTNAME`
|
||||
[ -z "$hn" ] && exit
|
||||
|
||||
# we should not be invoked directly from the command line
|
||||
[ -z "$GL_LOG" ] && die fatal: do not run $0 directly
|
||||
|
||||
# ----------
|
||||
|
||||
# get repo name then check if it's a local or slave (ie we're not the master)
|
||||
[ -z "$1" ] && die fatal: missing reponame argument
|
||||
repo=$1; shift
|
||||
|
||||
REPO_BASE=`get_rc_val REPO_BASE`
|
||||
cd $REPO_BASE/$repo.git
|
||||
gmm=`git config --get gitolite.mirror.master`
|
||||
|
||||
# is it local? (remember, empty/undef ==> local
|
||||
gmm=${gmm:-local}
|
||||
[ "$gmm" = "local" ] && exit
|
||||
|
||||
# is it a slave?
|
||||
[ "$hn" = "$gmm" ] || die fatal: wrong master. Try $gmm...
|
||||
|
||||
# ----------
|
||||
|
||||
# now see if we want to be foregrounded. Fg mode accepts only one slave
|
||||
[ "$1" = "-fg" ] && {
|
||||
[ -z "$2" ] && die fatal: missing slavename argument
|
||||
[ -n "$3" ] && die fatal: too many slavenames
|
||||
git push --mirror $2:$repo 2>&1 | sed -e "s/^/$hn:/"
|
||||
exit
|
||||
}
|
||||
|
||||
# ----------
|
||||
|
||||
# normal (self-backgrounding) mode. Any number of slaves. If none are given,
|
||||
# use the slave list from the repo config
|
||||
|
||||
export slaves
|
||||
if [ -n "$1" ]
|
||||
then
|
||||
slaves="$*"
|
||||
else
|
||||
slaves=`git config --get gitolite.mirror.slaves`
|
||||
fi
|
||||
|
||||
# ----------
|
||||
|
||||
# print out the job ID, then redirect all 3 FDs
|
||||
export job_id=$$ # can change to something else if needed
|
||||
echo "($job_id&) $hn ==== ($repo) ===>" $slaves >&2
|
||||
logfile=${GL_LOG/%.log/-mirror-pushes.log}
|
||||
exec >>$logfile 2>&1 </dev/null
|
||||
|
||||
# ----------
|
||||
|
||||
# and finally...
|
||||
|
||||
# double fork, no zombies
|
||||
(
|
||||
(
|
||||
echo `date +%T` $repo '===>'
|
||||
|
||||
for s in $slaves
|
||||
do
|
||||
[ "$s" = "$hn" ] && continue # skip ourselves
|
||||
git push --mirror $s:$repo
|
||||
done 2>&1 | sed -e "s/^/ /"
|
||||
echo `date +%T` '===>' $slaves
|
||||
echo
|
||||
) 2>&1 | sed -e "s/^/$job_id:/" & # background the whole thing
|
||||
)
|
|
@ -1,30 +1,139 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/perl
|
||||
|
||||
export GL_BYPASS_UPDATE_HOOK
|
||||
GL_BYPASS_UPDATE_HOOK=1
|
||||
# terminology:
|
||||
# native repo: a repo for which we are the master; pushes happen here
|
||||
# authkeys: shorthand for ~/.ssh/authorized_keys
|
||||
|
||||
get_rc_val() {
|
||||
${0%/*}/gl-query-rc $1
|
||||
# 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
|
||||
# XXX add this program to 'that bindir thing' in doc/developer-notes.mkd
|
||||
BEGIN {
|
||||
$0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2;
|
||||
}
|
||||
|
||||
REPO_BASE=$( get_rc_val REPO_BASE)
|
||||
REPO_UMASK=$(get_rc_val REPO_UMASK)
|
||||
use lib $ENV{GL_BINDIR};
|
||||
|
||||
umask $REPO_UMASK
|
||||
use gitolite_rc;
|
||||
use gitolite_env;
|
||||
use gitolite;
|
||||
|
||||
if echo $SSH_ORIGINAL_COMMAND | egrep git-upload\|git-receive >/dev/null
|
||||
then
|
||||
setup_environment();
|
||||
die "fatal: GL_HOSTNAME not set in rc; mirroring disabled\n" unless $GL_HOSTNAME;
|
||||
|
||||
# the (special) admin post-update hook needs these, so we cheat
|
||||
export GL_RC
|
||||
export GL_ADMINDIR
|
||||
export GL_BINDIR
|
||||
GL_RC=$( get_rc_val GL_RC)
|
||||
GL_ADMINDIR=$(get_rc_val GL_ADMINDIR)
|
||||
GL_BINDIR=$( get_rc_val GL_BINDIR)
|
||||
my $soc = $ENV{SSH_ORIGINAL_COMMAND} || '';
|
||||
|
||||
SSH_ORIGINAL_COMMAND=`echo $SSH_ORIGINAL_COMMAND | sed -e "s:':'$REPO_BASE/:"`
|
||||
exec git shell -c "$SSH_ORIGINAL_COMMAND"
|
||||
else
|
||||
bash -c "cd $REPO_BASE; $SSH_ORIGINAL_COMMAND"
|
||||
fi
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# 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]
|
||||
if ( ($ARGV[0] || '') eq 'request-push' and not $soc) {
|
||||
shift;
|
||||
# rest of the arguments are fit to go directly to gl-mirror-push
|
||||
# (reponame, optional list of slaves)
|
||||
system("gl-mirror-push", @ARGV);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
unless (@ARGV) { print STDERR "fatal: missing argument\n"; exit 1; }
|
||||
|
||||
# ----------
|
||||
|
||||
# now the remote invocations; log it, then get the sender name
|
||||
my $sender = shift;
|
||||
$ENV{GL_USER} ||= "host:$sender";
|
||||
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;
|
||||
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;
|
||||
# replace the repo path with the full path and hand off to git-shell
|
||||
# m-TODO: the admin repo will need more stuff :)
|
||||
$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 "$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 "$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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue