3
###########################################################################
5
# This shell script demonstrates a backup/restore recipe for live #
6
# Subversion repositories, using a standard full+incrementals process. #
8
# This script is intended only as an example; the idea is that you #
9
# can read over it, understand how it works (it's extensively commented) #
10
# and then implement real backup and restore scripts based on this #
13
# To reiterate: this is *not* a backup and restore solution. It's #
14
# really just documentation, in the form of code with comments. #
16
# If you do implement your own scripts based on the recipe here, and #
17
# your implementations are generic enough to be generally useful, #
18
# please post them to dev@subversion.tigris.org. It would be great if #
19
# we could offer a real solution, and not just a description of one. #
21
# This recipe is distilled from the Berkeley DB documentation, see #
22
# http://www.sleepycat.com/docs/ref/transapp/archival.html. #
24
# See also http://www.sleepycat.com/docs/ref/transapp/reclimit.html for #
25
# for possible problems using standard 'cp' in this recipe. #
27
###########################################################################
29
# High-level overview of the full backup recipe:
31
# 1. Ask BDB's db_archive for a list of unused log files.
33
# 2. Copy the entire db/ dir to the backup area.
35
# 3. Recopy all the logfiles to the backup area. There may be more
36
# logfiles now than there were when step (1) ran.
38
# 4. Remove the logfiles listed as inactive in step (1) from the
39
# repository, though not from the backup.
41
# High-level overview of the incremental backup recipe:
43
# 1. Just copy the Berkeley logfiles to a backup area.
45
# High-level overview of the restoration recipe:
47
# 1. Copy all the datafiles and logfiles back to the repository, in
48
# the same order they were backed up.
50
# 2. Run Berkeley's "catastrophic recovery" command on the repository.
52
# That's it. Here we go...
54
# You might need to customize some of these paths.
58
# See http://www.sleepycat.com/docs/utility/db_archive.html:
59
DB_ARCHIVE=/usr/local/BerkeleyDB.4.2/bin/db_archive
60
# See http://www.sleepycat.com/docs/utility/db_recover.html:
61
DB_RECOVER=/usr/local/BerkeleyDB.4.2/bin/db_recover
63
# This is just source data to generate repository activity.
64
# Any binary file of about 64k will do, it doesn't have to be /bin/ls.
67
# You shouldn't need to customize below here.
68
SANDBOX=`pwd`/backups-test-tmp
69
FULL_BACKUPS=${SANDBOX}/full
70
INCREMENTAL_PREFIX=${SANDBOX}/incremental-logs
71
RECORDS=${SANDBOX}/records
81
${SVNADMIN} create --bdb-log-keep ${REPOS}
82
${SVN} co file://${SANDBOX}/${REPOS} wc
86
# Put in enough data for us to exercise the logfiles.
90
${SVN} -q add a1 b1 c1
91
${SVN} -q ci -m "Initial add."
93
echo "Created test data."
97
# Exercise the logfiles by moving data around a lot. Note that we
98
# avoid adds-with-history, since those cause much less Berkeley
99
# activity than plain adds.
101
# Call this from the parent of wc, that is, with $SANDBOX as CWD.
102
# Pass one argument, a number, indicating how many cycles of exercise
103
# you want. The more cycles, the more logfiles will be generated.
104
# The ratio is about two cycles per logfile.
114
while [ ${i} -le ${limit} ]; do
118
${SVN} -q rm a1 b1 c1
119
${SVN} -q add a2 b2 c2
120
${SVN} -q ci -m "Move 1s to 2s, but not as cheap copies."
125
${SVN} -q rm a2 b2 c2
126
${SVN} -q add a1 b1 c1
127
${SVN} -q ci -m "Move 2s back to 1s, same way."
129
echo "Exercising repository, pass ${i} of ${limit}."
130
i=`dc -e "${i} 1 + p"`
137
# Generate some logfile activity.
141
head=`${SVNLOOK} youngest ${REPOS}`
142
echo "Starting full backup (at r${head})..."
143
mkdir ${FULL_BACKUPS}
144
mkdir ${FULL_BACKUPS}/${PROJ}
145
mkdir ${FULL_BACKUPS}/${PROJ}/repos
146
mkdir ${FULL_BACKUPS}/${PROJ}/logs
148
${DB_ARCHIVE} > ${RECORDS}/${PROJ}-full-backup-inactive-logfiles
150
cp -a ${REPOS} ${FULL_BACKUPS}/${PROJ}/repos/
152
for logfile in `${DB_ARCHIVE} -l`; do
153
# For maximum paranoia, we want repository activity *while* we're
154
# making the full backup.
156
cp ${logfile} ${FULL_BACKUPS}/${PROJ}/logs
158
cat ${RECORDS}/${PROJ}-full-backup-inactive-logfiles | xargs rm -f
160
echo "Full backup completed (r${head} was head when started)."
162
# Do the incremental backups for a nominal week.
163
for day in 1 2 3 4 5 6; do
165
head=`${SVNLOOK} youngest ${REPOS}`
166
echo "Starting incremental backup ${day} (at r${head})..."
167
mkdir ${INCREMENTAL_PREFIX}-${day}
168
mkdir ${INCREMENTAL_PREFIX}-${day}/${PROJ}
170
${DB_ARCHIVE} > ${RECORDS}/${PROJ}-incr-backup-${day}-inactive-logfiles
171
for logfile in `${DB_ARCHIVE} -l`; do
172
# For maximum paranoia, we want repository activity *while* we're
173
# making the incremental backup. But if we did commits with each
174
# logfile copy, this script would be quite slow (Fibonacci effect).
175
# So we only exercise on the last two "days" of incrementals.
176
if [ ${day} -ge 5 ]; then
179
cp ${logfile} ${INCREMENTAL_PREFIX}-${day}/${PROJ}
181
cat ${RECORDS}/${PROJ}-incr-backup-${day}-inactive-logfiles | xargs rm -f
183
echo "Incremental backup ${day} done (r${head} was head when started)."
186
# The last revision a restoration is guaranteed to contain is whatever
187
# was head at the start of the last incremental backup.
188
last_guaranteed_rev=${head}
190
# Make the repository vanish, so we can restore it.
191
mv ${REPOS} was_${REPOS}
194
echo "Oliver Cromwell has destroyed the repository! Restoration coming
200
# After copying the full repository backup over, we remove the shared
201
# memory segments and the dav/* stuff. Recovery recreates the shmem
202
# segments, and anything in dav/* is certainly obsolete if we're doing
205
# Note that we use db_recover instead of 'svnadmin recover'. This is
206
# because we want to pass the -c ('catastrophic') flag to db_recover.
207
# As of Subversion 1.0.x, there is no '--catastrophic' flag to
208
# 'svnadmin recover', unfortunately.
209
cp -a ${FULL_BACKUPS}/${PROJ}/repos/${REPOS} .
210
cp -a ${FULL_BACKUPS}/${PROJ}/logs/* ${REPOS}/db
211
rm -rf ${REPOS}/db/__db*
212
rm -rf ${REPOS}/dav/*
216
head=`${SVNLOOK} youngest ${REPOS}`
218
echo "(Restored from full backup to r${head}...)"
219
for day in 1 2 3 4 5 6; do
221
cp ${INCREMENTAL_PREFIX}-${day}/${PROJ}/* .
224
head=`${SVNLOOK} youngest ${REPOS}`
225
echo "(Restored from incremental-${day} to r${head}...)"
228
echo "Restoration complete. All hail the King."
230
# Verify the restoration.
231
was_head=`${SVNLOOK} youngest was_${REPOS}`
232
restored_head=`${SVNLOOK} youngest ${REPOS}`
234
echo "Highest revision in original repository: ${was_head}"
235
echo "Highest revision restored: ${restored_head}"
237
echo "(It's okay if restored is less than original, even much less.)"
239
if [ ${restored_head} -lt ${last_guaranteed_rev} ]; then
241
echo "Restoration failed because r${restored_head} is too low --"
242
echo "should have restored to at least r${last_guaranteed_rev}."
246
# Looks like we restored at least to the minimum required revision.
247
# Let's do some spot checks, though.
250
echo "Comparing logs up to r${restored_head} for both repositories..."
251
${SVN} log -v -r1:${restored_head} file://`pwd`/was_${REPOS} > a
252
${SVN} log -v -r1:${restored_head} file://`pwd`/${REPOS} > b
254
echo "Done comparing logs."
256
echo "Log comparison failed -- restored repository is not right."
261
echo "Comparing r${restored_head} exported trees from both repositories..."
262
${SVN} -q export -r${restored_head} file://`pwd`/was_${REPOS} orig-export
263
${SVN} -q export -r${restored_head} file://`pwd`/${REPOS} restored-export
264
if diff -q -r orig-export restored-export; then
265
echo "Done comparing r${restored_head} exported trees."
267
echo "Recursive diff failed -- restored repository is not right."