Make parseini properly handle line continuations.
[matthijs/upstream/backupninja.git] / handlers / rsync.in
1 # -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*-
2 # vim: set filetype=sh sw=3 sts=3 expandtab autoindent:
3 #
4 # backupninja handler for incremental backups using rsync and hardlinks
5 # feedback: rhatto at riseup.net
6 #
7 #  rsync handler is free software; you can redistribute it and/or modify it
8 #  under the terms of the GNU General Public License as published by the Free
9 #  Software Foundation; either version 2 of the License, or any later version.
10 #
11 #  rsync handler is distributed in the hope that it will be useful, but WITHOUT
12 #  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14 #  more details.
15 #
16 #  You should have received a copy of the GNU General Public License along with
17 #  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18 #  Place - Suite 330, Boston, MA 02111-1307, USA
19 #
20 # Inspiration
21 # -----------
22 #
23 #  - http://www.mikerubel.org/computers/rsync_snapshots/
24 #  - rsnap handler by paulv at bikkel.org
25 #  - maildir handler from backupninja
26 #
27 # Config file options
28 # -------------------
29 #
30 #   [general]
31 #   log = rsync log file
32 #   partition = partition where the backup lives
33 #   fscheck = set to 1 if fsck should run on $partition after the backup is made
34 #   read_only = set to 1 if $partition is mounted read-only
35 #   mountpoint = backup partition mountpoint or backup main folder (either local or remote)
36 #   backupdir = folder relative do $mountpoint where the backup should be stored (local or remote)
37 #   format = specify backup storage format: short, long or mirror (i.e, no rotations)
38 #   days = for short storage format, specify the number of backup increments (min = 5)
39 #   keepdaily = for long storage format, specify the number of daily backup increments
40 #   keepweekly = for long storage format, specify the number of weekly backup increments
41 #   keepmonthly = for long storage format, specify the number of monthly backup increments
42 #   lockfile = lockfile to be kept during backup execution
43 #   nicelevel = rsync command nice level
44 #   enable_mv_timestamp_bug = set to "yes" if your system isnt handling timestamps correctly
45 #   tmp = temp folder
46 #   multiconnection = set to "yes" if you want to use multiconnection ssh support
47 #
48 #   [source]
49 #   from = local or remote
50 #   host = source hostname or ip, if remote backup
51 #   port = remote port number (remote source only)
52 #   user = remote user name (remote source only)
53 #   testconnect = when "yes", test the connection for a remote source before backup
54 #   include = include folder on backup
55 #   exclude = exclude folder on backup
56 #   ssh = ssh command line (remote source only)
57 #   protocol = ssh or rsync (remote source only)
58 #   rsync = rsync program
59 #   rsync_options = rsync command options
60 #   exclude_vserver = vserver-name (valid only if vservers = yes on backupninja.conf)
61 #   numericids = when set to 1, use numeric ids instead of user/group mappings on rsync
62 #   compress = if set to 1, compress data on rsync (remote source only)
63 #   bandwidthlimit = set a badnwidth limit in kbps (remote source only)
64 #   remote_rsync = remote rsync program (remote source only)
65 #   id_file = ssh key file (remote source only)
66 #   batch = set to "yes" to rsync use a batch file as source
67 #   batchbase = folder where the batch file is located
68 #   filelist = set yes if you want rsync to use a file list source
69 #   filelistbase = folder where the file list is placed
70 #
71 #   [dest]
72 #   dest = backup destination type (local or remote)
73 #   testconnect = when "yes", test the connection for a remote source before backup
74 #   ssh = ssh command line (remote dest only)
75 #   protocol = ssh or rsync (remote dest only)
76 #   numericids = when set to 1, use numeric ids instead of user/group mappings on rsync
77 #   compress = if set to 1, compress data on rsync (remote source only)
78 #   host = destination host name (remote destination only)
79 #   port = remote port number (remote destination only)
80 #   user = remote user name (remote destination only)
81 #   id_file = ssh key file (remote destination only)
82 #   bandwidthlimit = set a badnwidth limit in kbps (remote destination only)
83 #   remote_rsync = remote rsync program (remote dest only)
84 #   batch = set to "yes" to rsync write a batch file from the changes
85 #   batchbase = folder where the batch file should be written
86 #   fakesuper = set to yes so rsync use the --fake-super flag (remote destination only)
87 #
88 #   [services]
89 #   initscripts = absolute path where scripts are located
90 #   service = script name to be stoped at the begining of the backup and started at its end
91 #
92 # You can also specify some system comands if you don't want the default system values:
93 #
94 #   [system]
95 #   rm = rm command
96 #   cp = cp command
97 #   touch = touch command
98 #   mv = mv command
99 #   fsck = fsck command
100 #
101 # You dont need to manually specify vservers using "include = /vservers".
102 # They are automatically backuped if vserver is set to "yes" on you backupninja.conf.
103 #
104 # Changelog
105 # ---------
106
107 # 20090329 - rhatto at riseup.net
108 #
109 #   - Added support for: 
110 #     - Remote destinations
111 #     - Long rotation format similar to maildir handler
112 #     - Batch files through --read-batch and --write-batch
113 #     - Custom file list using --files-from
114 #     - SSH persistent connection using ControlMaster
115 #     - The rsync:// protocol
116 #   - Metadata folder for each backup folder
117 #   - General refactoring
118 #   - Code cleanup
119 #
120
121 # function definitions
122
123 function eval_config {
124   
125   # system section
126   
127   setsection system
128   getconf rm rm
129   getconf cp cp
130   getconf touch touch
131   getconf mv mv
132   getconf fsck fsck
133   
134   # general section
135   
136   setsection general
137   getconf log /var/log/backup/rsync.log
138   getconf partition
139   getconf fscheck
140   getconf read_only
141   getconf mountpoint
142   getconf backupdir
143   getconf format short
144   getconf days
145   getconf keepdaily 5
146   getconf keepweekly 3
147   getconf keepmonthly 1
148   getconf lockfile
149   getconf nicelevel 0
150   getconf enable_mv_timestamp_bug no
151   getconf tmp /tmp
152   getconf multiconnection no
153   
154   # source section
155   
156   setsection source
157   getconf from local
158   getconf rsync $RSYNC
159   getconf rsync_options "-av --delete --recursive"
160   
161   if [ "$from" == "remote" ]; then
162     getconf testconnect no
163     getconf protocol ssh
164     getconf ssh ssh
165     getconf host
166
167     if [ "$protocol" == "ssh" ]; then
168       # sshd default listen port
169       getconf port 22
170     else
171       # rsyncd default listen port
172       getconf port 873
173     fi
174
175     getconf user
176     getconf bandwidthlimit
177     getconf remote_rsync rsync
178     getconf id_file /root/.ssh/id_dsa
179   fi
180   
181   getconf batch no
182
183   if [ "$batch" == "yes" ]; then
184     getconf batchbase
185     if [ ! -z "$batchbase" ]; then
186       batch="read"
187     fi
188   fi
189
190   getconf filelist no
191   getconf filelistbase
192   getconf include
193   getconf exclude
194   getconf exclude_vserver
195   getconf numericids 0
196   getconf compress 0
197   
198   # dest section
199   
200   setsection dest
201   getconf dest local
202   getconf fakesuper no
203   
204   if [ "$dest" == "remote" ]; then
205     getconf testconnect no
206     getconf protocol ssh
207     getconf ssh ssh
208     getconf host
209
210     if [ "$protocol" == "ssh" ]; then
211       # sshd default listen port
212       getconf port 22
213     else
214       # rsyncd default listen port
215       getconf port 873
216     fi
217
218     getconf user
219     getconf bandwidthlimit
220     getconf remote_rsync rsync
221     getconf id_file /root/.ssh/id_dsa
222   fi
223   
224   getconf batch no
225
226   if [ "$batch" != "yes" ]; then
227     getconf batch no
228     if [ "$batch" == "yes" ]; then
229       getconf batchbase
230       if [ ! -z "$batchbase" ]; then
231         batch="write"
232       fi
233     fi
234   fi
235
236   getconf numericids 0
237   getconf compress 0
238   
239   # services section
240   
241   setsection services
242   getconf initscripts /etc/init.d
243   getconf service
244
245   # config check
246
247   if [ "$dest" != "local" ] && [ "$from" == "remote" ]; then
248     fatal "When source is remote, destination should be local."
249     exit 1
250   fi
251
252   if [ "$from" != "local" ] && [ "$from" != "remote" ]; then
253     fatal "Invalid source $from"
254     exit 1
255   fi
256
257   backupdir="$mountpoint/$backupdir"
258
259   if [ "$dest" == "local" ] && [ ! -d "$backupdir" ]; then 
260     error "Backupdir $backupdir does not exist"
261     exit 1
262   fi
263
264   if [ ! -z "$log" ]; then
265     mkdir -p `dirname $log`
266   fi
267
268   if [ "$format" == "short" ]; then
269     if [ -z "$days" ]; then
270       keep="4"
271     else
272       keep="`echo $days - 1 | bc -l`"
273     fi
274   fi
275
276   if [ ! -z "$nicelevel" ]; then 
277     nice="nice -n $nicelevel"
278   else 
279     nice=""
280   fi
281
282   ssh_cmd="ssh -T -o PasswordAuthentication=no $host -p $port -l $user -i $id_file"
283
284   if [ "$from" == "remote" ] || [ "$dest" == "remote" ]; then
285     if [ "$testconnect" == "yes" ] && [ "$protocol" == "ssh" ]; then
286       test_connect $host $port $user $id_file
287     fi
288   fi
289
290   if [ "$multiconnection" == "yes" ]; then
291     ssh_cmd="$ssh_cmd -S $tmp/%r@%h:%p"
292   fi
293
294   if [ $enable_mv_timestamp_bug == "yes" ]; then
295     mv=move_files
296   fi
297
298   for path in $exclude; do
299     excludes="$excludes --exclude=$path"
300   done
301
302 }
303
304 function rotate_short {
305
306   local dest
307   local folder="$1"
308   local keep="$2"
309   local metadata="`dirname $folder`/metadata"
310
311   if [[ "$keep" -lt 4 ]]; then
312     error "Rotate: minimum of 4 rotations"
313     exit 1
314   fi
315
316   if [ -d $folder.$keep ]; then
317     $nice $mv /$folder.$keep /$folder.tmp
318   fi
319
320   for ((n=`echo "$keep - 1" | bc`; n >= 0; n--)); do
321     if [ -d $folder.$n ]; then
322       dest=`echo "$n + 1" | bc`
323       $nice $mv /$folder.$n /$folder.$dest
324       $touch /$folder.$dest
325       mkdir -p $metadata/`basename $folder`.$dest
326       date +%c%n%s > $metadata/`basename $folder`.$dest/rotated
327     fi
328   done
329
330   if [ -d $folder.tmp ]; then
331     $nice $mv /$folder.tmp /$folder.0
332   fi
333
334   if [ -d $folder.1 ]; then
335     $nice $cp -alf /$folder.1/. /$folder.0
336   fi
337
338 }
339
340 function rotate_short_remote {
341
342   local folder="$1"
343   local metadata="`dirname $folder`/metadata"
344   local keep="$2"
345
346   if [[ "$2" -lt 4 ]]; then
347     error "Rotate: minimum of 4 rotations"
348     exit 1
349   fi
350
351 (
352   $ssh_cmd <<EOF
353   ##### BEGIN REMOTE SCRIPT #####
354
355   if [ -d $folder.$keep ]; then
356     $nice mv /$folder.$keep /$folder.tmp
357   fi
358
359   for ((n=$(($keep - 1)); n >= 0; n--)); do
360     if [ -d $folder.\$n ]; then
361       dest=\$((\$n + 1))
362       $nice mv /$folder.\$n /$folder.\$dest
363       touch /$folder.\$dest
364       mkdir -p $metadata/`basename $folder`.\$dest
365       date +%c%n%s > $metadata/`basename $folder`.\$dest/rotated
366     fi
367   done
368
369   if [ -d $folder.tmp ]; then
370     $nice mv /$folder.tmp /$folder.0
371   fi
372
373   if [ -d $folder.1 ]; then
374     $nice $cp -alf /$folder.1/. /$folder.0
375   fi
376   ##### END REMOTE SCRIPT #######
377 EOF
378 ) | (while read a; do passthru $a; done)
379
380 }
381
382 function rotate_long {
383
384   backuproot="$1"
385   seconds_daily=86400
386   seconds_weekly=604800
387   seconds_monthly=2628000
388   keepdaily=$keepdaily
389   keepweekly=$keepweekly
390   keepmonthly=$keepmonthly
391   now=`date +%s`
392
393   local metadata
394
395   if [ ! -d "$backuproot" ]; then
396     echo "Debug: skipping rotate of $backuproot as it doesn't exist."
397     exit
398   fi
399
400   for rottype in daily weekly monthly; do
401     seconds=$((seconds_${rottype}))
402
403     dir="$backuproot/$rottype"
404     metadata="$backuproot/metadata/$rottype.1"
405     mkdir -p $metadata
406     if [ ! -d $dir.1 ]; then
407       echo "Debug: $dir.1 does not exist, skipping."
408       continue 1
409     elif [ ! -f $metadata/created ] && [ ! -f $metadata/rotated ]; then
410       echo "Warning: metadata does not exist for $dir.1. This backup may be only partially completed. Skipping rotation."
411       continue 1
412     fi
413     
414     # Rotate the current list of backups, if we can.
415     oldest=`find $backuproot -maxdepth 1 -type d -name $rottype'.*' | @SED@ 's/^.*\.//' | sort -n | tail -1`
416     [ "$oldest" == "" ] && oldest=0
417     for (( i=$oldest; i > 0; i-- )); do
418       if [ -d $dir.$i ]; then
419         if [ -f $metadata/created ]; then
420           created=`tail -1 $metadata/created`
421         elif [ -f $metadata/rotated ]; then
422           created=`tail -1 $metadata/rotated`
423         else
424           created=0
425         fi
426         cutoff_time=$(( now - (seconds*(i-1)) ))
427         if [ ! $created -gt $cutoff_time ]; then
428           next=$(( i + 1 ))
429           if [ ! -d $dir.$next ]; then
430             echo "Debug: $rottype.$i --> $rottype.$next"
431             $nice mv $dir.$i $dir.$next
432             mkdir -p $backuproot/metadata/$rottype.$next
433             date +%c%n%s > $backuproot/metadata/$rottype.$next/rotated
434           else
435             echo "Debug: skipping rotation of $dir.$i because $dir.$next already exists."
436           fi
437         else
438           echo "Debug: skipping rotation of $dir.$i because it was created" $(( (now-created)/86400)) "days ago ("$(( (now-cutoff_time)/86400))" needed)."
439         fi
440       fi
441     done
442   done
443
444   max=$((keepdaily+1))
445   if [ $keepweekly -gt 0 -a -d $backuproot/daily.$max -a ! -d $backuproot/weekly.1 ]; then
446     echo "Debug: daily.$max --> weekly.1"
447     $nice mv $backuproot/daily.$max $backuproot/weekly.1
448     mkdir -p $backuproot/metadata/weekly.1
449     date +%c%n%s > $backuproot/metadata/weekly.1/rotated
450   fi
451
452   max=$((keepweekly+1))
453   if [ $keepmonthly -gt 0 -a -d $backuproot/weekly.$max -a ! -d $backuproot/monthly.1 ]; then
454     echo "Debug: weekly.$max --> monthly.1"
455     $nice mv $backuproot/weekly.$max $backuproot/monthly.1
456     mkdir -p $backuproot/metadata/monthly.1
457     date +%c%n%s > $backuproot/metadata/monthly.1/rotated
458   fi
459
460   for rottype in daily weekly monthly; do
461     max=$((keep${rottype}+1))
462     dir="$backuproot/$rottype"
463     oldest=`find $backuproot -maxdepth 1 -type d -name $rottype'.*' | @SED@ 's/^.*\.//' | sort -n | tail -1`
464     [ "$oldest" == "" ] && oldest=0 
465     # if we've rotated the last backup off the stack, remove it.
466     for (( i=$oldest; i >= $max; i-- )); do
467       if [ -d $dir.$i ]; then
468         if [ -d $backuproot/rotate.tmp ]; then
469           echo "Debug: removing rotate.tmp"
470           $nice rm -rf $backuproot/rotate.tmp
471         fi
472         echo "Debug: moving $rottype.$i to rotate.tmp"
473         $nice mv $dir.$i $backuproot/rotate.tmp
474       fi
475     done
476   done
477
478 }
479
480 function rotate_long_remote {
481
482   local backuproot="$1"
483
484 (
485   $ssh_cmd <<EOF
486   ##### BEGIN REMOTE SCRIPT #####
487
488   seconds_daily=86400
489   seconds_weekly=604800
490   seconds_monthly=2628000
491   keepdaily=$keepdaily
492   keepweekly=$keepweekly
493   keepmonthly=$keepmonthly
494   now=\`date +%s\`
495
496   if [ ! -d "$backuproot" ]; then
497     echo "Debug: skipping rotate of $backuproot as it doesn't exist."
498     exit
499   fi
500
501   for rottype in daily weekly monthly; do
502     seconds=\$((seconds_\${rottype}))
503
504     dir="$backuproot/\$rottype"
505     metadata="$backuproot/metadata/\$rottype.1"
506     mkdir -p \$metadata
507     if [ ! -d \$dir.1 ]; then
508       echo "Debug: \$dir.1 does not exist, skipping."
509       continue 1
510     elif [ ! -f \$metadata/created ] && [ ! -f \$metadata/rotated ]; then
511       echo "Warning: metadata does not exist for \$dir.1. This backup may be only partially completed. Skipping rotation."
512       continue 1
513     fi
514     
515     # Rotate the current list of backups, if we can.
516     oldest=\`find $backuproot -maxdepth 1 -type d -name \$rottype'.*' | @SED@ 's/^.*\.//' | sort -n | tail -1\`
517     [ "\$oldest" == "" ] && oldest=0
518     for (( i=\$oldest; i > 0; i-- )); do
519       if [ -d \$dir.\$i ]; then
520         if [ -f \$metadata/created ]; then
521           created=\`tail -1 \$metadata/created\`
522         elif [ -f \$metadata/rotated ]; then
523           created=\`tail -1 \$metadata/rotated\`
524         else
525           created=0
526         fi
527         cutoff_time=\$(( now - (seconds*(i-1)) ))
528         if [ ! \$created -gt \$cutoff_time ]; then
529           next=\$(( i + 1 ))
530           if [ ! -d \$dir.\$next ]; then
531             echo "Debug: \$rottype.\$i --> \$rottype.\$next"
532             $nice mv \$dir.\$i \$dir.\$next
533             mkdir -p $backuproot/metadata/\$rottype.\$next
534             date +%c%n%s > $backuproot/metadata/\$rottype.\$next/rotated
535           else
536             echo "Debug: skipping rotation of \$dir.\$i because \$dir.\$next already exists."
537           fi
538         else
539           echo "Debug: skipping rotation of \$dir.\$i because it was created" \$(( (now-created)/86400)) "days ago ("\$(( (now-cutoff_time)/86400))" needed)."
540         fi
541       fi
542     done
543   done
544
545   max=\$((keepdaily+1))
546   if [ \$keepweekly -gt 0 -a -d $backuproot/daily.\$max -a ! -d \$backuproot/weekly.1 ]; then
547     echo "Debug: daily.\$max --> weekly.1"
548     $nice mv $backuproot/daily.\$max $backuproot/weekly.1
549     mkdir -p $backuproot/metadata/weekly.1
550     date +%c%n%s > $backuproot/metadata/weekly.1/rotated
551   fi
552
553   max=\$((keepweekly+1))
554   if [ \$keepmonthly -gt 0 -a -d $backuproot/weekly.\$max -a ! -d $backuproot/monthly.1 ]; then
555     echo "Debug: weekly.\$max --> monthly.1"
556     $nice mv $backuproot/weekly.\$max $backuproot/monthly.1
557     mkdir -p $backuproot/metadata/monthly.1
558     date +%c%n%s > $backuproot/metadata/monthly.1/rotated
559   fi
560
561   for rottype in daily weekly monthly; do
562     max=\$((keep\${rottype}+1))
563     dir="$backuproot/\$rottype"
564     oldest=\`find $backuproot -maxdepth 1 -type d -name \$rottype'.*' | @SED@ 's/^.*\.//' | sort -n | tail -1\`
565     [ "\$oldest" == "" ] && oldest=0 
566     # if we've rotated the last backup off the stack, remove it.
567     for (( i=\$oldest; i >= \$max; i-- )); do
568       if [ -d \$dir.\$i ]; then
569         if [ -d $backuproot/rotate.tmp ]; then
570           echo "Debug: removing rotate.tmp"
571           $nice rm -rf $backuproot/rotate.tmp
572         fi
573         echo "Debug: moving \$rottype.\$i to rotate.tmp"
574         $nice mv \$dir.\$i $backuproot/rotate.tmp
575       fi
576     done
577   done
578   ##### END REMOTE SCRIPT #######
579 EOF
580 ) | (while read a; do passthru $a; done)
581
582 }
583
584 function setup_long_dirs {
585
586   local destdir=$1
587   local backuptype=$2
588   local dir="$destdir/$backuptype"
589   local tmpdir="$destdir/rotate.tmp"
590   local metadata="$destdir/metadata/$backuptype.1"
591
592   if [ ! -d $destdir ]; then
593     echo "Creating destination directory $destdir..."
594     mkdir -p $destdir
595   fi
596
597   if [ -d $dir.1 ]; then
598     if [ -f $metadata/created ]; then
599       echo "Warning: $dir.1 already exists. Overwriting contents."
600     else
601       echo "Warning: we seem to be resuming a partially written $dir.1"
602     fi
603   else
604     if [ -d $tmpdir ]; then
605       mv $tmpdir $dir.1
606       if [ $? == 1 ]; then
607         echo "Fatal: could mv $destdir/rotate.tmp $dir.1 on host $host"
608         exit 1
609       fi
610     else
611       mkdir --parents $dir.1
612       if [ $? == 1 ]; then
613         echo "Fatal: could not create directory $dir.1 on host $host"
614         exit 1
615       fi
616     fi
617     if [ -d $dir.2 ]; then
618       echo "Debug: update links $backuptype.2 --> $backuptype.1"
619       cp -alf $dir.2/. $dir.1
620       #if [ $? == 1 ]; then
621       #  echo "Fatal: could not create hard links to $dir.1 on host $host"
622       #  exit 1
623       #fi
624     fi
625   fi
626   [ -f $metadata/created ] && rm $metadata/created
627   [ -f $metadata/rotated ] && rm $metadata/rotated
628
629 }
630
631 function setup_long_dirs_remote {
632
633   local destdir=$1
634   local backuptype=$2
635   local dir="$destdir/$backuptype"
636   local tmpdir="$destdir/rotate.tmp"
637   local metadata="$destdir/metadata/$backuptype.1"
638
639 (
640   $ssh_cmd <<EOF
641   ##### BEGIN REMOTE SCRIPT #####
642   if [ ! -d $destdir ]; then
643     echo "Creating destination directory $destdir on $host..."
644     mkdir -p $destdir
645   fi
646
647   if [ -d $dir.1 ]; then
648     if [ -f $metadata/created ]; then
649       echo "Warning: $dir.1 already exists. Overwriting contents."
650     else
651       echo "Warning: we seem to be resuming a partially written $dir.1"
652     fi
653   else
654     if [ -d $tmpdir ]; then
655       mv $tmpdir $dir.1
656       if [ \$? == 1 ]; then
657         echo "Fatal: could mv $destdir/rotate.tmp $dir.1 on host $host"
658         exit 1
659       fi
660     else
661       mkdir --parents $dir.1
662       if [ \$? == 1 ]; then
663         echo "Fatal: could not create directory $dir.1 on host $host"
664         exit 1
665       fi
666     fi
667     if [ -d $dir.2 ]; then
668       echo "Debug: update links $backuptype.2 --> $backuptype.1"
669       cp -alf $dir.2/. $dir.1
670       #if [ \$? == 1 ]; then
671       #  echo "Fatal: could not create hard links to $dir.1 on host $host"
672       #  exit 1
673       #fi
674     fi
675   fi
676   [ -f $metadata/created ] && rm $metadata/created
677   [ -f $metadata/rotated ] && rm $metadata/rotated
678   ##### END REMOTE SCRIPT #######
679 EOF
680 ) | (while read a; do passthru $a; done)
681
682 }
683
684 function move_files {
685
686   ref=$tmp/makesnapshot-mymv-$$;
687   $touch -r $1 $ref;
688   $mv $1 $2;
689   $touch -r $ref $2;
690   $rm $ref;
691
692 }
693
694 function prepare_storage {
695
696   section="`basename $SECTION`"
697
698   if [ "$format" == "short" ]; then
699
700     suffix="$section.0"
701     info "Rotating $backupdir/$SECTION..."
702     echo "Rotating $backupdir/$SECTION..." >> $log
703
704     if [ "$dest" == "remote" ]; then
705       rotate_short_remote $backupdir/$SECTION/$section $keep
706     else
707       rotate_short $backupdir/$SECTION/$section $keep
708       if [ ! -d "$backupdir/$SECTION/$section.0" ]; then
709         mkdir -p $backupdir/$SECTION/$section.0
710       fi
711     fi
712
713   elif [ "$format" == "long" ]; then
714
715     if [ $keepdaily -gt 0 ]; then
716       btype=daily
717     elif [ $keepweekly -gt 0 ]; then
718       btype=weekly
719     elif [ $keepmonthly -gt 0 ]; then
720       btype=monthly
721     else
722       fatal "keeping no backups";
723       exit 1
724     fi
725
726     suffix="$btype.1"
727     info "Rotating $backupdir/$SECTION/..."
728     echo "Rotating $backupdir/$SECTION/..." >> $log
729
730     if [ "$dest" == "remote" ]; then
731       rotate_long_remote $backupdir/$SECTION
732       setup_long_dirs_remote $backupdir/$SECTION $btype
733     else
734       rotate_long $backupdir/$SECTION
735       setup_long_dirs $backupdir/$SECTION $btype
736     fi
737
738   elif [ "$format" == "mirror" ]; then
739     suffix=""
740   else
741     fatal "Invalid backup format $format"
742     exit 1
743   fi
744
745 }
746
747 function set_orig {
748
749   if [ "$from" == "local" ]; then
750     orig="/$SECTION/"
751   elif [ "$from" == "remote" ]; then
752     if [ "$protocol" == "rsync" ]; then
753       orig="rsync://$user@$host:$port/$SECTION/"
754     else
755       orig="$user@$host:/$SECTION/"
756     fi
757   fi
758
759 }
760
761 function set_dest { 
762
763   if [ "$dest" == "local" ]; then
764     dest_path="$backupdir/$SECTION/$suffix/"
765   else
766     if [ "$protocol" == "rsync" ]; then
767       dest_path="rsync://$user@$host:$port/$backupdir/$SECTION/$suffix/"
768     else
769       dest_path="$user@$host:$backupdir/$SECTION/$suffix/"
770     fi
771   fi
772
773 }
774
775 function set_batch_mode {
776
777   local batch_file="$batchbase/$SECTION/$suffix"
778
779   if [ "$batch" == "read" ]; then
780     if [ -e "$batch_file" ]; then
781       orig=""
782       excludes=""
783       batch_option="--read-batch=$batch_file"
784     else
785       fatal "Batch file not found: $batch_file"
786       exit 1
787     fi
788   elif [ "$batch" == "write" ]; then
789     mkdir -p `dirname $batch_file`
790     batch_option="--write-batch=$batch_file"
791   fi
792
793 }
794
795 function update_metadata {
796
797   local metadata
798   local folder
799
800   if [ "$dest" == "local" ]; then
801     metadata="`dirname $dest_path`/metadata/`basename $dest_path`"
802     mkdir -p $metadata
803     date +%c%n%s > $metadata/created
804     $touch $backupdir/$SECTION/$suffix
805   else
806     folder="`echo $dest_path | cut -d : -f 2`"
807     metadata="`dirname $folder`/metadata/`basename $folder`"
808
809 (
810   $ssh_cmd <<EOF
811     ##### BEGIN REMOTE SCRIPT #####
812     mkdir -p $metadata
813     date +%c%n%s > $metadata/created
814     ##### END REMOTE SCRIPT #######
815 EOF
816 ) | (while read a; do passthru $a; done)
817
818   fi
819
820 }
821
822 function test_connect {
823
824   local host="$1"
825   local port="$2"
826   local user="$3"
827   local id_file="$4"
828
829   if [ -z "$host" ] || [ -z "$user" ]; then
830     fatal "Remote host or user not set"
831     exit 1
832   fi
833
834   debug "$ssh_cmd 'echo -n 1'"
835   result=`$ssh_cmd 'echo -n 1'`
836
837   if [ "$result" != "1" ]; then
838     fatal "Can't connect to $host as $user."
839     exit 1
840   else
841     debug "Connected to $host successfully"
842   fi
843
844 }
845
846 function set_lockfile {
847
848   if [ ! -z "$lockfile" ]; then
849     $touch $lockfile || warning "Could not create lockfile $lockfile"
850   fi
851
852 }
853
854 function unset_lockfile {
855
856   if [ ! -z "$lockfile" ]; then
857     $rm $lockfile || warning "Could not remove lockfile $lockfile"
858   fi
859
860 }
861
862 function set_filelist {
863
864   filelist_flag=""
865
866   if [ "$filelist" == "yes" ]; then
867     if [ ! -z "$filelistbase" ]; then
868       if [ -e "$filelistbase/$SECTION/$suffix" ]; then
869         filelist_flag="--files-from=$filelistbase/$SECTION/$suffix"
870       else
871         warning "File list $filelistbase/$SECTION/$suffix not found."
872       fi
873     else
874       warning "No filelistbase set."
875     fi
876   fi
877
878 }
879
880 function set_rsync_options {
881
882   if [ ! -z "$numericids" ]; then
883     rsync_options="$rsync_options --numeric-ids"
884   fi
885
886   if [ "$from" == "local" ] || [ "$dest" == "local" ]; then
887     # rsync options for local sources or destinations
888     rsync_options="$rsync_options"
889   fi
890
891   if [ "$from" == "remote" ] || [ "$dest" == "remote" ]; then
892
893     # rsync options for remote sources or destinations
894
895     if [ "$compress" == "1" ]; then
896       rsync_options="$rsync_options --compress"
897     fi
898
899     if [ ! -z "$bandwidthlimit" ]; then
900       rsync_options="$rsync_options --bwlimit=$bandwidthlimit"
901     fi
902     
903     if [ "$fakesuper" == "yes" ]; then
904       remote_rsync="$remote_rsync --fake-super"
905     fi
906
907     rsync_options=($rsync_options --rsync-path="$remote_rsync")
908
909     if [ "$protocol" == "ssh" ]; then
910       if [ ! -e "$id_file" ]; then
911         fatal "SSH Identity file $id_file not found"
912         exit 1
913       else
914         debug RSYNC_RSH=\"$ssh_cmd\"
915         echo RSYNC_RSH=\"$ssh_cmd\" >> $log
916         RSYNC_RSH="$ssh_cmd"
917       fi
918     fi
919
920   fi
921
922   include_vservers
923
924 }
925
926 function stop_services {
927
928   if [ ! -z "$service" ]; then
929     for daemon in $service; do
930       info "Stopping service $daemon..."
931       $initscripts/$daemon stop
932     done
933   fi
934
935 }
936
937 function start_services {
938
939   # restart services
940
941   if [ ! -z "$service" ]; then
942     for daemon in $service; do
943       info "Starting service $daemon..."
944       $initscripts/$daemon start
945     done
946   fi
947
948 }
949
950 function mount_rw {
951
952   # mount backup destination folder as read-write
953
954   if [ "$dest" == "local" ]; then
955     if [ "$read_only" == "1" ] || [ "$read_only" == "yes" ]; then
956       if [ -d "$mountpoint" ]; then
957         mount -o remount,rw $mountpoint
958         if (($?)); then
959           error "Could not mount $mountpoint"
960           exit 1
961         fi
962       fi
963     fi
964   fi
965
966 }
967
968 function mount_ro {
969
970   # remount backup destination as read-only
971
972   if [ "$dest" == "local" ]; then
973     if [ "$read_only" == "1" ] || [ "$read_only" == "yes" ]; then
974       mount -o remount,ro $mountpoint
975     fi
976   fi
977
978 }
979
980 function run_fsck {
981
982   # check partition for errors
983
984   if [ "$dest" == "local" ]; then
985     if [ "$fscheck" == "1" ] || [ "$fscheck" == "yes" ]; then
986       umount $mountpoint
987       if (($?)); then
988         warning "Could not umount $mountpoint to run fsck"
989       else
990         $nice $fsck -v -y $partition >> $log
991         mount $mountpoint
992       fi
993     fi
994   fi
995
996 }
997
998 function include_vservers {
999
1000   # add vservers to included folders
1001
1002   if [ "$vservers_are_available" == "yes" ]; then
1003
1004     # sane permission on backup
1005     mkdir -p $backupdir/$VROOTDIR
1006     chmod 000 $backupdir/$VROOTDIR
1007
1008     for candidate in $found_vservers; do
1009       candidate="`basename $candidate`"
1010       found_excluded_vserver="0"
1011       for excluded_vserver in $exclude_vserver; do
1012         if [ "$excluded_vserver" == "$candidate" ]; then
1013           found_excluded_vserver="1"
1014           break
1015         fi
1016       done
1017       if [ "$found_excluded_vserver" == "0" ]; then
1018         include="$include $VROOTDIR/$candidate"
1019       fi
1020     done
1021   fi
1022
1023 }
1024
1025 function start_mux {
1026
1027   if [ "$multiconnection" == "yes" ]; then
1028     debug "Starting master ssh connection"
1029     $ssh_cmd -M sleep 1d &
1030     sleep 1
1031   fi
1032
1033 }
1034
1035 function end_mux {
1036
1037   if [ "$multiconnection" == "yes" ]; then
1038     debug "Stopping master ssh connection"
1039     $ssh_cmd pkill sleep
1040   fi
1041
1042 }
1043
1044 # the backup procedure
1045
1046 eval_config
1047 set_lockfile
1048 set_rsync_options
1049 start_mux
1050 stop_services
1051 mount_rw
1052
1053 echo "Starting backup at `date`" >> $log
1054
1055 for SECTION in $include; do
1056
1057   prepare_storage
1058   set_orig
1059   set_batch_mode
1060   set_filelist
1061   set_dest
1062
1063   info "Syncing $SECTION on $dest_path..."
1064   debug $nice $rsync "${rsync_options[@]}" $filelist_flag $excludes $batch_option $orig $dest_path
1065   $nice $rsync "${rsync_options[@]}" $filelist_flag $excludes $batch_option $orig $dest_path >> $log
1066
1067   if [ "$?" != "0" ]; then
1068     warning "Rsync error when trying to transfer $SECTION"
1069   fi
1070
1071   update_metadata
1072
1073 done
1074
1075 mount_ro
1076 run_fsck
1077 start_services
1078 unset_lockfile
1079 end_mux
1080
1081 echo "Finnishing backup at `date`" >> $log
1082