mpbackup/mpbackup

192 lines
5.2 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env ruby
require 'io/console'
require 'optparse'
require 'pathname'
require 'yaml'
DEFAULT_CONFIG_FILE = Pathname.new('/etc/mpbackup/config.yaml')
NAME = 'mpbackup'
VERSION = '0.6.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
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
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)
STDERR.puts("Error: #{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|
options[:config_file] = v
end
parser.on("-h", "--help", "Print this help.") do
puts parser
exit
end
parser.on('-p', '--prune', 'Prunes the repository. Does NOT do anything else.') do |v|
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
puts "#{NAME}, #{VERSION}"
exit
end
end.parse!
if options[:config_file]
config_file = Pathname.new(options[:config_file])
else
config_file = DEFAULT_CONFIG_FILE
end
if !Pathname.new(config_file).exist? then
error(1, "Config file #{config_file} has not been found!")
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)
else
config_hash = configs.map {|c| [c['name'], c]}.to_h
config_names.each do |name|
if !config_hash.key? name
error(1, "Cannot find configuration #{name}.")
end
end
config_names.each do |config_name|
act(config_hash[config_name], options)
end
end