Class: S3TarBackup::Backup

Inherits:
Object
  • Object
show all
Defined in:
lib/s3_tar_backup/backup.rb

Constant Summary collapse

COMPRESSIONS =
{
  :none => {:flag => '', :ext => 'tar'},
  :gzip => {:flag => '-z', :ext => 'tar.gz'},
  :bzip2 => {:flag => '-j', :ext => 'tar.bz2'},
  :lzma => {:flag => '--lzma', :ext => 'tar.lzma'},
  :lzma2 => {:flag => '-J', :ext => 'tar.xz'}
}
ENCRYPTED_EXTENSIONS =
{ :gpg_key => 'asc', :password_file => 'gpg' }
PASSPHRASE_CIPHER_ALGO =
'AES256'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(backup_dir, name, sources, exclude, compression = :bzip2, encryption = nil) ⇒ Backup

Returns a new instance of Backup.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/s3_tar_backup/backup.rb', line 28

def initialize(backup_dir, name, sources, exclude, compression=:bzip2, encryption=nil)
  @backup_dir, @name = backup_dir, name
  raise "Unknown compression #{compression}. Valid options are #{COMPRESSIONS.keys.join(', ')}" unless COMPRESSIONS.has_key?(compression)
  @compression_flag = COMPRESSIONS[compression][:flag]
  @compression_ext = COMPRESSIONS[compression][:ext]
  @time = Time.now
  @encryption = encryption

  @sources = [*sources].map{ |x| x.gsub('\\', '/') }
  @exclude = [*exclude].map{ |x| x.gsub('\\', '/') }

  # If the backup dir is inside any of the sources, exclude it
  absolute_backup_dir = File.absolute_path(backup_dir)
  # I cannot for the life of me get tar to accept absolute paths to directories. Passing a path relative to the CWD seems to work though
  @exclude.push(*@sources.select{ |x| absolute_backup_dir.start_with?(File.absolute_path(x)) }.map{ |x| Pathname.new(absolute_backup_dir).relative_path_from(Pathname.new(Dir.pwd)).to_s })

  FileUtils.mkdir_p(@backup_dir) unless File.directory?(@backup_dir)
end

Class Method Details

.parse_object(object, profile) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/s3_tar_backup/backup.rb', line 84

def self.parse_object(object, profile)
  name = File.basename(object.path)
  match = name.match(/^backup-([\w\-]+)-(\d\d\d\d)(\d\d)(\d\d)_(\d\d)(\d\d)(\d\d)-(\w+)\.(.*?)(?:\.(#{ENCRYPTED_EXTENSIONS.values.join('|')}))?$/)
  return nil unless match && match[1] == profile

  return {
    :type => match[8].to_sym,
    :date => Time.new(match[2].to_i, match[3].to_i, match[4].to_i, match[5].to_i, match[6].to_i, match[7].to_i),
    :name => name,
    :ext => match[9],
    :size => object.size,
    :profile => match[1],
    :compression => COMPRESSIONS.find{ |k,v| v[:ext] == match[9] }[0],
    :encryption => match[10].nil? ? nil : ENCRYPTED_EXTENSIONS.key(match[10])
  }
end

.restore_cmd(restore_into, restore_from, verbose = false, password_file = nil) ⇒ Object

No real point in creating a whole new class for this one



102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/s3_tar_backup/backup.rb', line 102

def self.restore_cmd(restore_into, restore_from, verbose=false, password_file=nil)
  ext, encryption_ext = restore_from.match(/[^\.\\\/]+\.(.*?)(?:\.(#{ENCRYPTED_EXTENSIONS.values.join('|')}))?$/)[1..2]
  encryption = ENCRYPTED_EXTENSIONS.key(encryption_ext)
  compression_flag = COMPRESSIONS.find{ |k,v| v[:ext] == ext }[1][:flag]
  tar_archive = encryption ? '' : "f \"#{restore_from}\""
  gpg_cmd = encryption.nil? ? '' : case encryption
  when :gpg_key
    "gpg --yes -d \"#{restore_from}\" | "
  when :password_file
    flag = password_file && !password_file.empty? ? " --passphrase-file \"#{password_file}\"" : ''
    "gpg --yes#{flag} -d \"#{restore_from}\" | "
  end
  "#{gpg_cmd}tar xp#{verbose ? 'v' : ''}#{tar_archive} #{compression_flag} -G -C #{restore_into}"
end

Instance Method Details

#archiveObject



63
64
65
66
67
68
# File 'lib/s3_tar_backup/backup.rb', line 63

def archive
  return @archive if @archive
  type = snar_exists? ? 'incr' : 'full'
  encrypted_bit = @encryption ? ".#{ENCRYPTED_EXTENSIONS[@encryption[:type]]}" : ''
  File.join(@backup_dir, "backup-#{@name}-#{@time.strftime('%Y%m%d_%H%M%S')}-#{type}.#{@compression_ext}#{encrypted_bit}")
end

#backup_cmd(verbose = false) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/s3_tar_backup/backup.rb', line 70

def backup_cmd(verbose=false)
  exclude = @exclude.map{ |e| " --exclude \"#{e}\""}.join
  sources = @sources.map{ |s| "\"#{s}\""}.join(' ')
  @archive = archive
  tar_archive = @encryption ? '' : "f \"#{@archive}\""
  gpg_cmd = @encryption.nil? ? '' : case @encryption[:type]
  when :gpg_key
    " | gpg -r #{@encryption[:gpg_key]} -o \"#{@archive}\" --always-trust --yes --batch --no-tty -e"
  when :password_file
    " | gpg -c --passphrase-file \"#{@encryption[:password_file]}\" --cipher-algo #{PASSPHRASE_CIPHER_ALGO} -o \"#{@archive}\" --batch --yes --no-tty"
  end
  "tar c#{verbose ? 'v' : ''}#{tar_archive} #{@compression_flag} -g \"#{tmp_snar_path}\"#{exclude} --no-check-device #{sources}#{gpg_cmd}"
end

#snarObject



47
48
49
# File 'lib/s3_tar_backup/backup.rb', line 47

def snar
  "backup-#{@name}.snar"
end

#snar_exists?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/s3_tar_backup/backup.rb', line 59

def snar_exists?
  File.exists?(snar_path)
end

#snar_pathObject



51
52
53
# File 'lib/s3_tar_backup/backup.rb', line 51

def snar_path
  File.join(@backup_dir, snar)
end

#tmp_snar_pathObject



55
56
57
# File 'lib/s3_tar_backup/backup.rb', line 55

def tmp_snar_path
  snar_path + '.tmp'
end