Compare commits

..

14 commits

Author SHA1 Message Date
cfba3f37c5 Version 0.5.0 -> 0.6.0 2021-03-06 16:14:40 +01:00
b0973f25d7 Make path to restic executable configurable 2021-03-06 16:13:34 +01:00
84824aae02 Replace config option cache-home with cache-dir 2021-03-05 14:56:29 +01:00
0d98802d8a Add support for specifying tags for backups 2021-03-04 09:32:18 +01:00
67058870e2 Version 0.4.0 -> 0.5.0 2020-09-14 20:56:53 +02:00
0f3797eb32 Add support for more restic flags 2020-09-14 20:55:35 +02:00
22e8c358c6 Don't print repository path
The full path may contain a password.
2020-09-14 20:54:42 +02:00
e4ddf36695 Add config option for cache home directory 2020-09-14 20:53:55 +02:00
43fff22a2a Delete .envrc 2020-09-14 20:49:46 +02:00
c1d760da2e Version 0.3.0 -> 0.4.0 2020-03-26 12:11:03 +01:00
dc53973745 Optionally read repository from file 2020-03-26 12:08:52 +01:00
9e3a9f27ff Move method definitions to the top 2020-03-25 23:42:57 +01:00
9a7ac16d9f Version 0.2.0 -> 0.3.0 2020-03-25 23:30:33 +01:00
c400af5ed4 Add option -r for executing restic
The new option -r allows executing any restic command with the
repository and password settings specified in the configuration file.
2020-03-25 23:27:00 +01:00
2 changed files with 108 additions and 77 deletions

1
.envrc
View file

@ -1 +0,0 @@
use nix

182
mpbackup
View file

@ -6,17 +6,27 @@ require 'yaml'
DEFAULT_CONFIG_FILE = Pathname.new('/etc/mpbackup/config.yaml') DEFAULT_CONFIG_FILE = Pathname.new('/etc/mpbackup/config.yaml')
NAME = 'mpbackup' NAME = 'mpbackup'
VERSION = '0.2.0' VERSION = '0.6.0'
def set_restic_vars(config) 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'] repo = config['repository']
puts "Repository: #{repo}" else
error(1, 'No repository has been specified.')
end
if config['password-file'] if config['password-file']
puts "Reading password from file #{config['password-file']} …" puts "Reading password from file #{config['password-file']} …"
password = File.open(config['password-file'], &:gets).chomp password = File.open(config['password-file'], &:gets).chomp
else else
password = STDIN.getpass('Please put in your restic password: ') password = STDIN.getpass('Please put in your restic password: ')
end 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_REPOSITORY'] = repo
ENV['RESTIC_PASSWORD'] = password ENV['RESTIC_PASSWORD'] = password
@ -25,6 +35,13 @@ end
def unset_restic_vars def unset_restic_vars
ENV.delete('RESTIC_REPOSITORY') ENV.delete('RESTIC_REPOSITORY')
ENV.delete('RESTIC_PASSWORD') 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 end
def error(exit_status, message) def error(exit_status, message)
@ -32,22 +49,98 @@ def error(exit_status, message)
exit exit_status exit exit_status
end 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 unset_restic_vars
options = {} options = {}
options[:config_names] = [] options[:config_names] = []
options[:prune] = false options[:prune] = false
options[:run_restic] = false
OptionParser.new do |parser| OptionParser.new do |parser|
parser.on("-c NAME", "--configuration-name NAME", "Name of the configuration to use") do |v| parser.on("-c NAME", "--configuration-name NAME", "Name of the configuration to use") do |v|
options[:config_names] << v options[:config_names] << v
end 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 options[:config_file] = v
end end
parser.on("-h", "--help", "Print this help") do parser.on("-h", "--help", "Print this help.") do
puts parser puts parser
exit exit
end end
@ -56,7 +149,14 @@ OptionParser.new do |parser|
options[:prune] = v options[:prune] = v
end end
parser.on('--version', "Print version number") do 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}" puts "#{NAME}, #{VERSION}"
exit exit
end end
@ -72,76 +172,13 @@ if !Pathname.new(config_file).exist? then
error(1, "Config file #{config_file} has not been found!") error(1, "Config file #{config_file} has not been found!")
end 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} …" puts "Using config file #{config_file} …"
configs = YAML.load_stream(File.open(config_file)) configs = YAML.load_stream(File.open(config_file))
config_names = options[:config_names] config_names = options[:config_names]
if config_names.empty? if config_names.empty?
puts "No configuration name has been given. Will use the first "\ puts "No configuration name has been given. Will use the first "\
"configuration from the file (#{configs.dig(0, 'name')})." "configuration from the file (#{configs.dig(0, 'name')})."
if options[:prune] act(configs[0], options)
prune configs[0]
else
backup configs[0]
end
else else
config_hash = configs.map {|c| [c['name'], c]}.to_h config_hash = configs.map {|c| [c['name'], c]}.to_h
config_names.each do |name| config_names.each do |name|
@ -150,11 +187,6 @@ else
end end
end end
config_names.each do |config_name| config_names.each do |config_name|
config = config_hash[config_name] act(config_hash[config_name], options)
if options[:prune]
prune config
else
backup config
end
end end
end end