Created
January 14, 2010 09:20
-
-
Save rwoeber/277010 to your computer and use it in GitHub Desktop.
svn post-commit incremental backup to Amazon S3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# File : s3-post-commit.rb | |
# Description : A program to create svn backups using the | |
# Amazons S3 storage service | |
# Copyright : (c) 2007 Maximilian Schoefmann | |
# License : MIT, see the file MIT-LICENSE | |
# Modified : Richard Woeber | |
# | |
# Usage | |
# copty to svnserver:/path/to/svnrepo/hooks/post-commit and change the following constants | |
# to your projects/s3-account properties | |
# Run initial backup by hand: /path/to/svnrepo/hooks/post-commit path/to/svnrepo/ | |
AWS_ID = '00000000000000000000' | |
AWS_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' | |
BUCKET = 'org.example.myproject' | |
LOG_FILE = "/var/log/svn/s3/#{BUCKET}.log" | |
WORK_DIR = File.join(Dir.tmpdir, "#{Time.now.hash.to_s}#{BUCKET}") | |
SVN_BINDIR = '/usr/local/bin' | |
GZIP = '/bin/gzip' | |
KEEP_FILES = false | |
%w{rubygems logger base64 sha1 tempfile fileutils etc aws/s3}.each{|lib| require lib} | |
LOG = Logger.new(LOG_FILE) | |
def usage! | |
puts "Usage: #{$0} [path/to/repo [revision]]" | |
exit 0 | |
end | |
usage! if ARGV[0] =~ /^(?:-h|--help)$/ | |
repo = ARGV[0] || REPO_DIR | |
rev = (ARGV[1] || `#{SVN_BINDIR}/svnlook youngest #{repo}`.strip).to_i | |
include AWS::S3 | |
# returns -1 when no revision was found | |
def find_last_rev(bucket) | |
# Pattern of the S3Object-keys | |
pattern = /^rev_(\d+)_(\d+).*$/ | |
rev = -1 | |
# S3Objects can come theoretically in any order... | |
bucket.each do |o| | |
if o.key.match(pattern) | |
rev = $2.to_i if $2.to_i > rev | |
end | |
end | |
rev | |
end | |
# creates an incremental/delta/gzipped dump from rev1 to rev2 | |
def dump_repository(repo, rev1, rev2) | |
FileUtils.mkdir_p(WORK_DIR) | |
dump_file = File.join(WORK_DIR, "rev_#{rev1}_#{rev2}.dump.gz") | |
`#{SVN_BINDIR}/svnadmin dump #{repo} --incremental --deltas -q -r #{rev1}:#{rev2} | #{GZIP} -f >#{dump_file}` | |
if !FileTest.exist?(dump_file) || $?.exitstatus != 0 | |
LOG.fatal "'svnadmin dump' to file #{dump_file} failed" | |
exit 1 | |
end | |
dump_file | |
end | |
def connect_to_s3! | |
begin | |
AWS::S3::Base.establish_connection!( | |
:access_key_id => AWS_ID, | |
:secret_access_key => AWS_KEY | |
) | |
rescue => e | |
LOG.fatal e | |
exit 1 | |
end | |
end | |
def write_to_s3!(file, bucket_name) | |
begin | |
S3Object.store( | |
File.basename(file), | |
open(file), | |
bucket_name | |
) | |
rescue => e | |
LOG.fatal e | |
exit 1 | |
end | |
end | |
def main(repo, rev) | |
connect_to_s3! | |
bucket_name = BUCKET #"#{Base64.encode64(SHA1.digest(AWS_ID)).gsub(/\W/,'')}-#{BUCKET}" | |
begin | |
bucket = Bucket.find(bucket_name) | |
rescue NoSuchBucket | |
LOG.info "Creating new bucket #{bucket_name}" | |
Bucket.create(bucket_name) | |
bucket = Bucket.find(bucket_name) | |
end | |
next_rev = find_last_rev(bucket) + 1 | |
if next_rev > rev | |
LOG.warn "Last backup is of revision #{next_rev}, current revision is #{rev} - Aborting" | |
exit 0 | |
end | |
dump_file = dump_repository(repo, next_rev, rev) | |
write_to_s3!(dump_file, bucket_name) | |
LOG.info "Backup of revisions #{next_rev} to #{rev} finished" | |
unless KEEP_FILES | |
File.unlink(dump_file) | |
FileUtils.rmdir(WORK_DIR) | |
end | |
end | |
main(repo, rev) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment