summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2015-12-01 22:41:06 +0000
committerPatrick J Cherry <patrick@bytemark.co.uk>2015-12-01 22:41:06 +0000
commit668b9871e64cb82ac30c8defb29d56d774f3c140 (patch)
tree9b4bba7cbcbd2660485cf803402c4d4889e62b10 /bin
parent182a03798d49a3c0450b0f137977037cf9376e99 (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-xbin/byteback-receive57
-rwxr-xr-xbin/byteback-restore193
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)