summaryrefslogtreecommitdiff
path: root/byteback-restore
blob: 48eea9a945cc40d782ffd06055a8d44b115f0e65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/ruby
#
# Restore a file from the most recent backup, from the remote host.
#

$LOAD_PATH.unshift('/usr/lib/byteback')
$LOAD_PATH.unshift('./lib/')

require 'trollop'

#
#  Show an error message and abort.
#
def fatal(str)
  STDERR.puts(str)
  exit(1)
end

#
#  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 : '' }

  system(*args)
end

def list_files(pattern)
  ssh('byteback-receive', '--list', pattern)
end

#
#  We cannot use plain 'rsync' here because the receiver 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 the receiver program.
#
#
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',
      type: :string

  opt :revision, "The version of the file to restore - default is 'latest'",
      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')

#
#  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 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

#
#  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 restore')
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])
  exit(0)
end

#
#  Restore a file
#
restore_file(opts[:file], opts[:revision])
exit(0)