diff --git a/backup-container b/backup-container index 421e4bb..ce46689 100755 --- a/backup-container +++ b/backup-container @@ -2,37 +2,27 @@ require 'json' require 'pathname' require 'fileutils' -require 'optparse' +require 'open3' -RSYNC_CMD="rsync" -RSYNC_ARGS=["--archive", "--delete", "--numeric-ids"] -ZFS_SNAPSHOT_CMD="zfs-auto-snapshot" -# --quiet --syslog --label=daily --keep=31 backup/rsync" LXC_PATH=Pathname.new("/data/containers") BACKUP_LOCATIONS = %w{home srv etc usr/local} CONFIG_PATH="/etc/lxc/container.json" - -BACKUP_PATH="/backup" -#ZFS_DATASET="backup/rsync" +DUPLICITY_PATH= Pathname.new("/data/duplicity/") +BACKUP_PATH="file:///mnt/backup/duplicity" +FULL_BACKUP_COUNT=2 def load_config - return JSON.load(File.open(CONFIG_PATH)) + return JSON.load(File.open(CONFIG_PATH)) rescue SystemCallError => e abort "failed to open configuration '#{CONFIG_PATH}', #{e}" rescue JSON::ParserError => e abort "failed to parse configuration '#{CONFIG_PATH}', #{e}" end -module Utils - def self.sh(cmd, *args) - pretty_args = args.map {|arg| "'#{arg}'"} - puts ([cmd] + pretty_args).join(" ") - system(cmd, *args) - end - - def self.backup(remote_path, local_path) - sh RSYNC_CMD, *RSYNC_ARGS, remote_path, local_path - end +def sh(cmd, env={}, *args) + pretty_args = args.map {|arg| "'#{arg}'"} + puts ([cmd] + pretty_args).join(" ") + system(env, cmd, *args) end class Container @@ -42,26 +32,22 @@ class Container @backup_scripts = backup_scripts @path = LXC_PATH.join(name, "rootfs") end - def backup - backup_paths = BACKUP_LOCATIONS + def backup_paths + paths = BACKUP_LOCATIONS if @backup_paths.is_a?(Array) - backup_paths += @backup_paths + paths += @backup_paths end - backup_paths.each do |relative_path| - backup_path = @path.join(relative_path) - local_path = Pathname.new(@name).join(relative_path) - FileUtils.mkdir_p(local_path) - - if File.exists?(backup_path) && !empty_directory?(backup_path) - Utils.backup(backup_path.to_s, File.dirname(local_path)) - end + paths.map do |relative_path| + @path.join(relative_path) end end def run_backup_scripts if @backup_scripts.is_a?(Array) - @backup_scripts.each do |script| + @backup_scripts.map do |script| backup_script(script) end + else + [] end end @@ -78,12 +64,13 @@ class Container if backupname.nil? abort("backupname not set for backup-scripts for container '#{@name}'") end - backupname = backupname.gsub("/", "") + backupname = DUPLICITY_PATH.join(backupname.gsub("/", "")) FileUtils.mkdir_p(backupname) puts "cd #{backupname}" Dir.chdir(backupname) do - Utils.sh(command) + sh(command) end + backupname end def empty_directory?(path) @@ -92,29 +79,53 @@ class Container end end -options = {} -OptionParser.new do |opts| - opts.on("-l", "--label [LABEL]", String, "daily, hourly") do |label| - options[:label] = label +if ARGV.size > 0 + if ["full", "incr"].include?(ARGV.first) + action = ARGV.first + else + $stderr.puts "action must be full or incr" + $stderr.puts "USAGE #{File.basename($0)} [full|incr]" + exit 1 end - opts.on("-k", "--keep [NUMBER]", Integer, "Number of backups to keep") do |keep| - options[:keep] = keep - end -end.parse! - -puts "cd #{BACKUP_PATH}" -Dir.chdir(BACKUP_PATH) do - BACKUP_LOCATIONS.each do |location| - FileUtils.mkdir_p(location) - Utils.backup("/" + location, "localhost") - end - config = load_config - config["network"].each do |container, data| - next if data["lxc"] == false - container = Container.new(container, data["backup-paths"], data["backup-scripts"]) - container.backup - container.run_backup_scripts - end - #args = ["--label", options[:label], "--keep", options[:keep].to_s, ZFS_DATASET] - #Utils.sh(ZFS_SNAPSHOT_CMD, *args) +else + action = "incr" +end + +Dir.chdir(DUPLICITY_PATH) do + config = load_config + backup_paths = BACKUP_LOCATIONS.map do |location| + "/#{location}" + end + config["network"].each do |container, data| + 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 + gpg_args = ["--gpg-options", "--secret-keyring ./duplicity.sec --keyring ./duplicity.pub"] + args = gpg_args + ["--verbosity", "notice", + "--encrypt-key", "AF5834A6", + "--sign-key", "AF5834A6", + "--full-if-older-than", "60D", + "--num-retries", "3", + "--asynchronous-upload", + "--volsize", "250", + "--include-globbing-filelist", "/dev/stdin", + "--archive-dir", "cache", + "--log-file", "/var/log/duplicity.log", + action, "/", BACKUP_PATH] + env = { "PASSPHRASE" => File.read("pgp-passphrase") } + stdin, stdout, stderr = Open3.popen3(env, "duplicity", *args) + if action == "full" + sh("duplicity", env, "remove-all-but-n-full", FULL_BACKUP_COUNT.to_s, BACKUP_PATH, *gpg_args) + end + backup_paths.each do |path| + stdin.puts("+ #{path}") + puts("+ #{path}") + end + stdin.puts("- **") + puts("- **") + stdin.close + puts stdout.read + puts stderr.read end