summaryrefslogtreecommitdiff
path: root/byteback-backup
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2014-05-09 11:53:50 +0100
committerPatrick J Cherry <patrick@bytemark.co.uk>2014-05-09 11:53:50 +0100
commiteac2dadf65c9e94363647e2f42634d7ac956a5a9 (patch)
treed398adcf660a8990661dcb84969f3e9d06d57420 /byteback-backup
parent804a312b41fb5ca68da687f406abff506f7fe226 (diff)
byteback-backup: Adjusted not to use trollop. Also sources and excludes can be specified in /etc/byteback/sources and excludes.
Diffstat (limited to 'byteback-backup')
-rwxr-xr-xbyteback-backup286
1 files changed, 160 insertions, 126 deletions
diff --git a/byteback-backup b/byteback-backup
index 7494ad7..22fe0fb 100755
--- a/byteback-backup
+++ b/byteback-backup
@@ -1,182 +1,216 @@
#!/usr/bin/ruby
#
# Back up this system to a byteback-enabled server (just some command line
-# tools and SSH setup). We aim to make sure this backups are easy, complete
+# tools and SSH setup). We aim to make sure this backups are easy, complete
# and safe for most types of hosting customer.
#
# See 'man byteback' for more information.
-require 'trollop'
+require 'getoptlong'
require 'resolv'
-@sources = ["/"]
-@exclude = ["/swap.file", "/var/backups/localhost"]
def error(message)
- STDERR.print "*** #{message}\n"
- exit 1
+ STDERR.print "*** #{message}\n"
+ exit 1
end
def verbose(message)
- print "#{message}\n"
+ print "#{message}\n"
end
-opts = Trollop::options do
-
- opt :destination, "Backup destination (i.e. user@host:/path)",
- :type => :string
-
- opt :source, "Source paths",
- :type => :strings
+def help
+ puts <<EOF
+#{$0}: Back up this system to a byteback-enabled server
+
+Options:
+ --destination, -d <s>: Backup destination (i.e. user@host:/path)
+ --source, -s <s>: Source paths (defaults: / and /boot)
+ --exclude, -x <s>: Exclude paths (defaults: /swap.file, /var/backups/localhost, /var/cache)
+ --verbose, -v: Show rsync command and progress
+ --retry-number, -r <n>: Number of retries on error (default: 3)
+ --retry-delay, -e <n>: Wait number of seconds between retries (default: 1800)
+ --ssh-key, -k <s>: SSH key for connection (default: /etc/byteback/key)
+ --help, -h: Show this message
+EOF
+ exit 0
+end
- opt :verbose, "Show rsync command and progress"
- opt :retry_number, "Number of retries on error",
- :type => :integer,
- :default => 3
+opts = GetoptLong.new(
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
+ [ '--source', '-s', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--destination', '-d', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--retry-number', '-r', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--retry-delay', '-e', GetoptLong::REQUIRED_ARGUMENT ],
+ [ '--ssh-key' ,'-k', GetoptLong::REQUIRED_ARGUMENT ]
+)
+
+@ssh_key = nil
+@destination = nil
+@retry_number = 3
+@retry_delay = 1800
+@sources = nil
+@excludes = nil
+
+# Read the default destination
+if File.exists?("/etc/byteback/destination")
+ @destination = File.read("/etc/byteback/destination").chomp
+end
- opt :retry_delay, "Wait number of seconds between retries",
- :type => :integer,
- :default => 1800
+# Set the default SSH key
+if File.exists?("/etc/byteback/key")
+ @ssh_key = "/etc/byteback/key"
+end
- opt :ssh_key, "SSH key for connection",
- :type => :string,
- :default => "/etc/byteback/key"
+# Read in the default sources
+if File.exists?("/etc/byteback/sources")
+ @sources = File.readlines("/etc/byteback/sources").map{|m| m.chomp}
end
-@ssh_key = opts[:ssh_key]
-@verbose = opts[:verbose] ? "--verbose" : nil
-@sources = opts[:source] if opts[:source]
-@destination = opts[:destination]
-@retry_number = opts[:retry_number]
-@retry_delay = opts[:retry_delay]
+# Read in the default excludes
+if File.exists?("/etc/byteback/excludes")
+ @excludes = File.readlines("/etc/byteback/excludes").map{|m| m.chomp}
+end
-if !@destination && File.exists?("/etc/byteback/destination")
- @destination = File.read("/etc/byteback/destination").chomp
+begin
+ opts.each do |opt,arg|
+ case opt
+ when '--help'
+ help = true
+ when '--verbose'
+ $VERBOSE = true
+ when "--source"
+ @sources ||= []
+ @sources << arg
+ when "--exclude"
+ @excludes ||= []
+ @excludes << arg
+ when "--destination"
+ @destination = arg
+ when "--retry-number"
+ @retry_number = arg.to_i
+ when "--retry-delay"
+ @retry_delay = arg.to_i
+ when "--ssh-key"
+ @ssh_key = arg
+ end
+ end
+rescue => err
+ # any errors, show the help
+ warn err.to_s
+ help = true
end
-error("Must suply --destination or put it into /etc/bytebackup/destination") unless @destination
-_dummy, @destination_user, @destination_host, colon, @destination_path =
- /^(.*)?(?:@)([^:]+)(:)(.*)?$/.match(@destination).to_a
-error("Must be a remote path") unless colon
+#
+# Check our destination
+#
+if @destination =~ /^(?:(.+)@)?([^@:]+):(.+)?$/
+ @destination_user, @destination_host, @destination_path = [$1, $2, $3]
+else
+ error("Destination must be a remote path, e.g. ssh@host.com:/store/backups")
+end
+#
# Validate & normalise source directories
#
+@sources = ["/"] if @sources.nil?
+
error("No sources specified") if @sources.empty?
+
@sources = @sources.map do |s|
- s = s.gsub(/\/+/,"/")
- error("Can't read directory #{s}") unless File.readable?(s)
- s
+ s = s.gsub(/\/+/,"/")
+ error("Can't read directory #{s}") unless File.readable?(s)
+ s
end
-# Guess destination for backup
#
-if !@destination
- guesses = []
- hostname = `hostname -f`
- Resolv::DNS.open do |dns|
- suffix = hostname.split(".")[2..-1].join(".")
- ["byteback." + suffix].each do |name|
- [Resolv::DNS::Resource::IN::AAAA,
- Resolv::DNS::Resource::IN::A].each do |record_type|
- next if !guesses.empty? # only care about first result
- guesses += dns.getresources(name, record_type)
- end
- end
- end
-
- if guesses.empty?
- error "Couldn't guess at backup host, please specify --destination"
- end
-
- # ick, do I really have to do this to get a string represnetion of
- # the IP address?
- #
- guess = guesses.first.inspect
- match = / (.*)>$/.match(guess)[1]
- error "Result #{guesses} is not an IP" if !match
- @destination = "byteback@#{match[1]}:#{HOSTNAME}/current/"
-
- verbose "Guessed destination=#{@destination} from #{guess}"
+# Validate and normalise excludes
+#
+if @excludes.nil?
+ @excludes = ["/swap.file", "/var/backups/localhost"]
+ @excludes << "/var/cache/apt/archives" if File.directory?("/var/cache/apt/archives")
+end
+
+@excludes = @excludes.map do |e|
+ e.gsub(/\/+/,"/")
end
+error("Must suply --destination or put it into /etc/bytebackup/destination") unless @destination
+
+#
# Test ssh connection is good before we start
#
error("Could not read ssh key #{@ssh_key}") unless File.readable?(@ssh_key)
-def ssh(*args)
- ["ssh",
- "-o", "BatchMode=yes",
- "-x", "-a",
- "-i", @ssh_key,
- "-l", @destination_user,
- @destination_host
- ] +
- args.
- map { |a| a ? a : "" }
+def ssh(*ssh_args)
+ args = ["ssh",
+ "-o", "BatchMode=yes",
+ "-x", "-a",
+ "-i", @ssh_key,
+ "-l", @destination_user,
+ @destination_host
+ ] +
+ ssh_args.
+ map { |a| a ? a : "" }
+
+ print args.map { |a| / /.match(a) ? "\"#{a}\"" : a }.join(" ")+"\n" if $VERBOSE
+
+ system(*args)
end
-error("Could not connect to #{@destination}") unless
- system(*ssh("byteback-receive", "--ping", @verbose))
+error("Could not connect to #{@destination}") unless
+ ssh("byteback-receive", "--ping", ($VERBOSE ? "--verbose" : "" ))
+#
# Call rsync to copy certain sources, returns exit status (see man rsync)
#
def rsync(*sources)
- # Default options include --inplace because we only care about consistency
- # at the end of the job, and rsync will do more work for big files without
- # it.
- #
- args = [
- "rsync",
- "--archive",
- "--numeric-ids",
- "--delete",
- "--inplace",
- "--rsync-path",
- "rsync --fake-super",
- "--rsh",
- ssh[0..-2].join(" "),
- "--delete",
- "--one-file-system",
- "--relative"
- ]
-
- args << "--verbose" if @verbose
- args += @exclude.map { |x| ["--exclude", x] }.flatten
- args += sources
- args << @destination
-
- print args.map { |a| / /.match(a) ? "\"#{a}\"" : a }.join(" ")+"\n" if @verbose
-
- system(*args)
-
- return $?.exitstatus
+ # Default options include --inplace because we only care about consistency
+ # at the end of the job, and rsync will do more work for big files without
+ # it.
+ #
+ args = %w(rsync --archive --numeric-ids --delete --inplace --delete --one-file-system --relative)
+ args += [ "--rsync-path", "rsync --fake-super"]
+ args += [ "--rsh", "ssh -o BatchMode=yes -x -a -i #{@ssh_key} -l #{@destination_user}"]
+ args << "--verbose" if $VERBOSE
+ args += @excludes.map { |x| ["--exclude", x] }.flatten
+ args += sources
+ args << @destination
+
+ print args.map { |a| / /.match(a) ? "\"#{a}\"" : a }.join(" ")+"\n" if $VERBOSE
+
+ system(*args)
+
+ return $?.exitstatus
end
RSYNC_EXIT_STATUSES_TO_RETRY_ON = [10,11,20,21,22,23,24,30]
# Run the file copy, retrying if necessary
-#
+#
loop do
- status = rsync(*@sources)
-
- if status === 0
- break
- elsif RSYNC_EXIT_STATUSES_TO_RETRY_ON.include?(status)
- if @retry_number > 0
- @retry_number -= 1
- sleep @retry_delay
- redo
- else
- error("Maximum number of rsync retries reached")
- end
- else
- error("Fatal rsync error occurred (#{status})")
- end
+ status = rsync(*@sources)
+
+ if status === 0
+ break
+ elsif RSYNC_EXIT_STATUSES_TO_RETRY_ON.include?(status)
+ if @retry_number > 0
+ @retry_number -= 1
+ sleep @retry_delay
+ redo
+ else
+ error("Maximum number of rsync retries reached")
+ end
+ else
+ error("Fatal rsync error occurred (#{status})")
+ end
end
# Mark the backup as done on the other end
#
-error("Backup could not be marked complete") unless
- system(*ssh("sudo", "byteback-snapshot", "--snapshot", @verbose))
+error("Backup could not be marked complete") unless
+ ssh("sudo", "byteback-snapshot", "--snapshot", ($VERBOSE ? "--verbose" : ""))
+