Skip to content

Instantly share code, notes, and snippets.

@mlbright
Created August 17, 2009 18:22
Show Gist options
  • Save mlbright/169288 to your computer and use it in GitHub Desktop.
Save mlbright/169288 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
# backup svn, rsync backups to a remote location
import subprocess
import sys, time, os, datetime
import ConfigParser
touch = "/tmp/svn-remote-backup.touch"
source = "/svn/backups/" # Note trailing slash
target = "test@example:/storage/svn/backups"
rsync = "/usr/bin/rsync"
arguments = "-avz --delete -e ssh"
cmd = "%s %s %s %s &> %s" % (rsync, arguments, source, target, touch)
recipients = "mlbright@gmail.com"
timer = time.time
msg = ["svn backup is complete: rsync finished successfully",
"running time: %.2f seconds",
"=====================================================" ]
help_message = "Usage: svn-remote-backup.py REPOSITORYDIR BACKUPDIR"
class Usage(Exception):
def __init__(self, msg=help_message):
self.msg = msg
def removeOldBackups(directory, cutoff=21):
day = 24 * 60 * 60 # seconds in a day
for dirpath, dirnames, filenames in os.walk(directory, topdown=False):
for file in filenames:
f = os.path.join(dirpath, file)
mtime = os.stat(f)[-2]
now = int(time.time())
if now - mtime >= cutoff * day:
os.remove(f)
def mkdirp(newdir):
"""
http://code.activestate.com/recipes/82465/
- Limitations: it doesn't take the optional 'mode' argument yet
- already exists, silently complete
- regular file in the way, raise an exception
- parent directory(ies) does not exist, make them as well
"""
if os.path.isdir(newdir):
pass
elif os.path.isfile(newdir):
raise OSError("a file with the same name as the desired " \
"dir, '%s', already exists." % newdir)
else:
head, tail = os.path.split(newdir)
if head and not os.path.isdir(head):
mkdirp(head)
if tail:
try:
os.mkdir(newdir)
except:
raise OSError("Cannot create directory.")
def backup(repodir, backupdir):
youngest_rev = "svnlook youngest %s"
svndump = "svnadmin dump -q %s > %s"
svndumpincr = "svnadmin dump -q --incremental --revision %s:%s %s > %s"
bzip = "bzip2 %s"
for repo in os.listdir(repodir):
backup = backupdir + os.sep + repo
full = backup + os.sep + 'full'
incr = backup + os.sep + 'incr'
mkdirp(full)
mkdirp(incr)
rev_file = backup + os.sep + 'rev'
rev1 = 0
if os.path.isfile(rev_file):
t = file(rev_file).read()
rev1 = int(file(rev_file).read().strip())
p = subprocess.Popen(youngest_rev % (repodir + os.sep + repo),
shell=True, stdout=subprocess.PIPE)
rev2 = int(p.stdout.read().strip())
if rev2 == 0:
continue
if rev1 == 0 or datetime.date.today().isoweekday() == 5:
# full backup
abbrevdate = str(datetime.date.today()).replace('-', '')
fname = full + os.sep + '-'.join([repo, abbrevdate, "R" + str(rev2)])
if not os.path.isfile(fname):
subprocess.call(svndump % (repodir + os.sep + repo, fname), shell=True)
subprocess.call(bzip % (fname), shell=True)
elif rev1 != rev2:
# incremental backup
rev1 += 1
fname = incr + os.sep + '-'.join([repo, str(rev1), str(rev2)])
subprocess.call(svndumpincr % (str(rev1), str(rev2), repodir + os.sep + repo, fname), shell=True)
subprocess.call(bzip % (fname), shell=True)
else:
continue
# update the state file
fd = open(rev_file, 'w')
fd.write(str(rev2))
fd.close()
# delete backups older than 21 days (cutoff = 21 by default)
removeOldBackups(backup + os.sep + 'full')
removeOldBackups(backup + os.sep + 'incr')
def sync():
if os.path.exists(touch):
return
fd = open(touch, "w")
fd.close()
start = timer()
while True:
ret = subprocess.call(cmd, shell=True)
if ret != 0:
time.sleep(30)
else:
break
end = timer()
rsync_output = file(touch).read()
msg.append(rsync_output)
body = os.linesep.join(msg)
body = body % (end - start)
mailcmd = "echo '%s' | mail -s 'svn rsync done' %s" % (body, recipients)
subprocess.call(mailcmd, shell=True)
os.remove(touch)
def main(argv=None):
if argv is None:
argv = sys.argv
try:
if len(argv) < 2:
raise Usage()
repodir = sys.argv[1]
backupdir = sys.argv[2]
baddir = "%s is not a directory"
if not os.path.isdir(repodir):
raise Usage(baddir % (repodir))
if not os.path.isdir(backupdir):
raise Usage(baddir % (repodir))
backup(repodir, backupdir)
sync()
except Usage, err:
print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg)
return 1
if __name__ == "__main__":
sys.exit(main())
"""
#!/bin/bash
######################################################################
# Copyright (C) 2006-2007 pmade inc. (Peter Jones pjones@pmade.com)
# All Rights Reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name of the Author nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
######################################################################
if [ $# -eq 2 ]; then
REPODIR=$1
BACKUPDIR=$2
else
echo "Usage: svn-backup.sh REPOSITORYDIR BACKUPDIR"
exit 1
fi
for REPOSITORY in $REPODIR/*
do
REPO=`basename $REPOSITORY`
BACKUP=$BACKUPDIR/$REPO
REV_FILE=$BACKUP/rev
mkdir -p $BACKUP/full $BACKUP/incremental
REV1="" # instead of previous iteration's value
[ -r $REV_FILE ] && REV1=`head -1 $REV_FILE`
[ "x$REV1" = "x" ] && REV1=0
REV2=`svnlook youngest $REPOSITORY`
if [ $REV1 -eq 0 -o `date +%u` -eq 4 ]; then
FNAME=$BACKUP/full/$REPO-`date +%Y%m%d`-R$REV2
if [ -r $FNAME ]; then
continue
else
svnadmin dump -q $REPOSITORY > $FNAME
bzip2 $FNAME
fi
elif [ $REV1 != $REV2 ]; then
REV1=`expr $REV1 + 1`
FNAME=$BACKUP/incremental/$REPO-$REV1-$REV2
svnadmin -q dump --incremental --revision $REV1:$REV2 $REPOSITORY > $FNAME
bzip2 $FNAME
else
continue
fi
# update the state file
echo $REV2 > $REV_FILE
# delete backups older than 21 days
find $BACKUP/full $BACKUP/incremental -type f -mtime +21 -exec rm {} \;
done
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment