backupgem 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +6 -0
- data/README +387 -0
- data/bin/backup +12 -0
- data/bin/commands.sh +2 -0
- data/doc/LICENSE-GPL +280 -0
- data/doc/index.html +716 -0
- data/doc/styles.css +157 -0
- data/examples/global.rb +28 -0
- data/examples/mediawiki.rb +24 -0
- data/lib/backup/actor.rb +196 -0
- data/lib/backup/cli.rb +105 -0
- data/lib/backup/configuration.rb +138 -0
- data/lib/backup/date_parser.rb +37 -0
- data/lib/backup/extensions.rb +17 -0
- data/lib/backup/recipes/standard.rb +89 -0
- data/lib/backup/rotator.rb +155 -0
- data/lib/backup/ssh_helpers.rb +138 -0
- data/lib/backup.rb +7 -0
- data/tests/actor_test.rb +70 -0
- data/tests/cleanup.sh +2 -0
- data/tests/rotation_test.rb +32 -0
- data/tests/ssh_test.rb +21 -0
- data/tests/tests_helper.rb +5 -0
- metadata +99 -0
| @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            #------------------------------------------------------------------------------
         | 
| 2 | 
            +
            # Backup Global Settings
         | 
| 3 | 
            +
            # @author: Nate Murray <nate@natemurray.com>
         | 
| 4 | 
            +
            #   @date: Mon Aug 28 07:28:22 PDT 2006
         | 
| 5 | 
            +
            # 
         | 
| 6 | 
            +
            # The settings contained in this file will be global for all tasks and can be
         | 
| 7 | 
            +
            # overridden locally.
         | 
| 8 | 
            +
            #------------------------------------------------------------------------------
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # Sepcify sever settings
         | 
| 11 | 
            +
            set :servers,           %w{ localhost }
         | 
| 12 | 
            +
            set :action_order,      %w{ content compress encrypt deliver rotate cleanup }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            # Name of the SSH user
         | 
| 15 | 
            +
            set :ssh_user,          ENV['USER']
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            # Path to your SSH key
         | 
| 18 | 
            +
            set :identity_key,      ENV['HOME'] + "/.ssh/id_rsa"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            # Set global actions
         | 
| 21 | 
            +
            action :compress, :method => :tar_bz2 
         | 
| 22 | 
            +
            action :deliver,  :method => :mv      # action :deliver,  :method => :scp    
         | 
| 23 | 
            +
            action :rotate,   :method => :via_mv  # action :rotate,   :method => :via_ssh
         | 
| 24 | 
            +
            # action :encrypt,  :method => :gpg
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            # Specify a directory that backup can use as a temporary directory
         | 
| 27 | 
            +
            set :tmp_dir, "/tmp"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            # Options to be passed to gpg when encrypting
         | 
| 30 | 
            +
            set :encrypt, false
         | 
| 31 | 
            +
            set :gpg_encrypt_options, ""
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            # These settings specify the rotation variables
         | 
| 34 | 
            +
            # Rotation method. Currently the only method is gfs, grandfather-father-son. 
         | 
| 35 | 
            +
            # Read more about that below
         | 
| 36 | 
            +
            set :rotation_method,  :gfs
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            # :mon-sun
         | 
| 39 | 
            +
            # :last_day_of_the_month # whatever son_promoted on son was, but the last of the month
         | 
| 40 | 
            +
            # everything else you can define with a Runt object
         | 
| 41 | 
            +
            # set :son_created_on,     :every_day - if you dont want a son created dont run the program
         | 
| 42 | 
            +
            # a backup is created every time the program is run
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            set :son_promoted_on,    :fri
         | 
| 45 | 
            +
            set :father_promoted_on, :last_fri_of_the_month
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            # more complex
         | 
| 48 | 
            +
            # mon_wed_fri = Runt::DIWeek.new(Runt::Mon) | 
         | 
| 49 | 
            +
            #               Runt::DIWeek.new(Runt::Wed) | 
         | 
| 50 | 
            +
            #               Runt::DIWeek.new(Runt::Fri)
         | 
| 51 | 
            +
            # set :son_promoted_on, mon_wed_fri
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            set :sons_to_keep,         14
         | 
| 54 | 
            +
            set :fathers_to_keep,       6
         | 
| 55 | 
            +
            set :grandfathers_to_keep,  6   # 6 months, by default
         | 
| 56 | 
            +
             | 
| 57 | 
            +
             | 
| 58 | 
            +
            # -------------------------
         | 
| 59 | 
            +
            # Standard Actions
         | 
| 60 | 
            +
            # -------------------------
         | 
| 61 | 
            +
            action(:tar_bz2) do
         | 
| 62 | 
            +
              name = c[:tmp_dir] + "/" + File.basename(last_result) + ".tar.bz2"
         | 
| 63 | 
            +
              sh "tar -cvjf #{name} #{last_result}"
         | 
| 64 | 
            +
              name
         | 
| 65 | 
            +
            end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            action(:scp) do
         | 
| 68 | 
            +
              # what should the default scp task be?
         | 
| 69 | 
            +
              # scp the local file to the foreign directory. same name.
         | 
| 70 | 
            +
              c[:servers].each do |server|
         | 
| 71 | 
            +
                host = server =~ /localhost/ ? "" : "#{server}:"
         | 
| 72 | 
            +
                sh "scp #{last_result} #{host}#{c[:backup_path]}/"
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
              c[:backup_path] + "/" + File.basename(last_result)  
         | 
| 75 | 
            +
            end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            action(:mv) do
         | 
| 78 | 
            +
              sh "mv #{last_result} #{c[:backup_path]}/"
         | 
| 79 | 
            +
              c[:backup_path] + "/" + File.basename(last_result)
         | 
| 80 | 
            +
            end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            action(:encrypt) do
         | 
| 83 | 
            +
              result = last_result
         | 
| 84 | 
            +
              if c[:encrypt]
         | 
| 85 | 
            +
                sh "gpg #{c[:gpg_encrypt_options]} --encrypt #{last_result}"
         | 
| 86 | 
            +
                result = last_result + ".gpg" # ?
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
              result
         | 
| 89 | 
            +
            end
         | 
| @@ -0,0 +1,155 @@ | |
| 1 | 
            +
            require "rake"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Backup
         | 
| 4 | 
            +
              class Rotator
         | 
| 5 | 
            +
                include FileUtils
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                attr_reader :actor
         | 
| 8 | 
            +
                attr_reader :configuration
         | 
| 9 | 
            +
                alias_method :c, :configuration
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(actor)
         | 
| 12 | 
            +
                  @actor = actor
         | 
| 13 | 
            +
                  @configuration = actor.configuration
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Take the last result and rotate it via mv on the local machine
         | 
| 17 | 
            +
                def rotate_via_mv(last_result)
         | 
| 18 | 
            +
                  # verify that each of the directories exist, grandfathers, fathers, sons
         | 
| 19 | 
            +
                  hierarchy.each { |m| verify_local_backup_directory_exists(m) }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  # place todays backup into the specified directory with a timestamp. 
         | 
| 22 | 
            +
                  newname = timestamped_prefix(last_result)
         | 
| 23 | 
            +
                  sh "mv #{last_result} #{place_in}/#{newname}"
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  cleanup_via_mv(place_in, how_many_to_keep_today)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                def rotate_via_ssh(last_result)
         | 
| 29 | 
            +
                  ssh = Backup::SshActor.new(c) 
         | 
| 30 | 
            +
                  ssh.connect 
         | 
| 31 | 
            +
                  ssh.run "echo \"#{last_result}\""
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  hierarchy.each do |m| 
         | 
| 34 | 
            +
                    dir = c[:backup_path] + "/" + m
         | 
| 35 | 
            +
                    ssh.verify_directory_exists(dir) 
         | 
| 36 | 
            +
                  end  
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  newname = timestamped_prefix(last_result)
         | 
| 39 | 
            +
                  ssh.run "mv #{last_result} #{place_in}/#{newname}"
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  ssh.cleanup_directory(place_in, how_many_to_keep_today)
         | 
| 42 | 
            +
                  ssh.close
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # TODO
         | 
| 46 | 
            +
                def rotate_via_ftp(last_result)
         | 
| 47 | 
            +
            #      ftp = Backup::FtpActor.new(c) 
         | 
| 48 | 
            +
            #      ftp.connect 
         | 
| 49 | 
            +
            #
         | 
| 50 | 
            +
            #      hierarchy.each do |m| 
         | 
| 51 | 
            +
            #        dir = c[:backup_path] + "/" + m
         | 
| 52 | 
            +
            #        ftp.verify_directory_exists(dir) 
         | 
| 53 | 
            +
            #      end  
         | 
| 54 | 
            +
            #
         | 
| 55 | 
            +
            #      newname = timestamped_prefix(last_result)
         | 
| 56 | 
            +
            #      ftp.run "mv #{last_result} #{place_in}/#{newname}"
         | 
| 57 | 
            +
            #
         | 
| 58 | 
            +
            #      ftp.cleanup_directory(place_in, how_many_to_keep_today)
         | 
| 59 | 
            +
            #      ftp.close
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def create_sons_today?;     is_today_a? :son_created_on;     end
         | 
| 63 | 
            +
                def promote_sons_today?;    is_today_a? :son_promoted_on;    end
         | 
| 64 | 
            +
                def promote_fathers_today?; is_today_a? :father_promoted_on; end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                # old ( offset_days % c[:son_promoted_on] ) == 0 ? true : false
         | 
| 67 | 
            +
                # old ( offset_days % (c[:son_promoted_on] * c[:father_promoted_on]) ) == 0 ? true : false
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                private
         | 
| 70 | 
            +
                  def is_today_a?(symbol)
         | 
| 71 | 
            +
                    t = $test_time || Date.today
         | 
| 72 | 
            +
                    day = DateParser.date_from( c[symbol] )
         | 
| 73 | 
            +
                    day.include?( t )
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def verify_local_backup_directory_exists(dir)
         | 
| 77 | 
            +
                    path = c[:backup_path]
         | 
| 78 | 
            +
                    full = path + "/" + dir
         | 
| 79 | 
            +
                    unless File.exists?(full) 
         | 
| 80 | 
            +
                      sh "mkdir -p #{full}"  
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  #def offset_days
         | 
| 85 | 
            +
                  #  t = $test_time || Time.now # todo, write a test to use this
         | 
| 86 | 
            +
                  #  num_from_day = Time.num_from_day( c[:promote_on] )
         | 
| 87 | 
            +
                  #  offset = ( t.days_since_epoch + num_from_day + 0)
         | 
| 88 | 
            +
                  #  offset
         | 
| 89 | 
            +
                  #end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  def cleanup_via_mv(where, num_keep)
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    files = Dir[where + "/*"].sort
         | 
| 94 | 
            +
                    diff  = files.size - num_keep
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    1.upto( diff ) do 
         | 
| 97 | 
            +
                      extra = files.shift
         | 
| 98 | 
            +
                      sh "rm #{extra}"
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def hierarchy 
         | 
| 103 | 
            +
                    %w{grandfathers fathers sons}
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  # figure out which generation today's backup is
         | 
| 107 | 
            +
                  public
         | 
| 108 | 
            +
                  def todays_generation
         | 
| 109 | 
            +
                    goes_in = promote_fathers_today? ? "grandfathers" :         \
         | 
| 110 | 
            +
                              promote_sons_today?    ? "fathers"      : "sons"
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  private
         | 
| 114 | 
            +
                  def place_in
         | 
| 115 | 
            +
                    goes_in = todays_generation
         | 
| 116 | 
            +
                    place_in = c[:backup_path] + "/" + goes_in
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  # Given +name+ returns a timestamped version of name. 
         | 
| 121 | 
            +
                  def timestamped_prefix(name)
         | 
| 122 | 
            +
                    newname = Time.now.strftime("%Y-%m-%d-%H-%M-%S_") + File.basename(name)
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  # Returns the number of sons to keep. Looks for config values +:sons_to_keep+, 
         | 
| 126 | 
            +
                  # +:son_promoted_on+. Default +14+.
         | 
| 127 | 
            +
                  def sons_to_keep
         | 
| 128 | 
            +
                    c[:sons_to_keep]    || c[:son_promoted_on]    || 14
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
                  
         | 
| 131 | 
            +
                  # Returns the number of fathers to keep. Looks for config values +:fathers_to_keep+, 
         | 
| 132 | 
            +
                  # +:fathers_promoted_on+. Default +6+.
         | 
| 133 | 
            +
                  def fathers_to_keep
         | 
| 134 | 
            +
                    c[:fathers_to_keep] || c[:father_promoted_on] || 6
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  # Returns the number of grandfathers to keep. Looks for config values
         | 
| 138 | 
            +
                  # +:grandfathers_to_keep+. Default +6+.
         | 
| 139 | 
            +
                  def gfathers_to_keep
         | 
| 140 | 
            +
                    c[:grandfathers_to_keep] || 6
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  # This method returns the number of how many to keep in whatever today
         | 
| 144 | 
            +
                  # goes in. Example: if today is a day to create a +son+ then this
         | 
| 145 | 
            +
                  # function returns the value of +sons_to_keep+. If today is a +father+
         | 
| 146 | 
            +
                  # then +fathers_to_keep+ etc.
         | 
| 147 | 
            +
                  def how_many_to_keep_today
         | 
| 148 | 
            +
                    goes_in = todays_generation
         | 
| 149 | 
            +
                    keep = goes_in =~ /^sons$/         ? sons_to_keep     :
         | 
| 150 | 
            +
                           goes_in =~ /^fathers$/      ? fathers_to_keep  :
         | 
| 151 | 
            +
                           goes_in =~ /^grandfathers$/ ? gfathers_to_keep : 14
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
               end
         | 
| 155 | 
            +
            end
         | 
| @@ -0,0 +1,138 @@ | |
| 1 | 
            +
            # large portions borrowed from capistrano
         | 
| 2 | 
            +
            require 'rubygems'
         | 
| 3 | 
            +
            require 'net/ssh'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # TODO - add in a way to extend the belay script to work with this. thats a
         | 
| 6 | 
            +
            # good idea for the belay script over all. write the kernal extensions, and the
         | 
| 7 | 
            +
            # rake extensions. form there write an example third party extension.
         | 
| 8 | 
            +
            module Backup
         | 
| 9 | 
            +
              class Command
         | 
| 10 | 
            +
                attr_reader   :command, :options
         | 
| 11 | 
            +
                attr_reader   :actor
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(server_name, command, callback, options, actor) #:nodoc:
         | 
| 14 | 
            +
                  @command  = command.strip.gsub(/\r?\n/, "\\\n")
         | 
| 15 | 
            +
                  @callback = callback
         | 
| 16 | 
            +
                  @options  = options
         | 
| 17 | 
            +
                  @actor    = actor
         | 
| 18 | 
            +
                  @channels = open_channels
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def process!
         | 
| 22 | 
            +
                  since = Time.now
         | 
| 23 | 
            +
                  loop do
         | 
| 24 | 
            +
                    active = 0
         | 
| 25 | 
            +
                    @channels.each do |ch|
         | 
| 26 | 
            +
                      next if ch[:closed]
         | 
| 27 | 
            +
                      active += 1
         | 
| 28 | 
            +
                      ch.connection.process(true)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    break if active == 0
         | 
| 32 | 
            +
                    if Time.now - since >= 1
         | 
| 33 | 
            +
                      since = Time.now
         | 
| 34 | 
            +
                      @channels.each { |ch| ch.connection.ping! }
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    sleep 0.01 # a brief respite, to keep the CPU from going crazy
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  #logger.trace "command finished"
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  if failed = @channels.detect { |ch| ch[:status] != 0 }
         | 
| 42 | 
            +
                    raise "command #{@command.inspect} failed"
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  self
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
                 
         | 
| 48 | 
            +
                def open_channels
         | 
| 49 | 
            +
                  channel = actor.session.open_channel do |channel|
         | 
| 50 | 
            +
                       channel.request_pty( :want_reply => true )
         | 
| 51 | 
            +
                       channel[:actor] = @actor
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                       channel.on_success do |ch|
         | 
| 54 | 
            +
                         #logger.trace "executing command", ch[:host]
         | 
| 55 | 
            +
                         ch.exec command
         | 
| 56 | 
            +
                         ch.send_data options[:data] if options[:data]
         | 
| 57 | 
            +
                       end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                       channel.on_data do |ch, data|
         | 
| 60 | 
            +
                         puts data
         | 
| 61 | 
            +
                         @callback[ch, :out, data] if @callback
         | 
| 62 | 
            +
                       end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                       channel.on_failure do |ch|
         | 
| 65 | 
            +
                         #logger.important "could not open channel", ch[:host]
         | 
| 66 | 
            +
                         # puts "we got a faulure"
         | 
| 67 | 
            +
                         ch.close
         | 
| 68 | 
            +
                       end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                       channel.on_request do |ch, request, reply, data|
         | 
| 71 | 
            +
                         ch[:status] = data.read_long if request == "exit-status"
         | 
| 72 | 
            +
                       end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                       channel.on_close do |ch|
         | 
| 75 | 
            +
                         ch[:closed] = true
         | 
| 76 | 
            +
                       end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                  [channel]
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
              end  # end Class Command
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              class SshActor
         | 
| 83 | 
            +
                 
         | 
| 84 | 
            +
                #def self.new_for_ssh(server_name)
         | 
| 85 | 
            +
                #  a = new(server_name)
         | 
| 86 | 
            +
                #end 
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                attr_reader :session
         | 
| 89 | 
            +
                attr_reader :config
         | 
| 90 | 
            +
                alias_method :c, :config
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def initialize(config)
         | 
| 93 | 
            +
                  @config = config
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def connect
         | 
| 97 | 
            +
                  c[:servers].each do |server| # todo, make this actually work
         | 
| 98 | 
            +
                  @session = Net::SSH.start(
         | 
| 99 | 
            +
                       server, 
         | 
| 100 | 
            +
                       c[:ssh_user],
         | 
| 101 | 
            +
                       :host_key     => "ssh-rsa",
         | 
| 102 | 
            +
                       :keys         => [ c[:identity_key] ],
         | 
| 103 | 
            +
                       :auth_methods => %w{ publickey } )
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def run(cmd, options={}, &block)
         | 
| 108 | 
            +
                  #logger.debug "executing #{cmd.strip.inspect}"
         | 
| 109 | 
            +
                  puts "executing #{cmd.strip.inspect}"
         | 
| 110 | 
            +
                  command = Command.new(@server_name, cmd, block, options, self)
         | 
| 111 | 
            +
                  command.process! # raises an exception if command fails on any server
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def on_remote(&block)
         | 
| 115 | 
            +
                  connect
         | 
| 116 | 
            +
                  self.instance_eval(&block)
         | 
| 117 | 
            +
                  close
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def close
         | 
| 121 | 
            +
                  @session.close
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                def verify_directory_exists(dir)
         | 
| 125 | 
            +
                  run "if [ -d '#{dir}' ]; then true; else mkdir -p '#{dir}'; fi"
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                def cleanup_directory(dir, keep)
         | 
| 129 | 
            +
                  puts "Cleaning up" 
         | 
| 130 | 
            +
                  cleanup = <<-END
         | 
| 131 | 
            +
                  LOOK_IN="#{dir}"; COUNT=`ls -1 $LOOK_IN | wc -l`; MAX=#{keep}; if (( $COUNT > $MAX )); then let "OFFSET=$COUNT-$MAX"; i=1; for f in `ls -1 $LOOK_IN | sort`; do if (( $i <= $OFFSET )); then CMD="rm $LOOK_IN/$f"; echo $CMD; $CMD; fi; let "i=$i + 1"; done; else true; fi
         | 
| 132 | 
            +
                  END
         | 
| 133 | 
            +
                  run cleanup # todo make it so that even this can be overridden
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
               end # end class SshActor
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            end # end Module Backup
         | 
    
        data/lib/backup.rb
    ADDED
    
    
    
        data/tests/actor_test.rb
    ADDED
    
    | @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            #!/usr/bin/ruby
         | 
| 2 | 
            +
            require File.dirname(__FILE__) + "/tests_helper"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class ActorTest < Test::Unit::TestCase
         | 
| 5 | 
            +
              def setup
         | 
| 6 | 
            +
                @config = Backup::Configuration.new
         | 
| 7 | 
            +
                @config.load "standard"
         | 
| 8 | 
            +
                @actor = @config.actor
         | 
| 9 | 
            +
                setup_tmp_backup_dir
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def dont_test_exists
         | 
| 13 | 
            +
                 assert @actor
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def dont_test_is_file
         | 
| 17 | 
            +
                dir = create_tmp_files
         | 
| 18 | 
            +
                config = <<-END
         | 
| 19 | 
            +
                  action :content, :is_file => "#{dir}/1"
         | 
| 20 | 
            +
                END
         | 
| 21 | 
            +
                @config.load :string => config
         | 
| 22 | 
            +
                assert result = @actor.content
         | 
| 23 | 
            +
                assert File.exists?(result)
         | 
| 24 | 
            +
                puts "content result is: #{result}"
         | 
| 25 | 
            +
                @actor.start_process! 
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def dont_test_is_folder
         | 
| 29 | 
            +
                dir = create_tmp_files
         | 
| 30 | 
            +
                config = <<-END
         | 
| 31 | 
            +
                  action :content, :is_folder => "#{dir}"
         | 
| 32 | 
            +
                END
         | 
| 33 | 
            +
                @config.load :string => config
         | 
| 34 | 
            +
                assert result = @actor.content
         | 
| 35 | 
            +
                assert File.exists?(result)
         | 
| 36 | 
            +
                assert File.directory?(result)
         | 
| 37 | 
            +
                puts "content result is: #{result}"
         | 
| 38 | 
            +
                @actor.start_process! 
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def test_is_contents_of
         | 
| 42 | 
            +
                dir = create_tmp_files
         | 
| 43 | 
            +
                config = <<-END
         | 
| 44 | 
            +
                  action :content, :is_contents_of => "#{dir}", :copy => true
         | 
| 45 | 
            +
                END
         | 
| 46 | 
            +
                @config.load :string => config
         | 
| 47 | 
            +
                #@actor.content
         | 
| 48 | 
            +
                #@actor.cleanup
         | 
| 49 | 
            +
                @actor.start_process! 
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              private
         | 
| 53 | 
            +
                def setup_tmp_backup_dir
         | 
| 54 | 
            +
                  newtmp = @config[:tmp_dir] + "/backup_#{rand}_" + Time.now.strftime("%Y%m%d%H%M%S")
         | 
| 55 | 
            +
                  sh "mkdir #{newtmp}"
         | 
| 56 | 
            +
                  config = <<-END
         | 
| 57 | 
            +
                    set :backup_path, "#{newtmp}" 
         | 
| 58 | 
            +
                  END
         | 
| 59 | 
            +
                  @config.load :string => config
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def create_tmp_files
         | 
| 63 | 
            +
                  newtmp = @config[:tmp_dir] + "/test_#{rand}_" + Time.now.strftime("%Y%m%d%H%M%S")
         | 
| 64 | 
            +
                  sh "mkdir #{newtmp}"
         | 
| 65 | 
            +
                  0.upto(5) { |i| sh "touch #{newtmp}/#{i}" }
         | 
| 66 | 
            +
                  newtmp
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            end
         | 
| 70 | 
            +
             | 
    
        data/tests/cleanup.sh
    ADDED
    
    | @@ -0,0 +1,2 @@ | |
| 1 | 
            +
            #!/usr/bin/bash
         | 
| 2 | 
            +
            LOOK_IN="/var/local/backups/mediawiki/sons"; COUNT=`ls -1 $LOOK_IN | wc -l`; MAX=14; if (( $COUNT > $MAX )); then let "OFFSET=$COUNT-$MAX"; i=1; for f in `ls -1 $LOOK_IN | sort`; do if (( $i <= $OFFSET )); then CMD="rm $LOOK_IN/$f"; echo $CMD; $CMD; fi; echo "$i $f"; let "i=$i + 1"; done; else true; fi
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            #!/usr/bin/ruby
         | 
| 2 | 
            +
            require File.dirname(__FILE__) + "/tests_helper"
         | 
| 3 | 
            +
            require 'runt'
         | 
| 4 | 
            +
            require 'date'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class RotationTest < Test::Unit::TestCase
         | 
| 7 | 
            +
              def setup
         | 
| 8 | 
            +
                # setup our config to test the objects
         | 
| 9 | 
            +
                @config = Backup::Configuration.new
         | 
| 10 | 
            +
                @config.load "standard"
         | 
| 11 | 
            +
                @rotator = Backup::Rotator.new(@config.actor) 
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def test_rotator
         | 
| 15 | 
            +
                t = Date.today
         | 
| 16 | 
            +
                r = @rotator
         | 
| 17 | 
            +
                0.upto(14) do |i|
         | 
| 18 | 
            +
                  $test_time = t
         | 
| 19 | 
            +
                  print t.to_s + t.strftime(" #{i} %a ").to_s 
         | 
| 20 | 
            +
                  puts r.todays_generation 
         | 
| 21 | 
            +
                  t += 1
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def test_date_parsing
         | 
| 26 | 
            +
                dp = Backup::DateParser.new
         | 
| 27 | 
            +
                assert fri   = dp.date_from(:fri)
         | 
| 28 | 
            +
                assert daily = dp.date_from(:daily)
         | 
| 29 | 
            +
                assert last  = dp.date_from(:last_mon_of_the_month)
         | 
| 30 | 
            +
                assert_raise(RuntimeError) { dp.date_from(:asdfasdf) }
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
    
        data/tests/ssh_test.rb
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + "/tests_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class SSHTest < Test::Unit::TestCase
         | 
| 4 | 
            +
              def setup
         | 
| 5 | 
            +
                @config = Backup::Configuration.new
         | 
| 6 | 
            +
                @config.load "standard"
         | 
| 7 | 
            +
                @config.action :deliver, :method => :via_ssh 
         | 
| 8 | 
            +
                @actor = Backup::SshActor.new(@config) 
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def test_exists
         | 
| 12 | 
            +
                assert @config
         | 
| 13 | 
            +
                assert @actor
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def test_on_remote
         | 
| 17 | 
            +
                @actor.on_remote do
         | 
| 18 | 
            +
                  run "echo \"hello $HOSTNAME\""
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            rubygems_version: 0.9.0
         | 
| 3 | 
            +
            specification_version: 1
         | 
| 4 | 
            +
            name: backupgem
         | 
| 5 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 6 | 
            +
              version: 0.0.2
         | 
| 7 | 
            +
            date: 2006-10-06 00:00:00 -07:00
         | 
| 8 | 
            +
            summary: Beginning-to-end solution for backups and rotation.
         | 
| 9 | 
            +
            require_paths: 
         | 
| 10 | 
            +
            - lib
         | 
| 11 | 
            +
            email: nate@natemurray.com
         | 
| 12 | 
            +
            homepage: http://tech.natemurray.com/backup
         | 
| 13 | 
            +
            rubyforge_project: 
         | 
| 14 | 
            +
            description: 
         | 
| 15 | 
            +
            autorequire: backupgem
         | 
| 16 | 
            +
            default_executable: 
         | 
| 17 | 
            +
            bindir: bin
         | 
| 18 | 
            +
            has_rdoc: true
         | 
| 19 | 
            +
            required_ruby_version: !ruby/object:Gem::Version::Requirement 
         | 
| 20 | 
            +
              requirements: 
         | 
| 21 | 
            +
              - - ">"
         | 
| 22 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 23 | 
            +
                  version: 0.0.0
         | 
| 24 | 
            +
              version: 
         | 
| 25 | 
            +
            platform: ruby
         | 
| 26 | 
            +
            signing_key: 
         | 
| 27 | 
            +
            cert_chain: 
         | 
| 28 | 
            +
            post_install_message: 
         | 
| 29 | 
            +
            authors: 
         | 
| 30 | 
            +
            - Nate Murray
         | 
| 31 | 
            +
            files: 
         | 
| 32 | 
            +
            - bin/backup
         | 
| 33 | 
            +
            - bin/commands.sh
         | 
| 34 | 
            +
            - lib/backup
         | 
| 35 | 
            +
            - lib/backup.rb
         | 
| 36 | 
            +
            - lib/backup/actor.rb
         | 
| 37 | 
            +
            - lib/backup/cli.rb
         | 
| 38 | 
            +
            - lib/backup/configuration.rb
         | 
| 39 | 
            +
            - lib/backup/date_parser.rb
         | 
| 40 | 
            +
            - lib/backup/extensions.rb
         | 
| 41 | 
            +
            - lib/backup/recipes
         | 
| 42 | 
            +
            - lib/backup/rotator.rb
         | 
| 43 | 
            +
            - lib/backup/ssh_helpers.rb
         | 
| 44 | 
            +
            - lib/backup/recipes/standard.rb
         | 
| 45 | 
            +
            - tests/actor_test.rb
         | 
| 46 | 
            +
            - tests/cleanup.sh
         | 
| 47 | 
            +
            - tests/rotation_test.rb
         | 
| 48 | 
            +
            - tests/ssh_test.rb
         | 
| 49 | 
            +
            - tests/tests_helper.rb
         | 
| 50 | 
            +
            - examples/global.rb
         | 
| 51 | 
            +
            - examples/mediawiki.rb
         | 
| 52 | 
            +
            - doc/index.html
         | 
| 53 | 
            +
            - doc/LICENSE-GPL
         | 
| 54 | 
            +
            - doc/styles.css
         | 
| 55 | 
            +
            - README
         | 
| 56 | 
            +
            - CHANGELOG
         | 
| 57 | 
            +
            test_files: 
         | 
| 58 | 
            +
            - tests/actor_test.rb
         | 
| 59 | 
            +
            - tests/rotation_test.rb
         | 
| 60 | 
            +
            - tests/ssh_test.rb
         | 
| 61 | 
            +
            rdoc_options: []
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            extra_rdoc_files: 
         | 
| 64 | 
            +
            - README
         | 
| 65 | 
            +
            - CHANGELOG
         | 
| 66 | 
            +
            executables: []
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            extensions: []
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            requirements: []
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            dependencies: 
         | 
| 73 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 74 | 
            +
              name: rake
         | 
| 75 | 
            +
              version_requirement: 
         | 
| 76 | 
            +
              version_requirements: !ruby/object:Gem::Version::Requirement 
         | 
| 77 | 
            +
                requirements: 
         | 
| 78 | 
            +
                - - ">="
         | 
| 79 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 80 | 
            +
                    version: 0.7.1
         | 
| 81 | 
            +
                version: 
         | 
| 82 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 83 | 
            +
              name: runt
         | 
| 84 | 
            +
              version_requirement: 
         | 
| 85 | 
            +
              version_requirements: !ruby/object:Gem::Version::Requirement 
         | 
| 86 | 
            +
                requirements: 
         | 
| 87 | 
            +
                - - ">="
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 89 | 
            +
                    version: 0.3.0
         | 
| 90 | 
            +
                version: 
         | 
| 91 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 92 | 
            +
              name: net-ssh
         | 
| 93 | 
            +
              version_requirement: 
         | 
| 94 | 
            +
              version_requirements: !ruby/object:Gem::Version::Requirement 
         | 
| 95 | 
            +
                requirements: 
         | 
| 96 | 
            +
                - - ">="
         | 
| 97 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 98 | 
            +
                    version: 1.0.9
         | 
| 99 | 
            +
                version: 
         |