diff options
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) |