diff options
author | Patrick J Cherry <patrick@bytemark.co.uk> | 2015-12-01 22:41:06 +0000 |
---|---|---|
committer | Patrick J Cherry <patrick@bytemark.co.uk> | 2015-12-01 22:41:06 +0000 |
commit | 668b9871e64cb82ac30c8defb29d56d774f3c140 (patch) | |
tree | 9b4bba7cbcbd2660485cf803402c4d4889e62b10 /bin | |
parent | 182a03798d49a3c0450b0f137977037cf9376e99 (diff) |
Completely re-vamped restore command. Fixes #12403
The byteback-restore command now uses the rsync xattrs to display
information about the files due to be restored. It can handle filenames
with spaces (!) and other characters.
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/byteback-receive | 57 | ||||
-rwxr-xr-x | bin/byteback-restore | 193 |
2 files changed, 169 insertions, 81 deletions
diff --git a/bin/byteback-receive b/bin/byteback-receive index 5d0d35a..b415f0e 100755 --- a/bin/byteback-receive +++ b/bin/byteback-receive @@ -1,4 +1,5 @@ #!/usr/bin/ruby +# encoding: UTF-8 # # Program to receive backups and run rsync in receive mode. Must check that # user as authorised by SSH is allowed to access particular directory. @@ -7,6 +8,8 @@ $LOAD_PATH << '/usr/lib/byteback' require 'trollop' require 'byteback' +require 'byteback/restore' + include Byteback::Log if ENV['SSH_ORIGINAL_COMMAND'] @@ -22,28 +25,51 @@ fatal("#{byteback_root} does not exist") unless File.directory?(byteback_root) # # Force restores to be limited to the hostname we're connecting form # -if (ARGV[0] == 'restore') - ARGV[0] = 'rsync' - a = [] - ARGV.each do |tmp| - if tmp =~ /^\/(.*)/ - tmp = "#{byteback_host}/#{Regexp.last_match(1).dup}" +if (ARGV[0] == 'byteback-restore') + args = ["rsync"] + revision = nil + + while((arg = ARGV.shift) != ".") + break if arg.nil? + + verbose = arg if arg == "--verbose" + + if arg == "--revision" + revision = ARGV.shift + else + args << arg end - a.push(tmp) end - exec(*a) + + restore = Byteback::Restore.new(byteback_root) + restore.revision = revision if revision + restore.find(Byteback::Restore.decode_args(ARGV)) + + Dir.chdir(byteback_host) + + restore.results.each do |r| + args << File.join(".", r.snapshot, r.path) + end + + info(args.join(" ")) + exec(*args) + elsif ARGV[0] == 'rsync' ARGV[-1] = "#{byteback_root}/current" exec(*ARGV) + elsif ARGV[0] == 'byteback-snapshot' ARGV.concat(['--root', "#{byteback_root}"]) exec(*ARGV) + end opts = Trollop.options do opt :verbose, 'Print diagnostics' opt :ping, 'Check connection parameters and exit' - opt :list, 'Show backed up files matching the given pattern', :type => :string + opt :list, 'Show backed up files matching the given pattern' + opt :list_all, 'Show all stored versions of a file' + opt :revision, 'Show backed up files in a certain revision.', :default => '*' opt :restore, 'Perform a restoration operation', :type => :string opt :complete, 'Mark current backup as complete' end @@ -52,7 +78,18 @@ error('Please only choose one mode') if opts[:ping] && opts[:complete] if opts[:complete] system('byteback-snapshot', '--root', byteback_root) elsif opts[:list] - system("cd #{byteback_root} && find . -print | grep #{opts[:list]}") + args = Byteback::Restore.decode_args(ARGV[1..-1]) + + restore = Byteback::Restore.new(byteback_root) + restore.revision = opts[:revision] + restore.find(args, :all => opts[:list_all], :verbose => opts[:verbose]) + + if restore.results.empty? + puts "** Sorry. There were no files matching:" + puts "--> "+args.join("\n--> ") + else + puts restore.list + end exit(0) elsif opts[:ping] exit 0 diff --git a/bin/byteback-restore b/bin/byteback-restore index 7ac47dd..0a068a8 100755 --- a/bin/byteback-restore +++ b/bin/byteback-restore @@ -1,19 +1,19 @@ #!/usr/bin/ruby +# enocoding: UTF-8 # # Restore a file from the most recent backup, from the remote host. # $LOAD_PATH.unshift('/usr/lib/byteback') -$LOAD_PATH.unshift('./lib/') +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../lib')) # For development require 'trollop' require 'byteback/util' require 'byteback/log' +require 'byteback/restore' include Byteback::Util include Byteback::Log - - # # Run a remote command. # @@ -31,11 +31,16 @@ def ssh(*ssh_args) ] + ssh_args.map { |a| a ? a : '' } + puts args.join(" " ) if @verbose system(*args) end -def list_files(pattern) - ssh('byteback-receive', '--list', pattern) +def list_files(revision, list_all, pattern) + args = ['byteback-receive', '--revision', revision, '--list'] + args << "--list-all" if list_all + args << @verbose if @verbose + args += Byteback::Restore.encode_args(pattern) + ssh(*args) end # @@ -46,77 +51,123 @@ end # do that by setting "rsync-path" to point to a faux script. # # -def restore_file(path, revision) - cmd = %w( rsync ) - cmd += ['--rsh', 'ssh -o BatchMode=yes -x -a -i /etc/byteback/key -l byteback'] - cmd += ['--rsync-path', 'restore --fake-super'] - cmd += ['-aApzrX', '--numeric-ids'] - cmd += ["#{@destination_host}:/#{revision}/#{path}", '.'] - system(*cmd) -end - -# -# Parse our command-line arguments -# -opts = Trollop.options do - banner "byteback-restore: Restore a file\n " - - opt :file, 'The file to restore/list.', - :type => :string +def restore_files(paths, revision) - opt :revision, "The version of the file to restore.", - :type => :string - - opt :destination, 'Backup destination (i.e. user@host:/path).', - :type => :string - - opt :ssh_key, 'SSH key filename', - :type => :string, - :default => '/etc/byteback/key', - :short => 'k' -end - -# -# Setup default destination and key. -# -@destination = File.read('/etc/byteback/destination').chomp if - File.exist?('/etc/byteback/destination') -@ssh_key = '/etc/byteback/key' if File.exist?('/etc/byteback/key') + # + # Basic args + # + args = %w(rsync --archive --acls --numeric-ids --inplace --relative --xattrs --compress --no-implied-dirs) -# -# Allow the command-line to override them. -# -@ssh_key = opts[:ssh_key] unless opts[:ssh_key].nil? -@destination = opts[:destination] unless opts[:destination].nil? + # + # Add on the I/O-timeout + # + args += ['--timeout', @io_timeout.to_s ] unless ( @io_timeout.nil? ) + args += ['--rsh', "ssh -o BatchMode=yes -x -a -i #{@ssh_key} -l #{@destination_user}"] + args << '--verbose' if @verbose + args += ['--rsync-path', "byteback-restore --fake-super --revision #{revision}"] + dst = "#{@destination_user}@#{@destination_host}:" -# -# Check our destination is well-formed -# -if @destination =~ /^(?:(.+)@)?([^@:]+):(.+)?$/ - @destination_user, @destination_host, @destination_path = [Regexp.last_match(1), Regexp.last_match(2), Regexp.last_match(3)] -else - fatal('Destination must be a remote path, e.g. ssh@host.com:/store/backups') -end + paths.each do |path| + path = Byteback::Restore.encode_args(path).first + args << File.join(dst,path) + dst = ":" + end -# -# If the user didn't specify a file then we're not restoring anything, -# and we should abort. -# -if opts[:file].nil? - fatal('You must specify a file to search/restore') + args << "." + puts args.join(" " ) if @verbose + system(*args) end -# -# If the user specified a file, but not a revision, then we list -# the available revisions. -# -if opts[:revision].nil? - list_files(opts[:file]) +## +## Entry-point to our code. +## +if __FILE__ == $PROGRAM_NAME + + ME = File.basename($PROGRAM_NAME) + + # + # Parse our command-line arguments + # + opts = Trollop.options do + banner "#{ME}: Restore a file to this system from a byteback-enabled server\n " + + opt :restore, "Restore the files" + + opt :revision, "The version of the file to restore.", + :type => :string, :default => "*" + + opt :destination, 'Backup destination (i.e. user@host:/path).', + :type => :string + + opt :verbose, 'Show debugging messages' + + opt :io_timeout, 'Number of seconds to allow I/O timeout for', + :type => :integer, + :default => 10800 + + opt :ssh_key, 'SSH key filename', + :type => :string, + :default => '/etc/byteback/key', + :short => 'k' + + opt :list_all, 'List all versrions of each file' + + end + + @verbose = opts[:verbose] ? '--verbose' : nil + @io_timeout = opts[:io_timeout] if opts[:io_timeout] + + # Read the default destination + if File.exist?('/etc/byteback/destination') + @destination = File.read('/etc/byteback/destination').chomp + end + + # Set the default SSH key + if File.exist?('/etc/byteback/key') + @ssh_key = '/etc/byteback/key' + end + + # + # Allow the command-line to override them. + # + @ssh_key = opts[:ssh_key] unless opts[:ssh_key].nil? + @destination = opts[:destination] unless opts[:destination].nil? + + # + # Check our destination + # + fatal('Must suply --destination or put it into /etc/bytebackup/destination') unless @destination + + # + # Check our destination is well-formed + # + if @destination =~ /^(?:(.+)@)?([^@:]+):(.+)?$/ + @destination_user, @destination_host, @destination_path = [Regexp.last_match(1), Regexp.last_match(2), Regexp.last_match(3)] + else + fatal('Destination must be a remote path, e.g. ssh@host.com:/store/backups') + end + + # + # Test that we have an SSH-key which we can read. + # + fatal("Could not read ssh key #{@ssh_key}") unless File.readable?(@ssh_key) + + # + # If the user didn't specify a file then we're not restoring anything, + # and we should abort. + # + if ARGV.empty? + fatal('You must specify a file to search/restore') + end + + if opts[:restore] + # + # Restore a file + # + restore_files(ARGV.collect{|a| File.expand_path(a)}, opts[:revision]) + exit(0) + end + + list_files(opts[:revision], opts[:list_all], ARGV.collect{|a| File.expand_path(a)}) exit(0) end - -# -# Restore a file -# -restore_file(opts[:file], opts[:revision]) -exit(0) |