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