Skip to content

Instantly share code, notes, and snippets.

@MadCamel
Created December 20, 2018 19:06
Show Gist options
  • Save MadCamel/3970ba751d7dee39b6cc892c6eda06f4 to your computer and use it in GitHub Desktop.
Save MadCamel/3970ba751d7dee39b6cc892c6eda06f4 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
# This script does pull-style backups with restic for multiple hosts. It supports
# both filesystem and limited mysql/mariadb backups.
#
# It establishes a SSH connection to the remote host and creates a TCP port forward
# from the remote machine to the local machine. It runs rest-server in append-only
# mode on this port, and triggers a restic backup to the rest-server instance.
#
# This is a good system for backing up less-trusted hosts from a more-trusted backup
# server, perhaps behind a restrictive firewall. The hosts do not need access to
# the backup server in any way. Hosts are restricted to append-only access to the
# repository while the backup is running and have zero access when the backup is not.
#
# Restic must be installed in /usr/local/bin on all hosts. Additionally the backup
# server needs /usr/local/bin/rest-server
#
# This script takes an input file in the following format:
# hostname type pool excludes
# Ex:
# foo.com fs bigpool /var/lib/redis-server
# bar.com both bigpool
# baz.com mysql dbpool
#
# type can be 'fs' for filesystem, 'mysql', or 'both'
#
# pool is to create sub-repositories for different hosts. restic can be a RAM hog
# if the indexes get large so it is sometimes a good idea to use multiple repos
#
# Excludes are additional filesystem paths for restic to ignore on the host
# - MadCamel, No Rights Reserved
use warnings;
use v5.10;
use strict;
use Parallel::ForkManager;
# Perform backups with restic. See bottom of file for mini-docs.
# Backup repository password.
my $pass = 'IlikeCheese';
# Allow 3 backups in parallel. Seems about right.
my $pm = Parallel::ForkManager->new(3);
# Repository path
my $rpath = "/backups/restic";
my $cnt=0;
while(<>) {
chomp;
next if (/^#/ || /^$/);
my ($host, $type, $pool, $excludes) = split(/\s+/);
die("config error - missing host") unless $host;
die("config error - missing type") unless $type;
die("config error - missing pool") unless $pool;
$excludes = "" unless $excludes;
my $port = 65300 + $cnt++;
# Copy our binary to the host for good measure
#system("scp /usr/local/bin/restic $host:/usr/local/bin/restic >/dev/null 2>&1");
# Init our pool if it doesn't exist
$pool = "$rpath/$pool";
unless (-f "$pool/config") {
say("Creating pool $pool");
mkdir($pool) or die("could not make pool dir: $pool");
system("RESTIC_PASSWORD=$pass restic -r $pool init");
}
$pm->start and next;
say "Backing up $host ($type) to $pool";
# We need a rest-server for this pool/port
my $srv_pid = spawn_srv($port, $pool);
die("Could not spawn rest-server on port $port for pool $pool") unless $srv_pid;
if ($type =~ /fs/ || $type =~ /both/) {
my $ret = backup_host_fs($host, $port, $excludes);
if ($ret != 0) {
say("$0 WARNING: error backing up $host(fs) .. please check!");
}
}
if ($type =~ /mysql/ || $type =~ /both/) {
my $ret = backup_host_mysql($host, $port);
if ($ret != 0) {
say("$0 WARNING: error backing up $host(mysql) .. please check!");
}
}
kill(15, $srv_pid); # Done with rest-server
exit if ($pm->is_child);
}
$pm->finish;
sub backup_host_fs {
my ($host, $port, $exclude) = @_;
$exclude = '/dev,/media,/mnt,/proc,/run,/sys,/tmp,/var/tmp,'.
'/var/lib/mysql,/home/mysql,/var/log/nginx,/swapfile,' . $exclude;
$exclude =~ s/,$//;
my $ret = sc(
"ssh -oStrictHostKeyChecking=no -T -R 127.0.0.1:$port:127.0.0.1:$port ".
"root\@$host ".
"'RESTIC_PASSWORD=$pass nice ionice -c3 /usr/local/bin/restic -r rest:http://127.0.0.1:$port/ backup --tag fs --host $host / ".
"--exclude={$exclude} --no-cache'"
);
}
sub backup_host_mysql {
my ($host, $port) = @_;
my $ret = sc(
"ssh -oStrictHostKeyChecking=no -T -R 127.0.0.1:$port:127.0.0.1:$port ".
"root\@$host ".
"'nice mysqldump --events --skip-lock-tables -A |RESTIC_PASSWORD=$pass nice /usr/local/bin/restic -r rest:http://127.0.0.1:$port/ ".
"backup --tag mysql --host $host --stdin --no-cache' "
);
return $ret;
}
# Shell call
sub sc {
system("@_ ");
return $? >> 8;
}
# Spawn rest-server, return it's pid so we can kill it when we're done
sub spawn_srv {
my ($port, $repo) = @_;
my $pid = fork();
unless ($pid) {
close(STDIN);
close(STDOUT);
close(STDERR);
exec('/usr/local/bin/rest-server', '--append-only', '--listen', "127.0.0.1:$port", '--path', $repo) or return;
exit;
}
return $pid;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment