py2b
changeset 2 5f03a7843dc2
parent 1 505c5bd23279
child 3 3f1318ea6bcb
child 4 79ab63474be7
equal deleted inserted replaced
1:505c5bd23279 2:5f03a7843dc2
    16 
    16 
    17 my $opt_level = 0;
    17 my $opt_level = 0;
    18 my $opt_today = strftime("%F", localtime);
    18 my $opt_today = strftime("%F", localtime);
    19 my @opt_debug = ();
    19 my @opt_debug = ();
    20 my $opt_verbose = 0;
    20 my $opt_verbose = 0;
       
    21 my $opt_dry = 0;
    21 #my $opt_node = hostname;
    22 #my $opt_node = hostname;
    22 #my $opt_dir = "backups/$opt_node/daily";
    23 #my $opt_dir = "backups/$opt_node/daily";
    23 
    24 
    24 # all configs are below 
    25 # all configs are below 
    25 my $CONFIG_DIR = "./py2.d";
    26 my $CONFIG_DIR = "./py2.d";
    26 my $NODE = hostname;
    27 my $NODE = hostname;
    27 
    28 
    28 sub get_configs($);
    29 sub get_configs($);
    29 sub get_candidates();
    30 sub get_candidates();
    30 sub verbose(@);
    31 sub verbose(@);
       
    32 
       
    33 our @AT_EXIT;
       
    34 END { $_->() foreach @AT_EXIT };
       
    35 $SIG{INT} = sub { warn "Got signal INT\n"; exit 1 };
    31 
    36 
    32 MAIN: {
    37 MAIN: {
    33     GetOptions(
    38     GetOptions(
    34 	"l|level=i" => \$opt_level,
    39 	"l|level=i" => \$opt_level,
    35 	"d|debug:s" => sub { push @opt_debug, split /,/, $_[1] },
    40 	"d|debug:s" => sub { push @opt_debug, split /,/, $_[1] },
    36 	"h|help" => sub { pod2usage(-exit => 0, -verbose => 1) },
    41 	"h|help" => sub { pod2usage(-exit => 0, -verbose => 1) },
    37 	"m|man" => sub { pod2usage(-exit => 0, -verbose => 3) },
    42 	"m|man" => sub { pod2usage(-exit => 0, -verbose => 3) },
    38 	"v|verbose" => \$opt_verbose,
    43 	"v|verbose" => \$opt_verbose,
       
    44 	"dry" => \$opt_dry,
    39     ) or pod2usage;
    45     ) or pod2usage;
    40 
    46 
    41     my %cf = get_configs($CONFIG_DIR);
    47     my %cf = get_configs($CONFIG_DIR);
    42     my %default = %{$cf{DEFAULT}};
    48     my %default = %{$cf{DEFAULT}};
    43     ### config: %cf
    49     ### config: %cf
    44 
    50 
    45     my %dev = get_candidates();
    51     my @dev = get_candidates();
    46     ### current devices: %dev
    52     ### current candiates: @dev
    47 
    53 
    48     my $ftp = new FTP($default{FTP_HOST}, 
    54     my $ftp = new FTP($default{FTP_HOST}, 
    49 	Passive => $default{FTP_PASSIVE}, 
    55 	Passive => $default{FTP_PASSIVE}, 
    50 	Debug => @opt_debug ~~ /^ftp$/) or die $@;
    56 	Debug => @opt_debug ~~ /^ftp$/) or die $@;
    51     $ftp->login or die $ftp->message;
    57     $ftp->login or die $ftp->message;
       
    58     $ftp->try(binary => ());
    52     $ftp->try(mkpath => $default{FTP_DIR});    
    59     $ftp->try(mkpath => $default{FTP_DIR});    
    53     $ftp->try(cwd => $default{FTP_DIR});
    60     $ftp->try(cwd => $default{FTP_DIR});
    54 
    61 
    55     if ($opt_level == 0) {
    62     if ($opt_level == 0) {
    56 	$ftp->try(mkpath => $opt_today);
    63 	$ftp->try(mkpath => $opt_today);
    66 
    73 
    67     # now sitting inside the directory for the last full backup
    74     # now sitting inside the directory for the last full backup
    68     verbose "Now in @{[$ftp->pwd]}.\n";
    75     verbose "Now in @{[$ftp->pwd]}.\n";
    69 
    76 
    70     # and now we can start doing something with our filesystems
    77     # and now we can start doing something with our filesystems
    71     foreach my $dev (keys %dev) {
    78     foreach my $dev (@dev) {
    72 	my $file = basename($dev) . ".$opt_level.gz.ssl";
    79 
    73 	my $label = "$NODE:" . basename($dev{$dev});
    80 	my $file = basename($dev->{dev}) . ".$opt_level.gz.ssl";
    74 	verbose "Working on $dev as $dev{$dev}, stored as $file\n";
    81 	my $label = "$NODE:" . basename($dev->{rdev});
       
    82 	verbose "Working on $dev->{dev} as $dev->{rdev}, stored as $file\n";
       
    83 
       
    84 	# For LVM do a snapshot, for regular partitions
       
    85 	# do nothing. But anyway the device to dump is named in $dev->{dump}
       
    86 	if ($dev->{lvm}) {
       
    87 	    # we can do a snapshot
       
    88 	    # FIXME: calculate the size
       
    89 	    my $snap = "$dev->{lvm}{path}-0";
       
    90 
       
    91 	    verbose "Creating snapshot $snap\n";
       
    92 	    system($_ = "lvcreate -s -L 1G -n $snap $dev->{lvm}{path} >/dev/null");
       
    93 	    die "failed system command: $_\n" if $?;
       
    94 
       
    95 	    $dev->{cleanup} = sub { system "lvdisplay $snap &>/dev/null"
       
    96 				      . " && lvremove -f $snap >/dev/null" };
       
    97 	    push @AT_EXIT, $dev->{cleanup};
       
    98 
       
    99 	    (my $device) = (grep /lv name/i, `lvdisplay $snap`)[0] =~ /(\S+)\s*$/;
       
   100 
       
   101 	    system($_ = "fsck -f -C0 -y $device");
       
   102 	    warn "fsck on $device (using: $_) failed\n" if $?;
       
   103 
       
   104 	    ($dev->{dump}) = $device;
       
   105 
       
   106 	}
       
   107 	else {
       
   108 	    $dev->{dump} = $dev->{rdev}
       
   109 	}
       
   110 
       
   111 	### $dev
       
   112 
    75 	$ENV{key} = $default{KEY};
   113 	$ENV{key} = $default{KEY};
    76 	my $dumper = open(my $dump, "-|") or do {
   114 	my $dumper = open(my $dump, "-|") or do {
    77 	    my $head = <<__;
   115 	    my $head = <<__;
    78 #! /bin/bash
   116 #! /bin/bash
    79 echo "LEVEL $opt_level: $dev $dev{$dev}"
   117 echo "LEVEL $opt_level: $dev->{dev} $dev->{rdev} ($dev->{dump})" >&2
    80 read -p "sure to restore? (yes/no): "
   118 tail -c XXXX \$0 | openssl enc -d -blowfish "\$@" | gzip -d
    81 test "\$REPLY" = "yes" || exit
       
    82 exec dd if=\$0 bs=10k skip=1 | openssl enc -d -blowfish "\$@" | gzip -d | restore -rf-
       
    83 exit
   119 exit
       
   120 
    84 __
   121 __
    85 	    print $head, " " x (10240 - length($head) - 1), "\n";
   122 	    # adjust the placeholder
    86 	    exec "dump -$opt_level -L $label -f- -u $dev{$dev}"
   123 	    $head =~ s/XXXX/sprintf "% 4s", "+" . (length($head) +1)/e;
       
   124 	    print $head;
       
   125 	    exec "dump -$opt_level -L $label -f- -u $dev->{dump}"
    87 	    . "| gzip"
   126 	    . "| gzip"
    88 	    . "| openssl enc -pass env:key -salt -blowfish";
   127 	    . "| openssl enc -pass env:key -salt -blowfish";
    89 	    die "Can't exec dumper\n";
   128 	    die "Can't exec dumper\n";
    90 	};
   129 	};
       
   130 
    91 	$ftp->try(put => $dump, $file);
   131 	$ftp->try(put => $dump, $file);
       
   132 	$dev->{cleanup}->() if $dev->{cleanup};
    92 	verbose "Done.\n";
   133 	verbose "Done.\n";
    93     }
   134     }
    94 
   135 
    95 }
   136 }
    96 
   137 
   100 }
   141 }
   101 
   142 
   102 sub get_candidates() {
   143 sub get_candidates() {
   103 # return the list of backup candidates
   144 # return the list of backup candidates
   104 
   145 
   105     my %dev;
   146     my @dev;
       
   147 
       
   148     # later we need the major of the device mapper
       
   149     my $dev_mapper = 0;
       
   150     $_ = (grep /device.mapper/, slurp("/proc/devices"))[0]
       
   151 	and $dev_mapper = (split)[0];
   106 
   152 
   107     foreach (slurp("/etc/fstab")) {
   153     foreach (slurp("/etc/fstab")) {
   108 	my ($dev, $mp, $fstype, $options, $dump, $check)
   154 	my ($dev, $mp, $fstype, $options, $dump, $check)
   109 	    = split;
   155 	    = split;
   110 	next if not $dump;
   156 	next if not $dump;
   112 	# $dev does not have to contain the real device
   158 	# $dev does not have to contain the real device
   113 	my $rdev = $dev;
   159 	my $rdev = $dev;
   114 	if ($dev ~~ /^(LABEL|UUID)=/) {
   160 	if ($dev ~~ /^(LABEL|UUID)=/) {
   115 	    chomp($rdev = `blkid -c /dev/null -o device -t '$dev'`);
   161 	    chomp($rdev = `blkid -c /dev/null -o device -t '$dev'`);
   116 	}
   162 	}
   117 	$dev{$dev} = $rdev;
   163 	$rdev = readlink $rdev while -l $rdev;
   118     }
   164 
   119 
   165 	# if it's LVM we gather more information (to support snapshots)
   120     return %dev;
   166 	my $lvm;
       
   167 	if ((stat $rdev)[6] >> 8 == $dev_mapper) {
       
   168 	    @{$lvm}{qw/vg lv/} = map { s/--/-/g; $_ } basename($rdev) =~ /(.+[^-])-([^-].+)/;
       
   169 	    $lvm->{path} = "$lvm->{vg}/$lvm->{lv}";
       
   170 	}
       
   171 
       
   172 	push @dev, {
       
   173 	    dev => $dev,
       
   174 	    rdev => $rdev,
       
   175 	    mount_point => $mp,
       
   176 	    fstype => $fstype,
       
   177 	    lvm => $lvm,
       
   178 	};
       
   179     }
       
   180 
       
   181     return @dev;
   121 }
   182 }
   122 
   183 
   123 sub get_configs($) {
   184 sub get_configs($) {
   124     local $_;
   185     local $_;
   125     my %r;
   186     my %r;