Skip to content

Instantly share code, notes, and snippets.

@bitcloud
Last active July 10, 2021 11:01
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save bitcloud/4ac52586334a2ddc54ff to your computer and use it in GitHub Desktop.
Save bitcloud/4ac52586334a2ddc54ff to your computer and use it in GitHub Desktop.
Proxmox backup-hook for Hetzner Server
#!/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 uXXXXX@uXXXXX.your-backup.de
copy backup key
> scp backup_key uXXXXX@uXXXXX.your-backup.de:.ssh/authorized_keys
create a GPG Key on another system
> gpg --gen-key
export public key
> gpg --export --armor jqdoe@example.com > 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);
@bitcloud
Copy link
Author

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.

@aethernet
Copy link

There's a typo in the documentation at line 22 : scp backup_key uXXXXX@uXXXXX.your-backup.de:.ssh/authorized_keys should be scp backup_key_rfc.pub uXXXXX@uXXXXX.your-backup.de:.ssh/authorized_keys

@aethernet
Copy link

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

@aethernet
Copy link

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