diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/mpbackup b/mpbackup index 594526b..0060439 100644 --- a/mpbackup +++ b/mpbackup @@ -6,27 +6,17 @@ require 'yaml' DEFAULT_CONFIG_FILE = Pathname.new('/etc/mpbackup/config.yaml') NAME = 'mpbackup' -VERSION = '0.6.0' +VERSION = '0.2.0' def set_restic_vars(config) - if config['repository-file'] - puts "Reading repository from file #{config['repository-file']} …" - repo = File.open(config['repository-file'], &:gets).chomp - elsif config['repository'] - repo = config['repository'] - else - error(1, 'No repository has been specified.') - end + repo = config['repository'] + puts "Repository: #{repo}" if config['password-file'] puts "Reading password from file #{config['password-file']} …" password = File.open(config['password-file'], &:gets).chomp else password = STDIN.getpass('Please put in your restic password: ') end - if config['cache-dir'] - puts "Setting RESTIC_CACHE_DIR to #{config['cache-dir']} …" - ENV['RESTIC_CACHE_DIR'] = config['cache-dir'] - end ENV['RESTIC_REPOSITORY'] = repo ENV['RESTIC_PASSWORD'] = password @@ -35,13 +25,6 @@ end def unset_restic_vars ENV.delete('RESTIC_REPOSITORY') ENV.delete('RESTIC_PASSWORD') - ENV.delete('RESTIC_CACHE_DIR') -end - -def set_restic_path(config) - if !config.key? 'restic-path' - config['restic-path'] = 'restic' - end end def error(exit_status, message) @@ -49,98 +32,22 @@ def error(exit_status, message) exit exit_status end -def check(config) - puts 'Checking restic repo …' - check_command = [config['restic-path'], 'check'] - puts("Command: #{check_command.join(' ')}") - system(*check_command) - if $?.exitstatus > 0 - error(1, "Checking restic repository failed.") - end -end - -def backup(config) - puts "Backing up with restic …" - # https://restic.readthedocs.io/en/latest/040_backup.html#including-and-excluding-files - flags = config['backup'].select{|k,v| - k != 'exclude' && k != 'paths' && k != 'tags'} - flags = flags.map{|k,v| "--#{k}=#{v.to_s}"} - exclude = config.dig('backup', 'exclude')&.flat_map{|e| ['--exclude', e]} || [] - tags = config.dig('backup', 'tags')&.flat_map{|t| ['--tag', t]} || [] - paths = config.dig('backup', 'paths') || [] - backup_command = [config['restic-path'], 'backup', *exclude, *paths, *flags] - puts("Command: #{backup_command.join(' ')}") - system(*backup_command) - if $?.exitstatus > 0 - error(1, 'Failed to do backup.') - end - - check(config) - - if config.dig('forget', 'enable') - puts 'Forgetting unnecessary snapshots …' - flags = config['forget'].select{|k,v| k != 'enable'} - flags = flags.flat_map{|k,v| ['--' + k, v.to_s]} - forget_command = [config['restic-path'], 'forget'] + flags - puts("Command: #{forget_command.join(' ')}") - system(*forget_command) - # Data will only be deleted when `restic prune` is executed or when - # `restic forget` is called with `--prune`. - if $?.exitstatus > 0 - error(1, "Forgetting snapshots failed.") - end - end -end - -def prune(config) - puts 'Pruning restic repo …' - prune_command = [config['restic-path'], 'prune'] - puts("Command: #{prune_command.join(' ')}") - system(*prune_command) - if $?.exitstatus > 0 - error(1, 'Failed to prune.') - end - - check -end - -def run_restic(config) - puts 'Executing restic with the following arguments …' - puts "ARGV: #{ARGV}" - exec(config['restic-path'], *ARGV) -end - -def act(config, options) - puts "Applying configuration ‘#{config['name']}’ …" - set_restic_vars(config) - set_restic_path(config) - if options[:prune] - prune config - elsif options[:run_restic] - run_restic config - else - backup config - end - unset_restic_vars -end - unset_restic_vars options = {} options[:config_names] = [] options[:prune] = false -options[:run_restic] = false OptionParser.new do |parser| parser.on("-c NAME", "--configuration-name NAME", "Name of the configuration to use") do |v| options[:config_names] << v end - parser.on("-f CONFIG_FILE", "--config-file CONFIG_FILE", "Path to the configuration file.") do |v| + parser.on("-f CONFIG_FILE", "--config-file CONFIG_FILE", "Path to the configuration file") do |v| options[:config_file] = v end - parser.on("-h", "--help", "Print this help.") do + parser.on("-h", "--help", "Print this help") do puts parser exit end @@ -149,14 +56,7 @@ OptionParser.new do |parser| options[:prune] = v end - parser.on('-r', '--run-restic', 'Set environment variables '\ - 'RESTIC_REPOSITORY and RESTIC_PASSWORD, execute restic (while passing '\ - 'all following command line arguments to restic).') do |v| - options[:run_restic] = v - parser.terminate - end - - parser.on('--version', "Print version number.") do + parser.on('--version', "Print version number") do puts "#{NAME}, #{VERSION}" exit end @@ -172,13 +72,76 @@ if !Pathname.new(config_file).exist? then error(1, "Config file #{config_file} has not been found!") end +def check + puts 'Checking restic repo …' + check_command = ['restic', 'check'] + puts("Command: #{check_command.join(' ')}") + system(*check_command) + if $?.exitstatus > 0 + error(1, "Checking restic repository #{ENV['RESTIC_REPOSITORY']} failed.") + end +end + +def backup(config) + puts "Applying configuration ‘#{config['name']}’ …" + set_restic_vars(config) + + puts "Backing up with restic …" + # https://restic.readthedocs.io/en/latest/040_backup.html#including-and-excluding-files + exclude = config.dig('backup', 'exclude')&.flat_map{|e| ['--exclude', e]} || [] + paths = config.dig('backup', 'paths') || [] + backup_command = ['restic', 'backup', *exclude, *paths] + puts("Command: #{backup_command.join(' ')}") + system(*backup_command) + if $?.exitstatus > 0 + error(1, 'Failed to do backup.') + end + + check + + if config.dig('forget', 'enable') + puts 'Forgetting unnecessary snapshots …' + flags = config['forget'].select{|k,v| k != 'enable'} + flags = flags.flat_map{|k,v| ['--' + k, v.to_s]} + forget_command = ['restic', 'forget'] + flags + puts("Command: #{forget_command.join(' ')}") + system(*forget_command) + # Data will only be deleted when `restic prune` is executed or when + # `restic forget` is called with `--prune`. + if $?.exitstatus > 0 + error(1, "Forgetting snapshots failed.") + end + end + + unset_restic_vars +end + +def prune(config) + puts "Applying configuration ‘#{config['name']}’ …" + set_restic_vars(config) + + puts 'Pruning restic repo …' + prune_command = ['restic', 'prune'] + puts("Command: #{prune_command.join(' ')}") + system(*prune_command) + if $?.exitstatus > 0 + error(1, 'Failed to prune.') + end + + check +end + puts "Using config file #{config_file} …" configs = YAML.load_stream(File.open(config_file)) config_names = options[:config_names] if config_names.empty? puts "No configuration name has been given. Will use the first "\ "configuration from the file (‘#{configs.dig(0, 'name')}’)." - act(configs[0], options) + if options[:prune] + prune configs[0] + else + backup configs[0] + end else config_hash = configs.map {|c| [c['name'], c]}.to_h config_names.each do |name| @@ -187,6 +150,11 @@ else end end config_names.each do |config_name| - act(config_hash[config_name], options) + config = config_hash[config_name] + if options[:prune] + prune config + else + backup config + end end end