2014-10-19 15:27:33 +00:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
require 'json'
|
|
|
|
require 'pathname'
|
|
|
|
require 'fileutils'
|
2014-10-21 17:36:41 +00:00
|
|
|
require 'open3'
|
2014-10-19 15:27:33 +00:00
|
|
|
|
|
|
|
LXC_PATH=Pathname.new("/data/containers")
|
|
|
|
BACKUP_LOCATIONS = %w{home srv etc usr/local}
|
|
|
|
CONFIG_PATH="/etc/lxc/container.json"
|
2014-10-21 17:36:41 +00:00
|
|
|
DUPLICITY_PATH= Pathname.new("/data/duplicity/")
|
|
|
|
BACKUP_PATH="file:///mnt/backup/duplicity"
|
2014-11-02 15:52:35 +00:00
|
|
|
FULL_BACKUP_COUNT=1
|
2014-10-19 15:27:33 +00:00
|
|
|
|
|
|
|
def load_config
|
2014-10-21 17:36:41 +00:00
|
|
|
return JSON.load(File.open(CONFIG_PATH))
|
2014-10-19 15:27:33 +00:00
|
|
|
rescue SystemCallError => e
|
|
|
|
abort "failed to open configuration '#{CONFIG_PATH}', #{e}"
|
|
|
|
rescue JSON::ParserError => e
|
|
|
|
abort "failed to parse configuration '#{CONFIG_PATH}', #{e}"
|
|
|
|
end
|
|
|
|
|
2014-10-21 17:36:41 +00:00
|
|
|
def sh(cmd, env={}, *args)
|
|
|
|
pretty_args = args.map {|arg| "'#{arg}'"}
|
|
|
|
puts ([cmd] + pretty_args).join(" ")
|
|
|
|
system(env, cmd, *args)
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class Container
|
|
|
|
def initialize(name, backup_paths, backup_scripts)
|
|
|
|
@name = name
|
|
|
|
@backup_paths = backup_paths
|
|
|
|
@backup_scripts = backup_scripts
|
|
|
|
@path = LXC_PATH.join(name, "rootfs")
|
|
|
|
end
|
2014-10-21 17:36:41 +00:00
|
|
|
def backup_paths
|
|
|
|
paths = BACKUP_LOCATIONS
|
2014-10-19 15:27:33 +00:00
|
|
|
if @backup_paths.is_a?(Array)
|
2014-10-21 17:36:41 +00:00
|
|
|
paths += @backup_paths
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
2014-10-21 17:36:41 +00:00
|
|
|
paths.map do |relative_path|
|
|
|
|
@path.join(relative_path)
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
def run_backup_scripts
|
|
|
|
if @backup_scripts.is_a?(Array)
|
2014-10-21 17:36:41 +00:00
|
|
|
@backup_scripts.map do |script|
|
2014-10-19 15:27:33 +00:00
|
|
|
backup_script(script)
|
|
|
|
end
|
2014-10-21 17:36:41 +00:00
|
|
|
else
|
|
|
|
[]
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def backup_script(script)
|
|
|
|
unless script.is_a?(Hash)
|
|
|
|
abort("backup-scripts: Expected an Object, got #{script.class}")
|
|
|
|
end
|
|
|
|
command = script["command"]
|
|
|
|
if command.nil?
|
|
|
|
abort("command not set for backup-scripts for container '#{@name}'")
|
|
|
|
end
|
|
|
|
backupname = script["backupname"]
|
|
|
|
if backupname.nil?
|
|
|
|
abort("backupname not set for backup-scripts for container '#{@name}'")
|
|
|
|
end
|
2014-10-21 17:36:41 +00:00
|
|
|
backupname = DUPLICITY_PATH.join(backupname.gsub("/", ""))
|
2014-10-19 15:27:33 +00:00
|
|
|
FileUtils.mkdir_p(backupname)
|
|
|
|
puts "cd #{backupname}"
|
|
|
|
Dir.chdir(backupname) do
|
2014-10-21 17:36:41 +00:00
|
|
|
sh(command)
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
2014-10-21 17:36:41 +00:00
|
|
|
backupname
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def empty_directory?(path)
|
|
|
|
return false unless Dir.exists?(path)
|
|
|
|
return Dir.entries(path).size <= 2 # - [".", ".."]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-10-21 17:36:41 +00:00
|
|
|
Dir.chdir(DUPLICITY_PATH) do
|
2014-10-19 15:27:33 +00:00
|
|
|
config = load_config
|
2014-10-21 17:36:41 +00:00
|
|
|
backup_paths = BACKUP_LOCATIONS.map do |location|
|
|
|
|
"/#{location}"
|
|
|
|
end
|
2014-10-19 15:27:33 +00:00
|
|
|
config["network"].each do |container, data|
|
2014-10-21 17:36:41 +00:00
|
|
|
next if data["lxc"] == false
|
|
|
|
container = Container.new(container, data["backup-paths"], data["backup-scripts"])
|
|
|
|
backup_paths += container.backup_paths
|
|
|
|
backup_paths += container.run_backup_scripts
|
|
|
|
end
|
2014-11-02 15:52:35 +00:00
|
|
|
gpg_args = ["--sign-key", "AF5834A6", "--encrypt-key", "AF5834A6", "--gpg-options", "--secret-keyring ./duplicity.sec --keyring ./duplicity.pub"]
|
|
|
|
args = ["--archive-dir", "cache", "--log-file", "/var/log/duplicity.log"] + gpg_args
|
|
|
|
backup_args = args + ["--verbosity", "notice",
|
|
|
|
"--full-if-older-than", "30D",
|
2014-10-21 17:36:41 +00:00
|
|
|
"--num-retries", "3",
|
|
|
|
"--asynchronous-upload",
|
|
|
|
"--volsize", "250",
|
2014-11-02 15:52:35 +00:00
|
|
|
"--include-globbing-filelist", "/dev/stdin", "--exclude", "**",
|
|
|
|
"/", BACKUP_PATH]
|
2014-10-21 17:36:41 +00:00
|
|
|
env = { "PASSPHRASE" => File.read("pgp-passphrase") }
|
2014-11-02 15:52:35 +00:00
|
|
|
sh("find", "cache", "-type", "f", "-name", "lockfile.lock", "-exec", "rm -f {} ;")
|
|
|
|
sh("duplicity", env, "cleanup", "--force", BACKUP_PATH, *args)
|
|
|
|
stdin, stdout, stderr = Open3.popen3(env, "duplicity", *backup_args)
|
2014-10-21 17:36:41 +00:00
|
|
|
backup_paths.each do |path|
|
2014-11-02 15:52:35 +00:00
|
|
|
stdin.puts(path)
|
|
|
|
puts(path)
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|
2014-10-21 17:36:41 +00:00
|
|
|
stdin.close
|
|
|
|
puts stdout.read
|
|
|
|
puts stderr.read
|
2014-11-02 15:52:35 +00:00
|
|
|
sh("duplicity", env, "remove-older-than", "30D", "--force", BACKUP_PATH, *args)
|
|
|
|
sh("duplicity", env, "remove-all-inc-of-but-n-full", FULL_BACKUP_COUNT.to_s, "--force", BACKUP_PATH, *args)
|
2014-10-19 15:27:33 +00:00
|
|
|
end
|