#!/usr/bin/env ruby require 'json' require 'pathname' require 'fileutils' require 'open3' LXC_PATH = Pathname.new("/data/containers") BACKUP_LOCATIONS = %w{home srv etc usr/local} CONFIG_PATH = "/etc/lxc/container.json" BACKUP_PATH = "/mnt/backup/attic" ATTIC_PATH = Pathname.new("/data/attic") PASSWORD_FILE = ATTIC_PATH.join("passwordfile").to_s KEEP_DAILY = 7 KEEP_WEEKLY = 4 KEEP_MONTHLY = 0 def load_config 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 def sh(cmd, env={}, *args) pretty_args = args.map {|arg| "'#{arg}'"} puts ([cmd] + pretty_args).join(" ") system(env, cmd, *args) 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 def backup_paths paths = BACKUP_LOCATIONS if @backup_paths.is_a?(Array) paths += @backup_paths end paths.map do |relative_path| @path.join(relative_path) end end def run_backup_scripts if @backup_scripts.is_a?(Array) @backup_scripts.map do |script| backup_script(script) end else [] 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 backupname = ATTIC_PATH.join(backupname.gsub("/", "")) FileUtils.mkdir_p(backupname) puts "cd #{backupname}" Dir.chdir(backupname) do sh(command) end backupname end def empty_directory?(path) return false unless Dir.exists?(path) return Dir.entries(path).size <= 2 # - [".", ".."] end end 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 env = { "ATTIC_PASSPHRASE" => File.read(PASSWORD_FILE).chomp } now = Time.now.strftime("%Y-%m-%d-%H:%M:%S") paths = backup_paths.map {|path| path.to_s } sh("attic", env, "create", "--stats", "#{BACKUP_PATH}::eve-#{now}", '--exclude', '*/srv/repo', '--exclude', '*/home/joerg/git', '--exclude', '*/home/joerg/login/git', *paths) sh("attic", env, "prune", "-v", BACKUP_PATH, "--keep-daily", KEEP_DAILY.to_s, "--keep-weekly", KEEP_WEEKLY.to_s, "--keep-monthly", KEEP_MONTHLY.to_s)