/[psinib]/psinib.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /psinib.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.22 - (show annotations)
Mon Mar 1 19:38:16 2004 UTC (20 years, 1 month ago) by dpavlin
Branch: MAIN
CVS Tags: HEAD
Changes since 1.21: +6 -3 lines
File MIME type: text/plain
if home directory isn't writable (for e.g. if you are running psinib under
backup user with Debian) it will fallback to /tmp directory (which is
somewhat bad -- why whould you want to run under user without writable home
dir?)

1 #!/usr/bin/perl -w
2 #
3 # psinib - Perl Snapshot Is Not Incremental Backup
4 #
5 # written by Dobrica Pavlinusic <dpavlin@rot13.org> 2003-01-03
6 # released under GPL v2 or later.
7 #
8 # Backup SMB directories using file produced by LinNeighbourhood (or some
9 # other program [vi :-)] which produces file in format:
10 #
11 # smbmount service mountpoint options
12 #
13 #
14 # usage:
15 # $ psinib.pl mountscript
16
17 use strict 'vars';
18 use Data::Dumper;
19 use Net::Ping;
20 use POSIX qw(strftime);
21 use List::Compare;
22 use Filesys::SmbClient;
23 #use Taint;
24 use Fcntl qw(LOCK_EX LOCK_NB);
25 use Digest::MD5;
26 use File::Basename;
27 use Getopt::Long;
28
29 # configuration
30 my $LOG_TIME_FMT = '%Y-%m-%d %H:%M:%S'; # strftime format for logfile
31 my $DIR_TIME_FMT = '%Y%m%d'; # strftime format for backup dir
32
33 # define timeout for ping
34 my $PING_TIMEOUT = 5;
35
36 my $LOG = '/var/log/backup.log'; # add path here...
37 #$LOG = '/tmp/backup.log';
38
39 # store backups in which directory
40 my $BACKUP_DEST = '/backup/isis_backup';
41 #my $BACKUP_DEST = '/tmp/backup/';
42
43 # files to ignore in backup
44 my @ignore = ('.md5sum', '.backupignore', 'backupignore.txt');
45
46 # open log
47 open(L, ">> $LOG") || die "can't open log $LOG: $!";
48 select((select(L), $|=1)[0]); # flush output
49
50 # make a lock on logfile
51
52 my $c = 0;
53 {
54 flock L, LOCK_EX | LOCK_NB and last;
55 sleep 1;
56 redo if ++$c < 10;
57 # no response for 10 sec, bail out
58 xlog("ABORT","can't take lock on $LOG -- another $0 running?");
59 exit 1;
60 }
61
62 # taint path: nmblookup should be there!
63 $ENV{'PATH'} = "/usr/bin:/bin";
64
65 my $use_ping = 1; # default: use syn tcp ping to verify that host is up
66 my $verbose = 1; # default verbosity level
67 my $quiet = 0;
68 my $email;
69
70 my $result = GetOptions(
71 "ping!" => \$use_ping, "backupdest!" => \$BACKUP_DEST,
72 "verbose+" => \$verbose, "quiet+" => \$quiet,
73 "email=s" => \$email,
74 );
75
76 $verbose -= $quiet;
77
78 my $mounts = shift @ARGV ||
79 'mountscript';
80 # die "usage: $0 mountscript";
81
82 my $basedir = $0;
83 $basedir =~ s,/?[^/]+$,,g;
84
85 # default subject for e-mail messages
86 my @subjects = ('Backup needs your attention!');
87 my $sub_nr = 0;
88 my $email_body;
89
90 my $home_dir=$ENV{'HOME'};
91 $home_dir = '/tmp' if (! -w $home_dir);
92
93 if ($email) {
94 # It will use (and require) Tie::File only if --email=foo@bar.com
95 # arguement is used!
96 use Tie::File;
97 tie @subjects, 'Tie::File', "$basedir/subjects.txt" || xlog("CONFIG","Can't find $basedir/subjects.txt... using default (only one)");
98 chdir; # this will change directory to HOME
99 if (open(SN,"$home_dir/.psinib.subject")) {
100 $sub_nr = <SN>;
101 chomp($sub_nr);
102 close(SN);
103 }
104 $sub_nr++;
105 # skip comments in subjects.txt
106 while($subjects[$sub_nr] && $subjects[$sub_nr] =~ m/^#/) {
107 $sub_nr++;
108 }
109 $sub_nr = 0 if (! $subjects[$sub_nr]);
110
111 if (open(SN,"> $home_dir/.psinib.subject")) {
112 print SN "$sub_nr\n";
113 close (SN);
114 } else {
115 xlog("CONFIG","Can't open $home_dir/.psinib.subject -- I can't cycle subjects...");
116 };
117 }
118
119 my @in_backup; # shares which are backeduped this run
120
121 # init Net::Ping object
122 my $ping;
123 if ($use_ping) {
124 $ping = new Net::Ping->new("syn", 2);
125 # ping will try tcp connect to netbios-ssn (139)
126 $ping->{port_num} = getservbyname("netbios-ssn", "tcp");
127 }
128
129 # do syn ping to cifs port
130 sub host_up {
131 my $ping = shift || return;
132 my $host_ip = shift || xlog("host_up didn't get IP");
133 my $timeout = shift;
134 return 1 if (! $use_ping);
135
136 $ping->ping($host_ip,$timeout);
137 my $return = 0;
138
139 while (my ($host,$rtt,$ip) = $ping->ack) {
140 xlog("","HOST: $host [$ip] ACKed in $rtt seconds");
141 $return = 1 if ($ip eq $host_ip);
142 }
143 return $return;
144 }
145
146 my $backup_ok = 0;
147
148 my $smb;
149 my %smb_atime;
150 my %smb_mtime;
151 my %file_md5;
152
153 open(M, $mounts) || die "can't open $mounts: $!";
154 while(<M>) {
155 chomp;
156 next if !/^\s*smbmount\s/;
157 my (undef,$share,undef,$opt) = split(/\s+/,$_,4);
158
159 my ($user,$passwd,$workgroup,$ip);
160
161 foreach (split(/,/,$opt)) {
162 my ($n,$v) = split(/=/,$_,2);
163 if ($n =~ m/username/i) {
164 if ($v =~ m#^(.+)/(.+)%(.+)$#) {
165 ($user,$passwd,$workgroup) = ($1,$2,$3);
166 } elsif ($v =~ m#^(.+)/(.+)$#) {
167 ($user,$workgroup) = ($1,$2);
168 } elsif ($v =~ m#^(.+)%(.+)$#) {
169 ($user,$passwd) = ($1,$2);
170 } else {
171 $user = $v;
172 }
173 } elsif ($n =~ m#workgroup#i) {
174 $workgroup = $v;
175 } elsif ($n =~ m#ip#i) {
176 $ip = $v;
177 }
178 }
179
180 push @in_backup,$share;
181
182
183 my ($host,$dir,$date_dir) = share2host_dir($share);
184 my $bl = "$BACKUP_DEST/$host/$dir/latest"; # latest backup
185 my $bc = "$BACKUP_DEST/$host/$dir/$date_dir"; # current one
186 my $real_bl;
187 if (-l $bl) {
188 $real_bl=readlink($bl) || die "can't read link $bl: $!";
189 $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
190 if (-l $bc && $real_bl eq $bc) {
191 xlog($share,"allready backuped...");
192 $backup_ok++;
193 next;
194 }
195
196 }
197
198
199 xlog($share,"working on $share...");
200
201 # try to nmblookup IP
202 $ip = get_ip($share) if (! $ip);
203
204 if ($ip) {
205 xlog($share,"IP is $ip");
206 if (host_up($ping, $ip,$PING_TIMEOUT)) {
207 if (snap_share($share,$user,$passwd,$workgroup)) {
208 $backup_ok++;
209 }
210 }
211 }
212 }
213 close(M);
214
215 my $total = ($#in_backup + 1) || 0;
216 my $pcnt = "";
217 $pcnt = "(".int($backup_ok*100/$total)." %)" if ($total > 0);
218 xlog("","$backup_ok backups completed of total $total this time".$pcnt);
219
220 send_email();
221
222 1;
223
224 #-------------------------------------------------------------------------
225
226
227 # get IP number from share
228 sub get_ip {
229 my $share = shift;
230
231 my $host = $1 if ($share =~ m#//([^/]+)/#);
232
233 my $ip = `nmblookup $host`;
234 if ($ip =~ m/(\d+\.\d+\.\d+\.\d+)\s$host/i) {
235 return $1;
236 }
237 }
238
239 # send e-mail with all messages
240 sub send_email {
241 return if (! $email || $email eq "" || !$email_body);
242 require Mail::Send;
243 my $msg = new Mail::Send;
244 $msg->to($email);
245 $msg->subject($subjects[$sub_nr]);
246 my $fn=$msg->open;
247 print $fn $email_body;
248 $fn->close;
249 }
250
251
252 # write entry to screen and log
253 sub xlog {
254 my $share = shift;
255 my $t = strftime $LOG_TIME_FMT, localtime;
256 my $m = shift || '[no log entry]';
257 my $l = shift;
258 $l = 1 if (! defined $l); # default verbosity is 1
259 if ($verbose >= $l) {
260 if (! $email) {
261 print STDERR $m,"\n";
262 # don't e-mail mesages with verbosity < 1
263 } elsif ($l < 1) {
264 $email_body .= $m."\n";
265 }
266 }
267 print L "$t $share\t$m\n";
268 }
269
270 # dump warn and dies into log
271 BEGIN { $SIG{'__WARN__'} = sub { xlog('WARN',$_[0],1) ; exit 1 } }
272 BEGIN { $SIG{'__DIE__'} = sub { xlog('DIE',$_[0],0) ; exit 1 } }
273
274
275 # split share name to host, dir and currnet date dir
276 sub share2host_dir {
277 my $share = shift;
278 my ($host,$dir);
279 if ($share =~ m#//([^/]+)/(.+)$#) {
280 ($host,$dir) = ($1,$2);
281 $dir =~ s/\W/_/g;
282 $dir =~ s/^_+//;
283 $dir =~ s/_+$//;
284 } else {
285 xlog($share,"Can't parse share $share into host and directory!",1);
286 return;
287 }
288 return ($host,$dir,strftime $DIR_TIME_FMT, localtime);
289 }
290
291
292 # make a snapshot of a share
293 sub snap_share {
294
295 my $share = shift;
296
297 my %param = ( debug => 0 );
298
299 $param{username} = shift || warn "can't find username for share $share";
300 $param{password} = shift || warn "can't find passwod for share $share";
301 $param{workgroup} = shift || warn "can't find workgroup for share $share";
302
303 my ($host,$dir,$date_dir) = share2host_dir($share);
304
305 # latest backup directory
306 my $bl = "$BACKUP_DEST/$host/$dir/latest";
307 # current backup directory
308 my $bc = "$BACKUP_DEST/$host/$dir/$date_dir";
309
310 my $real_bl;
311 if (-l $bl) {
312 $real_bl=readlink($bl) || die "can't read link $bl: $!";
313 $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
314 if (! -e $real_bl) {
315 xlog($share,"latest link $bl -> $real_bl not valid, removing it");
316 unlink $bl || die "can't remove link $bl: $!";
317 undef $real_bl;
318 }
319 }
320 if (! $real_bl) {
321 xlog($share,"no old backup, trying to find last backup");
322 if (opendir(BL_DIR, "$BACKUP_DEST/$host/$dir")) {
323 my @bl_dirs = sort grep { !/^\./ && -d "$BACKUP_DEST/$host/$dir/$_" } readdir(BL_DIR);
324 closedir(BL_DIR);
325 $real_bl=pop @bl_dirs;
326 xlog($share,"using $real_bl as latest...");
327 $real_bl="$BACKUP_DEST/$host/$dir/$real_bl" if (substr($real_bl,0,1) ne "/");
328 if ($real_bl eq $bc) {
329 xlog($share,"latest from today (possible partial backup)");
330 rename $real_bl,$real_bl.".partial" || warn "can't reaname partial backup: $!";
331 $real_bl .= ".partial";
332 }
333 } else {
334 xlog($share,"this is first run...");
335 }
336 }
337
338 if (-l $bc && $real_bl && $real_bl eq $bc) {
339 xlog($share,"allready backuped...");
340 return 1;
341 }
342
343 die "You should really create BACKUP_DEST [$BACKUP_DEST] by hand! " if (!-e $BACKUP_DEST);
344
345 if (! -e "$BACKUP_DEST/$host") {
346 mkdir "$BACKUP_DEST/$host" || die "can't make dir for host $host, $BACKUP_DEST/$host: $!";
347 xlog($share,"created host directory $BACKUP_DEST/$host...");
348 }
349
350 if (! -e "$BACKUP_DEST/$host/$dir") {
351 mkdir "$BACKUP_DEST/$host/$dir" || die "can't make dir for share $share, $BACKUP_DEST/$host/$dir $!";
352 xlog($share,"created dir for this share $BACKUP_DEST/$host/$dir...");
353 }
354
355 mkdir $bc || die "can't make dir for current backup $bc: $!";
356
357 my @dirs = ( "/" );
358 my @smb_dirs = ( "/" );
359
360 my $transfer = 0; # bytes transfered over network
361
362 # this will store all available files and sizes
363 my @files;
364 my %file_size;
365 my %file_atime;
366 my %file_mtime;
367 #my %file_md5;
368 %file_md5 = ();
369
370 my @smb_files;
371 my %smb_size;
372 #my %smb_atime;
373 #my %smb_mtime;
374
375 sub norm_dir {
376 my $foo = shift;
377 my $prefix = shift;
378 $foo =~ s#//+#/#g;
379 $foo =~ s#/+$##g;
380 $foo =~ s#^/+##g;
381 return $prefix.$foo if ($prefix);
382 return $foo;
383 }
384
385 # read local filesystem
386 my $di = 0;
387 while ($di <= $#dirs && $real_bl) {
388 my $d=$dirs[$di++];
389 opendir(DIR,"$real_bl/$d") || warn "opendir($real_bl/$d): $!\n";
390
391 # read .backupignore if exists
392 if (-f "$real_bl/$d/.backupignore") {
393 open(I,"$real_bl/$d/.backupignore");
394 while(<I>) {
395 chomp;
396 push @ignore,norm_dir("$d/$_");
397 }
398 close(I);
399 #print STDERR "ignore: ",join("|",@ignore),"\n";
400 link "$real_bl/$d/.backupignore","$bc/$d/.backupignore" ||
401 warn "can't copy $real_bl/$d/.backupignore to current backup dir: $!\n";
402 }
403
404 # read .md5sum if exists
405 if (-f "$real_bl/$d/.md5sum") {
406 open(I,"$real_bl/$d/.md5sum");
407 while(<I>) {
408 chomp;
409 my ($md5,$f) = split(/\s+/,$_,2);
410 $file_md5{$f}=$md5;
411 }
412 close(I);
413 }
414
415 my @clutter = readdir(DIR);
416 foreach my $f (@clutter) {
417 next if ($f eq '.');
418 next if ($f eq '..');
419 my $pr = norm_dir("$d/$f"); # path relative
420 my $pf = norm_dir("$d/$f","$real_bl/"); # path full
421 if (grep(/^\Q$pr\E$/,@ignore) == 0) {
422 if (-f $pf) {
423 push @files,$pr;
424 $file_size{$pr}=(stat($pf))[7];
425 $file_atime{$pr}=(stat($pf))[8];
426 $file_mtime{$pr}=(stat($pf))[9];
427 } elsif (-d $pf) {
428 push @dirs,$pr;
429 } else {
430 xlog($share,"not file or directory: $pf",0);
431 }
432 } else {
433 xlog($share,"ignored: $pr");
434 }
435 }
436 }
437
438 # local dir always include /
439 xlog($share,($#files+1)." files and ".($#dirs)." dirs on local disk before backup");
440
441 # read smb filesystem
442
443 xlog($share,"smb to $share as $param{username}/$param{workgroup}");
444
445 # FIX: how to aviod creation of ~/.smb/smb.conf ?
446 $smb = new Filesys::SmbClient(%param) || die "SmbClient :$!\n";
447
448 $di = 0;
449 while ($di <= $#smb_dirs) {
450 my $d=$smb_dirs[$di];
451 my $pf = norm_dir($d,"smb:$share/"); # path full
452 my $D = $smb->opendir($pf);
453 if (! $D) {
454 xlog($share,"FATAL: $share [$pf] as $param{username}/$param{workgroup}: $!",0);
455 # remove failing dir
456 delete $smb_dirs[$di];
457 return 0; # failed
458 }
459 $di++;
460
461 my @clutter = $smb->readdir_struct($D);
462 foreach my $item (@clutter) {
463 my $f = $item->[1];
464 next if ($f eq '.');
465 next if ($f eq '..');
466 my $pr = norm_dir("$d/$f"); # path relative
467 my $pf = norm_dir("$d/$f","smb:$share/"); # path full
468 if (grep(/^\Q$pr\E$/,@ignore) == 0) {
469 if ($item->[0] == main::SMBC_FILE) {
470 push @smb_files,$pr;
471 $smb_size{$pr}=($smb->stat($pf))[7];
472 $smb_atime{$pr}=($smb->stat($pf))[10];
473 $smb_mtime{$pr}=($smb->stat($pf))[11];
474 } elsif ($item->[0] == main::SMBC_DIR) {
475 push @smb_dirs,$pr;
476 } else {
477 xlog($share,"not file or directory [".$item->[0]."]: $pf",0);
478 }
479 } else {
480 xlog($share,"smb ignored: $pr");
481 }
482 }
483 }
484
485 xlog($share,($#smb_files+1)." files and ".($#smb_dirs)." dirs on remote share");
486
487 # sync dirs
488 my $lc = List::Compare->new(\@dirs, \@smb_dirs);
489
490 my @dirs2erase = $lc->get_Lonly;
491 my @dirs2create = $lc->get_Ronly;
492 xlog($share,($#dirs2erase+1)." dirs to erase and ".($#dirs2create+1)." dirs to create");
493
494 # create new dirs
495 foreach (sort @smb_dirs) {
496 mkdir "$bc/$_" || warn "mkdir $_: $!\n";
497 }
498
499 # sync files
500 $lc = List::Compare->new(\@files, \@smb_files);
501
502 my @files2erase = $lc->get_Lonly;
503 my @files2create = $lc->get_Ronly;
504 xlog($share,($#files2erase+1)." files to erase and ".($#files2create+1)." files to create");
505
506 sub smb_copy {
507 my $smb = shift;
508
509 my $from = shift;
510 my $to = shift;
511
512
513 my $l = 0;
514
515 foreach my $f (@_) {
516 #print "smb_copy $from/$f -> $to/$f\n";
517 my $md5 = Digest::MD5->new;
518
519 my $fd = $smb->open("$from/$f");
520 if (! $fd) {
521 xlog("WARNING","can't open smb file $from/$f: $!");
522 next;
523 }
524
525 if (! open(F,"> $to/$f")) {
526 xlog("WARNING","can't open new file $to/$f: $!");
527 next;
528 }
529
530 while (defined(my $b=$smb->read($fd,4096))) {
531 print F $b;
532 $l += length($b);
533 $md5->add($b);
534 }
535
536 $smb->close($fd);
537 close(F);
538
539 $file_md5{$f} = $md5->hexdigest;
540
541 # FIX: this fails with -T
542 my ($a,$m) = ($smb->stat("$from/$f"))[10,11];
543 utime $a, $m, "$to/$f" ||
544 warn "can't update utime on $to/$f: $!\n";
545
546 }
547 return $l;
548 }
549
550 # copy new files
551 foreach (@files2create) {
552 $transfer += smb_copy($smb,"smb:$share",$bc,$_);
553 }
554
555 my $size_sync = 0;
556 my $atime_sync = 0;
557 my $mtime_sync = 0;
558 my @sync_files;
559 my @ln_files;
560
561 foreach ($lc->get_intersection) {
562
563 my $f;
564
565 if ($file_size{$_} != $smb_size{$_}) {
566 $f=$_;
567 $size_sync++;
568 }
569 if ($file_atime{$_} != $smb_atime{$_}) {
570 $f=$_;
571 $atime_sync++;
572 }
573 if ($file_mtime{$_} != $smb_mtime{$_}) {
574 $f=$_;
575 $mtime_sync++;
576 }
577
578 if ($f) {
579 push @sync_files, $f;
580 } else {
581 push @ln_files, $_;
582 }
583 }
584
585 xlog($share,($#sync_files+1)." files will be updated (diff: $size_sync size, $atime_sync atime, $mtime_sync mtime), ".($#ln_files+1)." will be linked.");
586
587 foreach (@sync_files) {
588 $transfer += smb_copy($smb,"smb:$share",$bc,$_);
589 }
590
591 xlog($share,"$transfer bytes transfered...");
592
593 foreach (@ln_files) {
594 link "$real_bl/$_","$bc/$_" || warn "link $real_bl/$_ -> $bc/$_: $!\n";
595 }
596
597 # remove files
598 foreach (sort @files2erase) {
599 unlink "$bc/$_" || warn "unlink $_: $!\n";
600 delete $file_md5{$_};
601 }
602
603 # remove not needed dirs (after files)
604 foreach (sort @dirs2erase) {
605 rmdir "$bc/$_" || warn "rmdir $_: $!\n";
606 }
607
608 # remove old .md5sum
609 foreach (sort @dirs) {
610 unlink "$bc/$_/.md5sum" if (-e "$bc/$_/.md5sum");
611 }
612
613 # erase stale entries in .md5sum
614 my @md5_files = keys %file_md5;
615 $lc = List::Compare->new(\@md5_files, \@smb_files);
616 foreach my $file ($lc->get_Lonly) {
617 xlog("NOTICE","removing stale '$file' from .md5sum");
618 delete $file_md5{$file};
619 }
620
621 # create .md5sum
622 my $last_dir = '';
623 my $md5;
624 foreach my $f (sort { $file_md5{$a} cmp $file_md5{$b} } keys %file_md5) {
625 my $dir = dirname($f);
626 my $file = basename($f);
627 #print "$f -- $dir / $file<--\n";
628 if ($dir ne $last_dir) {
629 close($md5) if ($md5);
630 open($md5, ">> $bc/$dir/.md5sum") || warn "can't create $bc/$dir/.md5sum: $!";
631 $last_dir = $dir;
632 #print STDERR "writing $last_dir/.md5sum\n";
633 }
634 print $md5 $file_md5{$f}," $file\n";
635 }
636 close($md5) if ($md5);
637
638 # create leatest link
639 #print "ln -s $bc $real_bl\n";
640 if (-l $bl) {
641 unlink $bl || warn "can't remove old latest symlink $bl: $!\n";
642 }
643 symlink $bc,$bl || warn "can't create latest symlink $bl -> $bc: $!\n";
644
645 # FIX: sanity check -- remove for speedup
646 xlog($share,"failed to create latest symlink $bl -> $bc...") if (readlink($bl) ne $bc || ! -l $bl);
647
648 xlog($share,"backup completed...");
649
650 return 1;
651 }
652 __END__
653 #-------------------------------------------------------------------------
654
655
656 =head1 NAME
657
658 psinib - Perl Snapshot Is Not Incremental Backup
659
660 =head1 SYNOPSIS
661
662 ./psinib.pl [OPTION]... [mount script]
663
664 =head1 DESCRIPTION
665
666 Option can be one of more of following:
667
668 =over 8
669
670 =item C<--backupdest=dir>
671
672 Specify backup destination directory (defaults is /data/
673
674 =item C<--noping>
675
676 Don't use ping to check if host is up (default is ti use tcp syn to cifs
677 port)
678
679 =item C<--verbose -v>
680
681 Increase verbosity level. Defailt is 1 which prints moderate amount of data
682 on STDOUT and STDERR.
683
684 =item C<--quiet -q>
685
686 Decrease verbosity level
687
688 =item C<--email=email@domain>
689
690 Send e-mails instead of dumping errors to STDERR. Useful for cron jobs.
691
692 =back
693
694 This script in current version support just backup of Samba (or Micro$oft
695 Winblowz) shares to central disk space. Central disk space is organized in
696 multiple directories named after:
697
698 =over 4
699
700 =item *
701 server which is sharing files to be backed up
702
703 =item *
704 name of share on server
705
706 =item *
707 dated directory named like standard ISO date format (YYYYMMDD).
708
709 =back
710
711 In each dated directory you will find I<snapshot> of all files on
712 exported share on that particular date.
713
714 You can also use symlink I<latest> which will lead you to
715 last completed backup. After that you can use some other backup
716 software to transfer I<snapshot> to tape, CD-ROM or some other media.
717
718 =head2 Design considerations
719
720 Since taking of share snapshot every day requires a lot of disk space and
721 network bandwidth, B<psinib> uses several techniques to keep disk usage and
722 network traffic at acceptable level:
723
724 =over 3
725
726 =item - usage of hard-links to provide same files in each snapshot (as opposed
727 to have multiple copies of same file)
728
729 =item - usage of file size, atime and mtime to find changes of files without
730 transferring whole file over network (just share browsing is transfered
731 over network)
732
733 =item - usage of C<.md5sum> files (compatible with command-line utility
734 C<md5sum>) to keep file between snapshots hard-linked
735
736 =back
737
738 =head1 CONFIGURATION
739
740 This section is not yet written.
741
742 =head1 HACKS, TRICKS, BUGS and LIMITATIONS
743
744 This chapter will have all content that doesn't fit anywhere else.
745
746 =head2 Can snapshots be more frequent than daily?
747
748 There is not real reason why you can't take snapshot more often than
749 once a day. Actually, if you are using B<psinib> to backup Windows
750 workstations you already know that they tend to come-and-go during the day
751 (reboots probably ;-), so running B<psinib> several times a day increases
752 your chance of having up-to-date backup (B<psinib> will not make multiple
753 snapshots for same day, nor will it update snapshot for current day if
754 it already exists).
755
756 However, changing B<psinib> to produce snapshots which are, for example, hourly
757 is a simple change of C<$DIR_TIME_FMT> which is currently set to
758 C<'%Y%m%d'> (see I<strftime> documentation for explanation of that
759 format). If you change that to C<'%Y%m%d-%H> you can have hourly snapshots
760 (if your network is fast enough, that is...). Also, some of messages in
761 program will sound strange, but other than that it should work.
762 I<You have been warned>.
763
764 =head2 Do I really need to share every directory which I want to snapshot?
765
766 Actually, no. Due to usage of C<Filesys::SmbClient> module, you can also
767 specify sub-directory inside your share that you want to backup. This feature
768 is most useful if you want to use administrative shares (but, have in mind
769 that you have to enter your Win administrator password in unencrypted file on
770 disk to do that) like this:
771
772 smbmount //server/c$/WinNT/fonts /mnt -o username=administrator%win
773
774 After that you will get directories with snapshots like:
775
776 server/c_WinNT_fonts/yyyymmdd/....
777
778 =head2 Won't I run out of disk space?
779
780 Of course you will... Snapshots and logfiles will eventually fill-up your disk.
781 However, you can do two things to stop that:
782
783 =head3 Clean snapshort older than x days
784
785 You can add following command to your C<root> crontab:
786
787 find /backup/isis_backup -type d -mindepth 3 -maxdepth 3 -mtime +11 -exec rm -Rf {} \;
788
789 I assume that C</backup/isis_backup> is directory in which are your snapshots
790 and that you don't want to keep snapshots older than 11 days (that's
791 C<-mtime +11> part of command).
792
793 =head3 Rotate your logs
794
795 I will leave that to you. I relay on GNU/Debian's C<logrotate> to do it for me.
796
797 =head2 What are I<YYYYMMDD.partial> directories?
798
799 If there isn't I<latest> symlink in snapshot directory, it's preatty safe to
800 assume that previous backup from that day failed. So, that directory will
801 be renamed to I<YYYYMMDD.partial> and snapshot will be performed again,
802 linking same files (other alternative would be to erase that dir and find
803 second-oldest directory, but this seemed like more correct approach).
804
805 =head2 I can't connect to any share
806
807 Please verify that nmblookup (which is part of samba package) is in /bin or
808 /usr/bin. Also verify that nmblookup returns IP address for your server
809 using:
810
811 $ nmblookup tvhouse
812 querying tvhouse on 192.168.34.255
813 192.168.34.30 tvhouse<00>
814
815 If you don't get any output, your samba might not listen to correct interface
816 (see interfaces in smb.conf).
817
818 =head2 Aren't backups boring?
819
820 No! If you have subjects.txt in same directory as C<psinib.pl> you can get
821 various funny subjects in your mail. They change over time as long as you
822 ignore your backup.
823
824 =head1 AUTHOR
825
826 Dobrica Pavlinusic <dpavlin@rot13.org>
827
828 L<http:E<sol>E<sol>www.rot13.orgE<sol>~dpavlinE<sol>>
829
830 =head1 LICENSE
831
832 This product is licensed under GNU Public License (GPL) v2 or later.
833
834 =cut

  ViewVC Help
Powered by ViewVC 1.1.26