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; |