diff options
-rwxr-xr-x | byteback-backup | 358 |
1 files changed, 179 insertions, 179 deletions
diff --git a/byteback-backup b/byteback-backup index 430975e..ffc4186 100755 --- a/byteback-backup +++ b/byteback-backup @@ -1,179 +1,179 @@ -#!/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
-# and safe for most types of hosting customer.
-#
-# See 'man byteback' for more information.
-
-require 'trollop'
-require 'resolv'
-
-@sources = ["/"]
-@exclude = ["/swap.file", "/var/backups/localhost"]
-
-def error(message)
- STDERR.print "*** #{message}\n"
- exit 1
-end
-
-def verbose(message)
- 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
-
- opt :verbose, "Show rsync command and progress"
-
- opt :retry_number, "Number of retries on error",
- :type => :integer,
- :default => 3
-
- opt :retry_delay, "Wait number of seconds between retries",
- :type => :integer,
- :default => 1800
-
- opt :ssh_key, "SSH key for connection",
- :type => :string,
- :default => "/etc/byteback/key"
-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]
-
-if !@destination && File.exists?("/etc/byteback/destination")
- @destination = File.read("/etc/byteback/destination").chomp
-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
-
-# Validate & normalise source directories
-#
-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
-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}"
-end
-
-# 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
- ] +
- args
-end
-
-error("Could not connect to #{@destination}") unless
- system(*ssh("byteback-receive", "--ping", @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.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
-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
-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))
+#!/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 +# and safe for most types of hosting customer. +# +# See 'man byteback' for more information. + +require 'trollop' +require 'resolv' + +@sources = ["/"] +@exclude = ["/swap.file", "/var/backups/localhost"] + +def error(message) + STDERR.print "*** #{message}\n" + exit 1 +end + +def verbose(message) + 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 + + opt :verbose, "Show rsync command and progress" + + opt :retry_number, "Number of retries on error", + :type => :integer, + :default => 3 + + opt :retry_delay, "Wait number of seconds between retries", + :type => :integer, + :default => 1800 + + opt :ssh_key, "SSH key for connection", + :type => :string, + :default => "/etc/byteback/key" +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] + +if !@destination && File.exists?("/etc/byteback/destination") + @destination = File.read("/etc/byteback/destination").chomp +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 + +# Validate & normalise source directories +# +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 +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}" +end + +# 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 + ] + + args +end + +error("Could not connect to #{@destination}") unless + system(*ssh("byteback-receive", "--ping", @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.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 +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 +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)) |