#!/usr/bin/perl # Copyright 2011, David Bremner # # This add-on to gitolite is licensed under the same terms as gitolite # At the time of this writing this is GPL-2, but I also grant # Sitaram Chamarty the right to re-license as he chooses. =pod S3BACKUP - Backup the whole gitolite home directory to amazon s3. RUNNING To run it (assuming you call this "s3backup") As an ADC, only by a user with read access to gitolite-admin : ssh git@server s3backup [-v error|warning|notice|info|debug] [subcommands] You must pass the AWS_SECRET_ACCESS_KEY on stdin. SUBCOMMANDS You may optionally pass one of the following sub commands =over =item incr|full Run incremental or full backup. By default, leave it to duplicity to guess. =item prune remove all but $S3_KEEP_FULL (see Configuration below) =back OPTIONS -v specifies a log level, passed straight to duplicity. incr or full specify backup level CONFIGURATION Make a file ~git/.s3backup.rc that looks like $S3_EUROPE=0; # 1 to store in europe # (only matters at creation) $S3_KEEP_FULL=3; # keep 3 full backups $S3_BUCKET="my_bucket"; # s3 bucket name, will be created # if it doesn't exist. $S3_AWS_KEY_ID="ABADABA57DOO"; # this is the _non_ secret one $S3_ENCRYPT_KEY="DEADBEEF AA232332"; # gpg keys, space delimited. # note that duplicity will abort if # these keys are not trusted =cut use strict; use warnings; die "ENV GL_RC not set\n" unless $ENV{GL_RC}; die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; unshift @INC, $ENV{GL_BINDIR}; require gitolite or die "parse gitolite.pm failed\n"; gitolite->import; my ($perm, $creator) = check_access("gitolite-admin"); die "no read access to gitolite-admin\n" unless $perm =~ /R/; our ($S3_EUROPE, $S3_BUCKET, $S3_AWS_KEY_ID, $S3_ENCRYPT_KEYS, $S3_KEEP_FULL); chdir($ENV{HOME}) or die "chdir $ENV{HOME} : $!\n"; my $configfile = $ENV{HOME} . "/.s3backup.rc"; do $configfile or die "error parsing $configfile\n"; # ANCHOR ALL PATTERNS die 'bad value for $S3_EUROPE' unless (defined($S3_EUROPE) && $S3_EUROPE =~ m/^0|1$/); die 'bad value for $S3_KEEP_FULL' unless (defined($S3_KEEP_FULL) && $S3_KEEP_FULL =~ m/^[0-9]+$/); die 'bad value for $S3_BACKUP' unless (defined($S3_BUCKET) && $S3_BUCKET =~ m/^[a-z\-_0-9\.]+$/); die 'bad value for $S3_AWS_KEY_ID' unless (defined($S3_AWS_KEY_ID) && $S3_AWS_KEY_ID =~ m/^[A-Z0-9]+$/); die 'bad value for $S3_ENCRYPT_KEYS' unless (defined($S3_ENCRYPT_KEYS) && $S3_ENCRYPT_KEYS =~ m/^[a-fA-F0-9]+(\s+[a-fA-F0-9]+)*/); my $verbosity='notice'; if (scalar(@ARGV)>0 and $ARGV[0] eq '-v'){ shift; $verbosity = shift; } die "bad verbosity" unless ($verbosity =~ m/^(error|warning|notice|info|debug)$/); my $subcommand=shift || 'default'; die "bad subcommand" if (defined($subcommand) && $subcommand !~ m/^(incr|full|prune|default)$/); $ENV{AWS_ACCESS_KEY_ID}=$S3_AWS_KEY_ID; chomp($ENV{AWS_SECRET_ACCESS_KEY}=<>) or die "must pass SECRET_ACCESS_KEY on stdin"; my @args=(); if ($subcommand ne 'default' ){ if ($subcommand eq 'prune') { push(@args, 'remove-all-but-n-full', $S3_KEEP_FULL, '--force'); } else { push(@args, $subcommand); } } push(@args, '--verb', $verbosity); push(@args, '--s3-use-new-style'); foreach my $key (split(' ',$S3_ENCRYPT_KEYS)){ push(@args, '--encrypt-key', $key); } push(@args, '--s3-european-buckets') if ($S3_EUROPE); push(@args, $ENV{HOME}) unless ($subcommand eq 'prune'); push(@args, 's3+http://'.$S3_BUCKET); my $semaphore=$ENV{HOME}."/.gitolite.down"; die "$semaphore already exists" if (-f $semaphore); eval { open (SEMFD,'>',$semaphore) or die ("failed to open $semaphore"); my $now = gmtime(); print SEMFD "Repo unavailable due to $subcommand backup started at $now GMT\n"; close SEMFD; system '/usr/bin/duplicity', @args; }; unlink $semaphore;