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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
#!/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.exists?("/etc/byteback/destination")
@ssh_key = "/etc/byteback/key" if File.exists?("/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 = [$1, $2, $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)
|