Created
December 20, 2018 19:06
-
-
Save MadCamel/3970ba751d7dee39b6cc892c6eda06f4 to your computer and use it in GitHub Desktop.
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 | |
# 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