a44c2535142ded7df6a092112668dd3a277aa5ff
[vmdk-backup] / vmdk-backup.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 use autodie qw(:all);
5 use Data::Dump qw(dump);
6
7 my $start_t = time();
8
9 my $hostname = `hostname -s`;
10 chomp($hostname);
11
12 # path to create vmdk into (not on same disk as backup)
13 my $vmdk="/mnt/backup/${hostname}.vmdk";
14
15 # root disk device name
16 my $sda="sda";
17
18 # name of new backup vg
19 my $vg_backup="backup";
20
21 # where to mount new filesystem while creating vmdk
22 my $tmp = "/tmp/backup";
23
24
25 warn "collecting filesystem info\n";
26
27 my $blks;
28 open(my $blkid, '-|', 'blkid');
29 while(<$blkid>) {
30         chomp;
31         my ( $dev, $params ) = split(/:\s+/, $_, 2);
32         foreach my $single ( split(/\s+/, $params ) ) {
33                 my ( $name, $value ) = split(/=/, $single, 2);
34                 $value =~ s/"//g;
35                 $blks->{$dev}->{$name} = $value;
36         }
37 }
38 warn "blks = ",dump($blks);
39
40 my @pvs;
41 open(my $pv, '-|', "pvs --noheadings --options pv_name --unbuffered");
42 while(<$pv>) {
43         chomp;
44         s/ *//g;
45         if ( s{$sda}{nbd0p} ) {
46                 push @pvs, $_;
47         } else {
48                 warn "SKIP pv $_\n";
49         }
50 }
51 warn "# pvs = ",dump(@pvs);
52
53 my @lv_create;
54 my @lv_create_snapshot;
55 my @lv_remove;
56 open(my $lvs, '-|', "lvs --noheadings --options lv_name,vg_name,lv_size --units k");
57 while(<$lvs>) {
58         chomp;
59         s/^\s*//;
60         s/\s*$//;
61         my ($name,$vg,$size) = split(/\s+/, $_, 3);
62         if ( $vg eq $vg_backup ) {
63                 warn "SKIP $name in $vg_backup !\n";
64                 next;
65         }
66         push @lv_create, "lvcreate --name $name --size $size $vg_backup";
67         next if $name =~ m/swap/;
68         push @lv_create, "lvcreate --snapshot /dev/$vg/$name --name ${name}-snap --size 100M";
69         push @lv_remove, "/dev/$vg/$name-snap";
70 }
71 warn "# lv_create = ",dump(@lv_create);
72
73 my @mounts;
74 open(my $mount, '-|', 'mount');
75 while(<$mount>) {
76         chomp;
77         my ($dev, undef, $path, undef, $fs, undef) = split(/\s+/,$_);
78         push @mounts, [ $dev, $path, $fs ];
79 }
80 warn "# mounts = ",dump(@mounts);
81
82 sub sh {
83         warn "# @_\n";
84         system @_ unless $ENV{DEBUG};
85 }
86
87
88 warn "begin vmdk creation...\n";
89
90 my $size = `blockdev --getsize64 /dev/$sda`;
91 sh "qemu-img create -f vmdk -o compat6 $vmdk $size";
92
93 sh "modprobe nbd max_part=8";
94
95 my $nbd_pid;
96 if ( $nbd_pid = fork ) {
97         # parent
98
99         sh "qemu-nbd --verbose --connect /dev/nbd0 $vmdk";
100
101         exit 0;
102 }
103
104 sleep 1;
105
106 sh "sfdisk -d /dev/$sda | sfdisk --force /dev/nbd0";
107
108 sh "pvcreate $_" foreach @pvs;
109
110 sh "vgcreate $vg_backup @pvs";
111
112 sh $_ foreach @lv_create;
113
114 mkdir $tmp unless -d $tmp;
115
116 my @umount;
117
118 sub dev_to_backup {
119         my $dev = shift;
120         $dev =~ s{/dev/$sda}{/dev/nbd0p} ||
121         $dev =~ s{/dev/mapper/.+-([^-]+)}{/dev/mapper/$vg_backup-$1} ||
122         die "can't map $dev to new backup device!";
123         return $dev;
124 }
125
126 foreach my $mount ( @mounts ) {
127         my ( $dev, $path, $fs ) = @$mount;
128         if ( exists $blks->{$dev} ) {
129                 warn "working on $dev $path $fs\n";
130                 my $dev_backup = dev_to_backup($dev);
131                 my $label = $blks->{$dev}->{LABEL};
132                 $label = $label ? "-L $label" : '';
133                 sh "mkfs.$fs $label $dev_backup";
134                 mkdir $tmp . $path unless -e $tmp . $path;
135                 sh "mount $dev_backup $tmp$path";
136                 unshift @umount, $tmp . $path;
137                 $dev .= '-snap' if -e $dev . '-snap';
138                 chdir $tmp . $path;
139                 sh "dump -0 -f - $dev | restore -r -f -";
140         } else {
141                 warn "SKIP $dev $path $fs";
142         }
143 }
144
145 chdir '/';
146
147 if ( my $swap = (grep { $blks->{$_}->{TYPE} eq 'swap' } keys %$blks)[0] ) {
148         my $dev = dev_to_backup($swap);
149         my $label = $blks->{$swap}->{LABEL};
150         $label = $label ? "-L $label" : '';
151         warn "create swap on $dev\n";
152         sh "mkswap $label $dev";
153 }
154
155
156 warn "mount bind chroot...\n";
157
158 foreach ( qw(dev proc sys) ) {
159         sh "mount --bind /$_ $tmp/$_";
160         unshift @umount, "$tmp/$_";
161 }
162
163 warn "make backup bootable...\n";
164
165 open(my $sh, '>', "$tmp/tmp/backup-fixup.sh");
166 print $sh qq{
167 grub-install /dev/nbd0
168 update-grub
169 };
170 close($sh);
171 chmod 0755, "$tmp/tmp/backup-fixup.sh";
172
173 sh "chroot $tmp /tmp/backup-fixup.sh";
174
175 warn "wait for ENTER to continue...\n";
176 <STDIN>;
177
178
179 warn "cleanup...\n";
180
181 sh "umount $_" foreach @umount;
182
183 sh "lvremove -f $_" foreach @lv_remove;
184
185 sh "vgchange --available n $vg_backup";
186
187 sh "vgexport $vg_backup";
188
189 warn "finished in ", time() - $start_t, " seconds\n";
190
191 sh "qemu-nbd --disconnect /dev/nbd0";
192
193 sh "rmmod nbd";