Last active
January 22, 2025 20:18
-
-
Save bitcloud/4ac52586334a2ddc54ff to your computer and use it in GitHub Desktop.
Proxmox backup-hook for Hetzner Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl -w | |
# hook script for vzdump (--script option) | |
=begin comment | |
backuphook for Proxmox | |
renames files so that they include the hostname of the machine | |
Instructions for Hetzner Backup Server auth via SSH | |
--- | |
Generate ssh key | |
> ssh-keygen | |
change to rfc format | |
> ssh-keygen -e -f backup_key.pub | grep -v "Comment:" > backup_key_rfc.pub | |
create .ssh folder on backupserver | |
> echo mkdir .ssh | sftp [email protected] | |
copy backup key | |
> scp backup_key [email protected]:.ssh/authorized_keys | |
create a GPG Key on another system | |
> gpg --gen-key | |
export public key | |
> gpg --export --armor [email protected] > backup-pubkey.asc | |
import key on server | |
> gpg --import backup-pubkey.asc | |
get key id and put it in the script | |
> gpg --list-keys | |
maxlocalbackups sets the amount of backups stored locally. the proxmox internal function doesn't work anymore, becuase of renaming the files. | |
maxremotebackups sets the amount of backups stored remotly | |
add this script to the vzdump config file | |
# echo "script: /usr/local/bin/backup-hook.pl" >> /etc/vzdump.conf | |
Required tools are: | |
gpg, lftp | |
lftp had an issue with uploading streamed data via sftp while I was writing this (https://github.com/lavv17/lftp/issues/104) | |
Had to compile my own but fix should be distributed soon. | |
# Based on: | |
# http://www.dim13.org/2011/05/Backup-Proxmox-Containers-to-FTP | |
# http://undefinederror.org/kvmopenvz-dumps-with-specific-name-using-a-hook/ | |
=end comment | |
=cut | |
use strict; | |
use warnings; | |
use File::Copy qw(move); | |
use File::Basename; | |
use Time::Local; | |
print "HOOK: " . join (' ', @ARGV) . "\n"; | |
my $gpgkeyid = "XXXXXXXX"; | |
my $maxremotebackups = 2; | |
my $maxlocalbackups = 7; | |
my $sshkey = "/root/backup/backup_key"; | |
my $sshhost = "sftp://uXXXXX:\@uXXXXX.your-backup.de"; | |
my %lftpscript = ( | |
"lftpbin" => "/root/backup/lftp", | |
"prefix" => "set sftp:connect-program \"ssh -a -x -i $sshkey\";\n", | |
"connect" => "connect $sshhost;\ncd vm_backups;\n", | |
"get-file-list" => "cls -1", | |
"remove-file" => "rm ", | |
"upload" => "put /dev/stdin -o " | |
); | |
my $phase = shift; | |
my $mode = shift; # stop/suspend/snapshot | |
my $vmid = shift; | |
my $vmtype = $ENV{VMTYPE}; # openvz/qemu/lxc | |
my $dumpdir = $ENV{DUMPDIR}; | |
my $hostname = $ENV{HOSTNAME}; | |
my $tarfile = $ENV{TARFILE}; | |
my $logfile = $ENV{LOGFILE}; | |
my %dispatch = ( | |
"job-start" => \&nop, | |
"job-end" => \&nop, | |
"job-abort" => \&nop, | |
"backup-start" => \&backup_start, | |
"backup-end" => \&backup_end, | |
"backup-abort" => \&nop, | |
"log-end" => \&log_end, | |
"pre-stop" => \&nop, | |
"pre-restart" => \&nop, | |
"post-restart" => \&nop, | |
); | |
# code to remove old backups copied and extended from proxmox to support changed names | |
sub get_backup_file_list { | |
my ($dir, $bkname, $exclude_fn) = @_; | |
my $bklist = []; | |
foreach my $fn (<$dir/${bkname}-*>) { | |
next if $exclude_fn && $fn eq $exclude_fn; | |
if ($fn =~ m!/(${bkname}-?.*-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|((tar|vma)(\.(gz|lzo))?)))$!) { | |
$fn = "$dir/$1"; # untaint | |
my $t = timelocal ($7, $6, $5, $4, $3 - 1, $2 - 1900); | |
push @$bklist, [$fn, $t]; | |
} | |
} | |
return $bklist; | |
} | |
sub remove_old_backups { | |
my $file = shift; | |
$file=~/.+\/(vzdump-(qemu|openvz|lxc)-\d+).+/; | |
my $bkname = $1; | |
my $bklist = get_backup_file_list($dumpdir, $bkname, $file); | |
$bklist = [ sort { $b->[1] <=> $a->[1] } @$bklist ]; | |
while (scalar (@$bklist) >= $maxlocalbackups) { | |
my $d = pop @$bklist; | |
print "delete old backup '$d->[0]'\n"; | |
unlink $d->[0]; | |
my $logfn = $d->[0]; | |
$logfn =~ s/\.(tgz|((tar|vma)(\.(gz|lzo))?))$/\.log/; | |
unlink $logfn; | |
} | |
} | |
sub get_remote_backup_file_list { | |
my ( $bkname, $exclude_fn) = @_; | |
my $bklist = []; | |
my $ftpcommand = $lftpscript{'lftpbin'} . " -c '" . $lftpscript{'prefix'} . $lftpscript{'connect'} . $lftpscript{'get-file-list'} . "'"; | |
my @filelist = split /\n/, `$ftpcommand`; | |
foreach my $fn (@filelist) { | |
next if $exclude_fn && $fn eq $exclude_fn; | |
if ($fn =~ m!(${bkname}(-.*)?-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|((tar|vma)(\.(gz|lzo))?))\.gpg)$!) { | |
my $t = timelocal ($8, $7, $6, $5, $4 - 1, $3 - 1900); | |
push @$bklist, [$fn, $t]; | |
} | |
} | |
return $bklist; | |
} | |
sub unlink_remote_files{ | |
my $filelist = shift; | |
my $ftpcommand = $lftpscript{'lftpbin'} . " -c '" . $lftpscript{'prefix'} . $lftpscript{'connect'}; | |
foreach (@$filelist) { | |
$ftpcommand .= $lftpscript{'remove-file'} . "$_;\n"; | |
} | |
$ftpcommand .= "'"; | |
system($ftpcommand); | |
} | |
sub remove_old_remote_backups { | |
my $file = shift; | |
$file=~/(vzdump-(qemu|openvz|lxc)-\d+).+/; | |
my $bkname = $1; | |
my $bklist = get_remote_backup_file_list($bkname, $file . ".gpg"); | |
$bklist = [ sort { $b->[1] <=> $a->[1] } @$bklist ]; | |
my $unlinklist = []; | |
while (scalar (@$bklist) >= $maxremotebackups) { | |
my $d = pop @$bklist; | |
print "delete old backup '$d->[0]'\n"; | |
push @$unlinklist, $d->[0]; | |
my $logfn = $d->[0]; | |
$logfn =~ s/\.(tgz|((tar|vma)(\.(gz|lzo))\.gpg?))$/\.log.gpg/; | |
push @$unlinklist, $logfn; | |
} | |
unlink_remote_files($unlinklist) if (@$unlinklist); | |
} | |
sub renameFile { | |
my $file = shift; | |
if (defined ($file) and defined ($hostname)) { | |
if ( $file=~/(.+\/vzdump-(qemu|openvz|lxc)-\d+-)(\d\d\d\d_[^\/]+)/ ){ | |
my $newfile=$1.$hostname."-".$3; | |
print "HOOK: Renaming file $file to $newfile\n"; | |
move $file, $newfile; | |
return $newfile; | |
} | |
} | |
} | |
sub upload { | |
my $file = shift; | |
my $fileonly = basename($file); | |
print "HOOK: uploading encrypted file " . $file . " to ftp ...\n"; | |
my $ftpcommand = $lftpscript{'lftpbin'} . " -c '" . $lftpscript{'prefix'} . $lftpscript{'connect'} . $lftpscript{'upload'} . " $fileonly.gpg'"; | |
system("gpg --encrypt -r $gpgkeyid -o - $file | $ftpcommand") == 0 || | |
die "upload to backup-host failed"; | |
print "HOOK: encrypted upload done.\n"; | |
} | |
sub nop { | |
# nothing | |
} | |
sub backup_start { | |
#print "HOOK-ENV: phase=$phase; mode=$mode; vmid=$vmid; vmtype=$vmtype; dumpdir=$dumpdir; hostname=$hostname; tarfile=$tarfile; logfile=$logfile\n"; | |
remove_old_backups($tarfile); | |
remove_old_remote_backups($tarfile); | |
} | |
sub backup_end { | |
#print "HOOK-ENV: phase=$phase; mode=$mode; vmid=$vmid; vmtype=$vmtype; dumpdir=$dumpdir; hostname=$hostname; tarfile=$tarfile; logfile=$logfile\n"; | |
} | |
sub log_end { | |
#print "HOOK-ENV: phase=$phase; mode=$mode; vmid=$vmid; vmtype=$vmtype; dumpdir=$dumpdir; hostname=$hostname; tarfile=$tarfile; logfile=$logfile\n"; | |
$tarfile = renameFile($tarfile); | |
upload($tarfile); | |
$logfile = renameFile($logfile); | |
upload($logfile); | |
} | |
exists $dispatch{$phase} ? $dispatch{$phase}() : die "got unknown phase '$phase'"; | |
exit (0); |
There's a typo in the documentation at line 22 : scp backup_key [email protected]:.ssh/authorized_keys
should be scp backup_key_rfc.pub [email protected]:.ssh/authorized_keys
Also we had to edit our GPG key on the server to change the thrust level to 5 to avoid a thrust prompt. To do that : #gpg --edit-key 8FB08575
then gpg> trust
And change the path of lftp from /root/backup/lftp
to lftp
as we didn't compile ourself :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Moved the upload of the tar file to the log-end event to release the lvm snapshot volume early.
Should solve the problem that the snapshot outgrows its 1gb during upload.