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."