lsync 1.2.1
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/bin/lsync +152 -0
 - data/lib/lsync.rb +40 -0
 - data/lib/lsync/action.rb +100 -0
 - data/lib/lsync/actions/darwin/disk +19 -0
 - data/lib/lsync/actions/darwin/terminal +8 -0
 - data/lib/lsync/actions/generic/prune +251 -0
 - data/lib/lsync/actions/generic/rotate +75 -0
 - data/lib/lsync/actions/linux/disk +28 -0
 - data/lib/lsync/actions/linux/terminal +6 -0
 - data/lib/lsync/backup_error.rb +33 -0
 - data/lib/lsync/backup_plan.rb +249 -0
 - data/lib/lsync/backup_script.rb +136 -0
 - data/lib/lsync/directory.rb +39 -0
 - data/lib/lsync/extensions.rb +22 -0
 - data/lib/lsync/lb.py +1304 -0
 - data/lib/lsync/method.rb +191 -0
 - data/lib/lsync/password.rb +35 -0
 - data/lib/lsync/run.rb +34 -0
 - data/lib/lsync/server.rb +94 -0
 - data/lib/lsync/shell.rb +103 -0
 - data/lib/lsync/shell_client.rb +84 -0
 - data/lib/lsync/tee_logger.rb +37 -0
 - data/lib/lsync/version.rb +24 -0
 - metadata +131 -0
 
    
        data/bin/lsync
    ADDED
    
    | 
         @@ -0,0 +1,152 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'ftools'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'lsync'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'lsync/version'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            OPTIONS = {
         
     | 
| 
      
 9 
     | 
    
         
            +
              :ConfigFiles => [],
         
     | 
| 
      
 10 
     | 
    
         
            +
              :Plan => nil,
         
     | 
| 
      
 11 
     | 
    
         
            +
              :LogPath => "/var/log/lsync/backup.log"
         
     | 
| 
      
 12 
     | 
    
         
            +
            }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            ARGV.options do |o|
         
     | 
| 
      
 15 
     | 
    
         
            +
              script_name = File.basename($0)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              o.set_summary_indent('  ')
         
     | 
| 
      
 18 
     | 
    
         
            +
              o.banner = "Usage: #{script_name} [options] [directory | config]"
         
     | 
| 
      
 19 
     | 
    
         
            +
              o.define_head "This script is used to run backups. If you specify a directory, files ending in .conf will all be processed."
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              o.separator ""
         
     | 
| 
      
 22 
     | 
    
         
            +
              o.separator "Help and Copyright information"
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              o.on("-p plan", String, "Run the specified backup plan") { |plan| OPTIONS[:Plan] = plan }
         
     | 
| 
      
 25 
     | 
    
         
            +
              o.on("-l log_path", String, "Set the directory for backup logs") { |log_path| OPTIONS[:LogPath] = log_path }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              o.on_tail("--copy", "Display copyright information") do
         
     | 
| 
      
 28 
     | 
    
         
            +
                puts "#{script_name} v#{LSync::VERSION::STRING}. Copyright (c) 2008-2009 Samuel Williams. Released under the GPLv3."
         
     | 
| 
      
 29 
     | 
    
         
            +
                puts "See http://www.oriontransfer.co.nz/ for more information."
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                exit
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
         
     | 
| 
      
 35 
     | 
    
         
            +
            end.parse!
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            # ============================== Main Scripts / Plans ==============================
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            class LogPathChecker
         
     | 
| 
      
 40 
     | 
    
         
            +
              def initialize(directory, filename)
         
     | 
| 
      
 41 
     | 
    
         
            +
                full_path = File.join(directory, filename)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                @directory_writable = File.directory?(directory) && File.writable?(directory)
         
     | 
| 
      
 44 
     | 
    
         
            +
                @file_not_writable = File.exist?(full_path) && !File.writable?(full_path)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                @accessable = @directory_writable && !@file_not_writable
         
     | 
| 
      
 47 
     | 
    
         
            +
                @full_path = full_path
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
              
         
     | 
| 
      
 50 
     | 
    
         
            +
              attr :accessable
         
     | 
| 
      
 51 
     | 
    
         
            +
              attr :full_path
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            def setup_logging
         
     | 
| 
      
 55 
     | 
    
         
            +
              # Console log output
         
     | 
| 
      
 56 
     | 
    
         
            +
              $console_log = Logger.new($stdout)
         
     | 
| 
      
 57 
     | 
    
         
            +
              $console_log.formatter = MinimalLogFormat.new
         
     | 
| 
      
 58 
     | 
    
         
            +
              
         
     | 
| 
      
 59 
     | 
    
         
            +
              # File log output
         
     | 
| 
      
 60 
     | 
    
         
            +
              filename = File.basename(OPTIONS[:LogPath])
         
     | 
| 
      
 61 
     | 
    
         
            +
              directory = File.dirname(OPTIONS[:LogPath])
         
     | 
| 
      
 62 
     | 
    
         
            +
              
         
     | 
| 
      
 63 
     | 
    
         
            +
              if filename == nil || filename == ""
         
     | 
| 
      
 64 
     | 
    
         
            +
                filename = "backup.log"
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
              
         
     | 
| 
      
 67 
     | 
    
         
            +
              check = LogPathChecker.new(directory, filename)
         
     | 
| 
      
 68 
     | 
    
         
            +
              unless check.accessable
         
     | 
| 
      
 69 
     | 
    
         
            +
                $console_log.warn "Could not write to #{check.full_path.dump} - changing log path to #{Dir.pwd.dump}"
         
     | 
| 
      
 70 
     | 
    
         
            +
                
         
     | 
| 
      
 71 
     | 
    
         
            +
                directory = Dir.pwd
         
     | 
| 
      
 72 
     | 
    
         
            +
                check = LogPathChecker.new(directory, filename)
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
              
         
     | 
| 
      
 75 
     | 
    
         
            +
              if check.accessable
         
     | 
| 
      
 76 
     | 
    
         
            +
                $backup_log = Logger.new(check.full_path, 'weekly')
         
     | 
| 
      
 77 
     | 
    
         
            +
              else
         
     | 
| 
      
 78 
     | 
    
         
            +
                $console_log.warn "Could not write to #{check.full_path.dump} - not logging to disk"
         
     | 
| 
      
 79 
     | 
    
         
            +
                $backup_log = nil
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
              
         
     | 
| 
      
 82 
     | 
    
         
            +
              $logger = TeeLogger.new($console_log, $backup_log)
         
     | 
| 
      
 83 
     | 
    
         
            +
            end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            def process_config_files
         
     | 
| 
      
 86 
     | 
    
         
            +
              ARGV.each do |p|
         
     | 
| 
      
 87 
     | 
    
         
            +
                unless File.exists? p
         
     | 
| 
      
 88 
     | 
    
         
            +
                  $logger.error "Could not process #{p}"
         
     | 
| 
      
 89 
     | 
    
         
            +
                  exit(20)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
              
         
     | 
| 
      
 92 
     | 
    
         
            +
                if File.directory? p
         
     | 
| 
      
 93 
     | 
    
         
            +
                  OPTIONS[:ConfigFiles] += Dir[File.join(p, "*.conf")]
         
     | 
| 
      
 94 
     | 
    
         
            +
                else
         
     | 
| 
      
 95 
     | 
    
         
            +
                  OPTIONS[:ConfigFiles] << p
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            def validate_options
         
     | 
| 
      
 101 
     | 
    
         
            +
              if OPTIONS[:Plan] && OPTIONS[:ConfigFiles].size > 0
         
     | 
| 
      
 102 
     | 
    
         
            +
                $logger.error "Please specify either a backup plan or a set of backup scripts, but not both in one run."
         
     | 
| 
      
 103 
     | 
    
         
            +
                exit(27)
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
            end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
            def run_backups
         
     | 
| 
      
 108 
     | 
    
         
            +
              OPTIONS[:ConfigFiles].each do |c|
         
     | 
| 
      
 109 
     | 
    
         
            +
                config = LSync::BackupScript.load_from_file(c)
         
     | 
| 
      
 110 
     | 
    
         
            +
                config.logger = $logger
         
     | 
| 
      
 111 
     | 
    
         
            +
                config.run_backup
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
              if OPTIONS[:Plan]
         
     | 
| 
      
 115 
     | 
    
         
            +
                config = LSync::BackupPlan.load_from_file(OPTIONS[:Plan])
         
     | 
| 
      
 116 
     | 
    
         
            +
                config.logger = $logger
         
     | 
| 
      
 117 
     | 
    
         
            +
                config.run_backup
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
            end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            def dump_exception_backtrace ex
         
     | 
| 
      
 122 
     | 
    
         
            +
              ex.backtrace.each do |bt|
         
     | 
| 
      
 123 
     | 
    
         
            +
                $logger.error bt
         
     | 
| 
      
 124 
     | 
    
         
            +
              end
         
     | 
| 
      
 125 
     | 
    
         
            +
            end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
            setup_logging
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
            process_config_files
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            validate_options
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
            $logger.info "  Backup beginning at #{Time.now.to_s}  ".center(96, "=")
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
            begin
         
     | 
| 
      
 136 
     | 
    
         
            +
              run_backups
         
     | 
| 
      
 137 
     | 
    
         
            +
            rescue LSync::BackupError
         
     | 
| 
      
 138 
     | 
    
         
            +
              $logger.error "#{$!.class.name}: #{$!.to_s}. Dumping backtrace:"
         
     | 
| 
      
 139 
     | 
    
         
            +
              dump_exception_backtrace($!)
         
     | 
| 
      
 140 
     | 
    
         
            +
              exit(230)
         
     | 
| 
      
 141 
     | 
    
         
            +
            rescue Interrupt
         
     | 
| 
      
 142 
     | 
    
         
            +
              $logger.error "Backup process recevied interrupt (Ctrl-C). Dumping backtrace:"
         
     | 
| 
      
 143 
     | 
    
         
            +
              dump_exception_backtrace($!)
         
     | 
| 
      
 144 
     | 
    
         
            +
              exit(240)
         
     | 
| 
      
 145 
     | 
    
         
            +
            rescue
         
     | 
| 
      
 146 
     | 
    
         
            +
              $logger.error "Unknown exception #{$!.class.name} thrown: #{$!.to_s}. Dumping backtrace:"
         
     | 
| 
      
 147 
     | 
    
         
            +
              dump_exception_backtrace($!)
         
     | 
| 
      
 148 
     | 
    
         
            +
              exit(250)
         
     | 
| 
      
 149 
     | 
    
         
            +
            ensure
         
     | 
| 
      
 150 
     | 
    
         
            +
              $logger.info "  Backup finishing at #{Time.now.to_s}  ".center(96, "=")
         
     | 
| 
      
 151 
     | 
    
         
            +
            end
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
    
        data/lib/lsync.rb
    ADDED
    
    | 
         @@ -0,0 +1,40 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright (c) 2007 Samuel Williams. Released under the GNU GPLv2.
         
     | 
| 
      
 2 
     | 
    
         
            +
            #
         
     | 
| 
      
 3 
     | 
    
         
            +
            # This program is free software: you can redistribute it and/or modify
         
     | 
| 
      
 4 
     | 
    
         
            +
            # it under the terms of the GNU General Public License as published by
         
     | 
| 
      
 5 
     | 
    
         
            +
            # the Free Software Foundation, either version 3 of the License, or
         
     | 
| 
      
 6 
     | 
    
         
            +
            # (at your option) any later version.
         
     | 
| 
      
 7 
     | 
    
         
            +
            # 
         
     | 
| 
      
 8 
     | 
    
         
            +
            # This program is distributed in the hope that it will be useful,
         
     | 
| 
      
 9 
     | 
    
         
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         
     | 
| 
      
 10 
     | 
    
         
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         
     | 
| 
      
 11 
     | 
    
         
            +
            # GNU General Public License for more details.
         
     | 
| 
      
 12 
     | 
    
         
            +
            # 
         
     | 
| 
      
 13 
     | 
    
         
            +
            # You should have received a copy of the GNU General Public License
         
     | 
| 
      
 14 
     | 
    
         
            +
            # along with this program.  If not, see <http://www.gnu.org/licenses/>.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            gem 'termios'
         
     | 
| 
      
 19 
     | 
    
         
            +
            gem 'net-ssh'
         
     | 
| 
      
 20 
     | 
    
         
            +
            gem 'ruleby'
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 23 
     | 
    
         
            +
            require 'socket'
         
     | 
| 
      
 24 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 25 
     | 
    
         
            +
            require 'logger'
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            require 'lsync/version'
         
     | 
| 
      
 28 
     | 
    
         
            +
            require 'lsync/extensions'
         
     | 
| 
      
 29 
     | 
    
         
            +
            require 'lsync/backup_script'
         
     | 
| 
      
 30 
     | 
    
         
            +
            require 'lsync/backup_plan'
         
     | 
| 
      
 31 
     | 
    
         
            +
            require 'lsync/tee_logger'
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 34 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            require 'open-uri'
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            module LSync
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/lsync/action.rb
    ADDED
    
    | 
         @@ -0,0 +1,100 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'lsync/run'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'lsync/backup_error'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module LSync
         
     | 
| 
      
 7 
     | 
    
         
            +
              
         
     | 
| 
      
 8 
     | 
    
         
            +
              class AbortBackupException < Exception
         
     | 
| 
      
 9 
     | 
    
         
            +
                
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
              
         
     | 
| 
      
 12 
     | 
    
         
            +
              class Action
         
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(function)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @function = function
         
     | 
| 
      
 15 
     | 
    
         
            +
                  
         
     | 
| 
      
 16 
     | 
    
         
            +
                  if @function.match(/^\%([a-z]+)(\s+.*)?$/)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @script_name = $1
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @arguments = $2
         
     | 
| 
      
 19 
     | 
    
         
            +
                  else
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @script_name = nil
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
                
         
     | 
| 
      
 24 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @function
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                def run_on_server(server, logger)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  logger.info "Running #{@function} on #{server}"
         
     | 
| 
      
 30 
     | 
    
         
            +
                  
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if server.is_local?
         
     | 
| 
      
 32 
     | 
    
         
            +
                    run_locally(server, logger)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  else
         
     | 
| 
      
 34 
     | 
    
         
            +
                    run_remotely(server, logger)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
                
         
     | 
| 
      
 38 
     | 
    
         
            +
              private
         
     | 
| 
      
 39 
     | 
    
         
            +
                def run_locally(server, logger)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  command = nil
         
     | 
| 
      
 41 
     | 
    
         
            +
                  
         
     | 
| 
      
 42 
     | 
    
         
            +
                  if @script_name
         
     | 
| 
      
 43 
     | 
    
         
            +
                    uname = `uname`.chomp.downcase
         
     | 
| 
      
 44 
     | 
    
         
            +
                    
         
     | 
| 
      
 45 
     | 
    
         
            +
                    local_path = Action.script_path(uname, @script_name)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    command = local_path.to_cmd + @arguments
         
     | 
| 
      
 47 
     | 
    
         
            +
                  else
         
     | 
| 
      
 48 
     | 
    
         
            +
                    command = @function
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  
         
     | 
| 
      
 51 
     | 
    
         
            +
                  ret = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                  Dir.chdir(server.root_path) do
         
     | 
| 
      
 53 
     | 
    
         
            +
                    ret = LSync.run_command(command, logger)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  
         
     | 
| 
      
 56 
     | 
    
         
            +
                  case(ret)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  when 0
         
     | 
| 
      
 58 
     | 
    
         
            +
                    return
         
     | 
| 
      
 59 
     | 
    
         
            +
                  when 1
         
     | 
| 
      
 60 
     | 
    
         
            +
                    raise AbortBackupException
         
     | 
| 
      
 61 
     | 
    
         
            +
                  else
         
     | 
| 
      
 62 
     | 
    
         
            +
                    raise BackupActionError
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
                
         
     | 
| 
      
 66 
     | 
    
         
            +
                def run_remotely(server, logger)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  conn = server.connect
         
     | 
| 
      
 68 
     | 
    
         
            +
                  conn.send_object([:set_working_dir, server.root_path])
         
     | 
| 
      
 69 
     | 
    
         
            +
                  
         
     | 
| 
      
 70 
     | 
    
         
            +
                  if @script_name
         
     | 
| 
      
 71 
     | 
    
         
            +
                    uname = `uname`.chomp.downcase
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    local_path = Action.script_path(uname, @script_name)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    
         
     | 
| 
      
 75 
     | 
    
         
            +
                    logger.info("Sending run_script #{@script_name}...")
         
     | 
| 
      
 76 
     | 
    
         
            +
                    conn.send_object([:run_script, @script_name, Pathname.new(local_path).read, @arguments])
         
     | 
| 
      
 77 
     | 
    
         
            +
                  else
         
     | 
| 
      
 78 
     | 
    
         
            +
                    logger.info("Sending run_command #{@function}...")
         
     | 
| 
      
 79 
     | 
    
         
            +
                    conn.send_object([:run_command, @function])
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  
         
     | 
| 
      
 82 
     | 
    
         
            +
                  conn.run do |message|
         
     | 
| 
      
 83 
     | 
    
         
            +
                    break if message == :done
         
     | 
| 
      
 84 
     | 
    
         
            +
                    
         
     | 
| 
      
 85 
     | 
    
         
            +
                    logger.send(*message)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
                
         
     | 
| 
      
 89 
     | 
    
         
            +
                def self.script_path(platform, name)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  exact_script_path(platform, name) || exact_script_path("generic", name)
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
                
         
     | 
| 
      
 93 
     | 
    
         
            +
                private
         
     | 
| 
      
 94 
     | 
    
         
            +
                def self.exact_script_path(platform, name)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  path = (Pathname.new(__FILE__).dirname + "actions" + platform + name).expand_path
         
     | 
| 
      
 96 
     | 
    
         
            +
                  path.exist? ? path : nil
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            DISKUTIL = "diskutil"
         
     | 
| 
      
 4 
     | 
    
         
            +
            action = ARGV[0]
         
     | 
| 
      
 5 
     | 
    
         
            +
            disk_name = ARGV[1]
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            def get_disk_id(name)
         
     | 
| 
      
 8 
     | 
    
         
            +
              begin
         
     | 
| 
      
 9 
     | 
    
         
            +
            	  `diskutil list`.match(/#{name}\s*\*?[0-9]+\.[0-9]+ .B\s+(disk[0-9]s[0-9])$/)[1]
         
     | 
| 
      
 10 
     | 
    
         
            +
            	rescue
         
     | 
| 
      
 11 
     | 
    
         
            +
            	  exit 5
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            if (action == 'mountpoint')
         
     | 
| 
      
 16 
     | 
    
         
            +
            	puts File.join('', 'Volumes', disk_name, ARGV[2..-1])
         
     | 
| 
      
 17 
     | 
    
         
            +
            else
         
     | 
| 
      
 18 
     | 
    
         
            +
              system DISKUTIL, action, get_disk_id(disk_name)
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,251 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # This script takes a specified directory, and removes any directories that
         
     | 
| 
      
 4 
     | 
    
         
            +
            # don't match the supplied policy. Thanks to Scott Lu and his "snapfilter"
         
     | 
| 
      
 5 
     | 
    
         
            +
            # command which made me realise how complicated my first attempt at doing this
         
     | 
| 
      
 6 
     | 
    
         
            +
            # was.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require 'time'
         
     | 
| 
      
 14 
     | 
    
         
            +
            # Required for strptime
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            class Rotation
         
     | 
| 
      
 18 
     | 
    
         
            +
            	def initialize(path, time)
         
     | 
| 
      
 19 
     | 
    
         
            +
            		@path = path
         
     | 
| 
      
 20 
     | 
    
         
            +
            		@time = time
         
     | 
| 
      
 21 
     | 
    
         
            +
            	end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            	attr :time
         
     | 
| 
      
 24 
     | 
    
         
            +
            	attr :path
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            	# Sort in reverse order by default
         
     | 
| 
      
 27 
     | 
    
         
            +
            	def <=> other
         
     | 
| 
      
 28 
     | 
    
         
            +
            		return other.time <=> @time
         
     | 
| 
      
 29 
     | 
    
         
            +
            	end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            class Period
         
     | 
| 
      
 33 
     | 
    
         
            +
            	KeepOldest = Proc.new do |t1, t2|
         
     | 
| 
      
 34 
     | 
    
         
            +
            		t1 > t2
         
     | 
| 
      
 35 
     | 
    
         
            +
            	end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            	KeepYoungest = Proc.new do |t1, t2|
         
     | 
| 
      
 38 
     | 
    
         
            +
            		t1 < t2
         
     | 
| 
      
 39 
     | 
    
         
            +
            	end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            	def initialize(count)
         
     | 
| 
      
 42 
     | 
    
         
            +
            		@count = count
         
     | 
| 
      
 43 
     | 
    
         
            +
            	end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            	def filter(values, options = {})
         
     | 
| 
      
 46 
     | 
    
         
            +
            		slots = {}
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            		keep = (options[:keep] == :youngest) ? KeepYoungest : KeepOldest
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            		values.each do |value|
         
     | 
| 
      
 51 
     | 
    
         
            +
            			time = value.time
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            			k = key(time)
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            			# We want to keep the newest backup if possible (<).
         
     | 
| 
      
 56 
     | 
    
         
            +
            			next if slots.key?(k) and keep.call(value.time, slots[k].time)
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            			slots[k] = value
         
     | 
| 
      
 59 
     | 
    
         
            +
            		end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            		sorted_values = slots.values.sort
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
            		return sorted_values[0...@count]
         
     | 
| 
      
 64 
     | 
    
         
            +
            	end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 67 
     | 
    
         
            +
            		raise ArgumentError
         
     | 
| 
      
 68 
     | 
    
         
            +
            	end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            	def mktime(year, month=1, day=1, hour=0, minute=0, second=0)
         
     | 
| 
      
 71 
     | 
    
         
            +
            		return Time.gm(year, month, day, hour, minute, second)
         
     | 
| 
      
 72 
     | 
    
         
            +
            	end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            	attr :count
         
     | 
| 
      
 75 
     | 
    
         
            +
            end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            class Hourly < Period
         
     | 
| 
      
 78 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 79 
     | 
    
         
            +
            		mktime(t.year, t.month, t.day, t.hour)
         
     | 
| 
      
 80 
     | 
    
         
            +
            	end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            class Daily < Period
         
     | 
| 
      
 84 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 85 
     | 
    
         
            +
            		mktime(t.year, t.month, t.day)
         
     | 
| 
      
 86 
     | 
    
         
            +
            	end
         
     | 
| 
      
 87 
     | 
    
         
            +
            end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            class Weekly < Period
         
     | 
| 
      
 90 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 91 
     | 
    
         
            +
            		mktime(t.year, t.month, t.day) - (t.wday * 3600 * 24)
         
     | 
| 
      
 92 
     | 
    
         
            +
            	end
         
     | 
| 
      
 93 
     | 
    
         
            +
            end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
            class Monthly < Period
         
     | 
| 
      
 96 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 97 
     | 
    
         
            +
            		mktime(t.year, t.month)
         
     | 
| 
      
 98 
     | 
    
         
            +
            	end
         
     | 
| 
      
 99 
     | 
    
         
            +
            end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
            class Quarterly < Period
         
     | 
| 
      
 102 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 103 
     | 
    
         
            +
            		mktime(t.year, (t.month - 1) / 3 * 3 + 1)
         
     | 
| 
      
 104 
     | 
    
         
            +
            	end
         
     | 
| 
      
 105 
     | 
    
         
            +
            end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
            class Yearly < Period
         
     | 
| 
      
 108 
     | 
    
         
            +
            	def key(t)
         
     | 
| 
      
 109 
     | 
    
         
            +
            		mktime(t.year)
         
     | 
| 
      
 110 
     | 
    
         
            +
            	end
         
     | 
| 
      
 111 
     | 
    
         
            +
            end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
            class Policy
         
     | 
| 
      
 114 
     | 
    
         
            +
            	def initialize
         
     | 
| 
      
 115 
     | 
    
         
            +
            		@periods = {}
         
     | 
| 
      
 116 
     | 
    
         
            +
            	end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            	def <<(period)
         
     | 
| 
      
 119 
     | 
    
         
            +
            		@periods[period.class] = period
         
     | 
| 
      
 120 
     | 
    
         
            +
            	end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
            	def filter(values, options = {})
         
     | 
| 
      
 123 
     | 
    
         
            +
            		filtered_values = Set.new
         
     | 
| 
      
 124 
     | 
    
         
            +
            		@periods.values.each do |period|
         
     | 
| 
      
 125 
     | 
    
         
            +
            			filtered_values += period.filter(values, options)
         
     | 
| 
      
 126 
     | 
    
         
            +
            		end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
            		return filtered_values.to_a, (Set.new(values) - filtered_values).to_a
         
     | 
| 
      
 129 
     | 
    
         
            +
            	end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            	attr :periods
         
     | 
| 
      
 132 
     | 
    
         
            +
            end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
            OPTIONS = {
         
     | 
| 
      
 135 
     | 
    
         
            +
            	:Format => "%Y.%m.%d-%H.%M.%S",
         
     | 
| 
      
 136 
     | 
    
         
            +
            	:Destination => "./*",
         
     | 
| 
      
 137 
     | 
    
         
            +
            	:Policy => Policy.new,
         
     | 
| 
      
 138 
     | 
    
         
            +
            	:PolicyOptions => {},
         
     | 
| 
      
 139 
     | 
    
         
            +
            	:Wet => true
         
     | 
| 
      
 140 
     | 
    
         
            +
            }
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
            ARGV.options do |o|
         
     | 
| 
      
 143 
     | 
    
         
            +
            	script_name = File.basename($0)
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            	o.set_summary_indent('  ')
         
     | 
| 
      
 146 
     | 
    
         
            +
            	o.banner = "Usage: #{script_name} [options] [directory]"
         
     | 
| 
      
 147 
     | 
    
         
            +
            	o.define_head "This script is used to prune old backups."
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            	o.separator ""
         
     | 
| 
      
 150 
     | 
    
         
            +
            	o.separator "Help and Copyright information"
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
            	o.on("-f format", String, "Set the format of the rotated directory names. See Time$strftime") do |format| 
         
     | 
| 
      
 153 
     | 
    
         
            +
            		OPTIONS[:Format] = format
         
     | 
| 
      
 154 
     | 
    
         
            +
            	end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
            	o.on("-d destination", String, "Set the directory that contains backups to prune.") do |destination|
         
     | 
| 
      
 157 
     | 
    
         
            +
            		OPTIONS[:Destination] = destination
         
     | 
| 
      
 158 
     | 
    
         
            +
            	end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
            	o.on("--dry-run", "Print out what would be deleted, but don't actually delete anything.") do
         
     | 
| 
      
 161 
     | 
    
         
            +
            		OPTIONS[:Wet] = false
         
     | 
| 
      
 162 
     | 
    
         
            +
            	end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
            	o.separator ""
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
            	o.on("--default-policy", "Sets up a typical policy retaining a reasonable number of rotations for up to 20 years.") do
         
     | 
| 
      
 167 
     | 
    
         
            +
            		OPTIONS[:Policy] << Hourly.new(24)
         
     | 
| 
      
 168 
     | 
    
         
            +
            		OPTIONS[:Policy] << Daily.new(7*4)
         
     | 
| 
      
 169 
     | 
    
         
            +
            		OPTIONS[:Policy] << Weekly.new(52)
         
     | 
| 
      
 170 
     | 
    
         
            +
            		OPTIONS[:Policy] << Monthly.new(12*3)
         
     | 
| 
      
 171 
     | 
    
         
            +
            		OPTIONS[:Policy] << Quarterly.new(4*10)
         
     | 
| 
      
 172 
     | 
    
         
            +
            		OPTIONS[:Policy] << Yearly.new(20)
         
     | 
| 
      
 173 
     | 
    
         
            +
            	end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
            	o.separator ""
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
            	o.on("--keep-oldest", "Keep older backups within the same period divsion (the default).") do
         
     | 
| 
      
 178 
     | 
    
         
            +
            		OPTIONS[:PolicyOptions][:keep] = :oldest
         
     | 
| 
      
 179 
     | 
    
         
            +
            	end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
            	o.on("--keep-youngest", "Keep younger backups within the same period division.") do
         
     | 
| 
      
 182 
     | 
    
         
            +
            		OPTIONS[:PolicyOptions][:keep] = :youngest
         
     | 
| 
      
 183 
     | 
    
         
            +
            	end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
            	o.on("--hourly count", Integer, "Set the number of hourly backups to keep.") do |count|
         
     | 
| 
      
 186 
     | 
    
         
            +
            		OPTIONS[:Policy] << Hourly.new(count)
         
     | 
| 
      
 187 
     | 
    
         
            +
            	end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
            	o.on("--daily count", Integer, "Set the number of daily backups to keep.") do |count|
         
     | 
| 
      
 190 
     | 
    
         
            +
            		OPTIONS[:Policy] << Daily.new(count)
         
     | 
| 
      
 191 
     | 
    
         
            +
            	end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            	o.on("--weekly count", Integer, "Set the number of weekly backups to keep.") do |count|
         
     | 
| 
      
 194 
     | 
    
         
            +
            		OPTIONS[:Policy] << Weekly.new(count)
         
     | 
| 
      
 195 
     | 
    
         
            +
            	end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
            	o.on("--monthly count", Integer, "Set the number of monthly backups to keep.") do |count|
         
     | 
| 
      
 198 
     | 
    
         
            +
            		OPTIONS[:Policy] << Monthly.new(count)
         
     | 
| 
      
 199 
     | 
    
         
            +
            	end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
            	o.on("--quaterly count", Integer, "Set the number of monthly backups to keep.") do |count|
         
     | 
| 
      
 202 
     | 
    
         
            +
            		OPTIONS[:Policy] << Quarterly.new(count)
         
     | 
| 
      
 203 
     | 
    
         
            +
            	end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
            	o.on("--yearly count", Integer, "Set the number of monthly backups to keep.") do |count|
         
     | 
| 
      
 206 
     | 
    
         
            +
            		OPTIONS[:Policy] << Yearly.new(count)
         
     | 
| 
      
 207 
     | 
    
         
            +
            	end
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
            	o.separator ""
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
            	o.on_tail("--copy", "Display copyright information") do
         
     | 
| 
      
 212 
     | 
    
         
            +
            		puts "#{script_name}. Copyright (c) 2008-2009 Samuel Williams. Released under the GPLv3."
         
     | 
| 
      
 213 
     | 
    
         
            +
            		puts "See http://www.oriontransfer.co.nz/ for more information."
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
            		exit
         
     | 
| 
      
 216 
     | 
    
         
            +
            	end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
            	o.on_tail("-h", "--help", "Show this help message.") do
         
     | 
| 
      
 219 
     | 
    
         
            +
            		puts o
         
     | 
| 
      
 220 
     | 
    
         
            +
            		exit
         
     | 
| 
      
 221 
     | 
    
         
            +
            	end
         
     | 
| 
      
 222 
     | 
    
         
            +
            end.parse!
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            backups = []
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
            Dir[OPTIONS[:Destination]].each do |path|
         
     | 
| 
      
 227 
     | 
    
         
            +
            	next if path.match("latest")
         
     | 
| 
      
 228 
     | 
    
         
            +
            	date_string = File.basename(path)
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
            	begin
         
     | 
| 
      
 231 
     | 
    
         
            +
            		backups << Rotation.new(path, DateTime.strptime(date_string, OPTIONS[:Format]))
         
     | 
| 
      
 232 
     | 
    
         
            +
            	rescue ArgumentError
         
     | 
| 
      
 233 
     | 
    
         
            +
            		puts "Skipping #{path}, error parsing #{date_string}: #{$!}"
         
     | 
| 
      
 234 
     | 
    
         
            +
            	end
         
     | 
| 
      
 235 
     | 
    
         
            +
            end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
            keep, erase = OPTIONS[:Policy].filter(backups)
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
            if OPTIONS[:Wet]
         
     | 
| 
      
 240 
     | 
    
         
            +
            	erase.sort.each do |backup|
         
     | 
| 
      
 241 
     | 
    
         
            +
            		puts "Erasing #{backup.path}..."
         
     | 
| 
      
 242 
     | 
    
         
            +
            		$stdout.flush
         
     | 
| 
      
 243 
     | 
    
         
            +
            		FileUtils.rm_rf(backup.path)
         
     | 
| 
      
 244 
     | 
    
         
            +
            	end
         
     | 
| 
      
 245 
     | 
    
         
            +
            else
         
     | 
| 
      
 246 
     | 
    
         
            +
            	puts "*** Dry Run ***"
         
     | 
| 
      
 247 
     | 
    
         
            +
            	puts "\tKeeping:"
         
     | 
| 
      
 248 
     | 
    
         
            +
            	keep.sort.each { |backup| puts "\t\t#{backup.path}" }
         
     | 
| 
      
 249 
     | 
    
         
            +
            	puts "\tErasing:"
         
     | 
| 
      
 250 
     | 
    
         
            +
            	erase.sort.each { |backup| puts "\t\t#{backup.path}" }
         
     | 
| 
      
 251 
     | 
    
         
            +
            end
         
     |