#!/usr/bin/ruby # encoding: UTF-8 # # Restore a file from the most recent backup, from the remote host. # $LOAD_PATH.unshift('/usr/lib/byteback') $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. # def ssh(*ssh_args) args = ['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectionAttempts=5', '-o', 'ConnectTimeout=30', '-o', 'ServerAliveInterval=60', '-o', 'TCPKeepAlive=yes', '-x', '-a', '-i', @ssh_key, '-l', @destination_user, @destination_host ] + ssh_args.map { |a| a ? a : '' } puts args.join(" " ) if @verbose system(*args) end def list_files(pattern, snapshot, all) args = ['byteback-receive', '--snapshot', snapshot, '--list'] args << "--all" if all args << @verbose if @verbose args += Byteback::Restore.encode_args(pattern) ssh(*args) end # # We cannot use plain 'rsync' here because the receiving-command will # see that, and rewrite our arguments. # # To cater to this we have to wrap the rsync for the restore and we # do that by setting "rsync-path" to point to a faux script. # # def restore_files(paths, snapshot, all) # # Basic args # args = %w(rsync --archive --numeric-ids --inplace --relative --compress) # # 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 --snapshot #{snapshot}" + (all ? " --all" : "")] # # To add extra rsync flags, a file can be used. This can have flags all on one line, or one per line. # if File.exists?("/etc/byteback/rsync_flags") args += File.readlines("/etc/byteback/rsync_flags").map(&:chomp) end dst = "#{@destination_user}@#{@destination_host}:" paths.each do |path| path = Byteback::Restore.encode_args(path).first args << File.join(dst,path) dst = ":" end args << "." puts args.join(" ") if @verbose system(*args) end ## ## 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 :list, "List or find files on the backup server. This is the default mode." opt :restore, "Restore files from the backup server." opt :snapshot, "The specific snapshot to use", :type => :string, :default => "*" opt :destination, 'Backup destination (i.e. user@host:/path)', :type => :string opt :io_timeout, 'Number of seconds to allow I/O to timeout', :type => :integer, :default => 300 opt :ssh_key, 'SSH key filename', :type => :string, :default => '/etc/byteback/key', :short => 'k' opt :all, 'List or restore all versrions of each file', :short => 'a' opt :verbose, 'Show more output' end # # Make sure we know what we're doing. # fatal("Please choose either --list or --restore") if opts[:list] and opts[:restore] @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[:snapshot], opts[:all]) exit(0) end list_files(ARGV.collect{|a| File.expand_path(a)}, opts[:snapshot], opts[:all]) exit(0) end