yolo_backup 0.0.0
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/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +43 -0
- data/README.md +11 -0
- data/Rakefile +11 -0
- data/docs/configuration_example.yml +47 -0
- data/lib/yolo_backup.rb +6 -0
- data/lib/yolo_backup/backup_runner.rb +39 -0
- data/lib/yolo_backup/backup_runner/backend.rb +6 -0
- data/lib/yolo_backup/backup_runner/backend/rsync.rb +31 -0
- data/lib/yolo_backup/backup_runner/job.rb +61 -0
- data/lib/yolo_backup/cli.rb +111 -0
- data/lib/yolo_backup/core_ext/string.rb +21 -0
- data/lib/yolo_backup/exclude_set.rb +14 -0
- data/lib/yolo_backup/helper/log.rb +9 -0
- data/lib/yolo_backup/rotation_plan.rb +24 -0
- data/lib/yolo_backup/server.rb +30 -0
- data/lib/yolo_backup/storage_pool.rb +9 -0
- data/lib/yolo_backup/storage_pool/file.rb +70 -0
- data/lib/yolo_backup/storage_pool/file/cleaner.rb +71 -0
- data/lib/yolo_backup/version.rb +3 -0
- data/yolo_backup.gemspec +28 -0
- metadata +167 -0
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
yolo-backup
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p194
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
yolo_backup (0.0.0)
|
5
|
+
activesupport (~> 3.2)
|
6
|
+
sys-filesystem (~> 1.1)
|
7
|
+
thor (~> 0.18)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (3.2.13)
|
13
|
+
i18n (= 0.6.1)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
diff-lcs (1.2.4)
|
16
|
+
ffi (1.8.1)
|
17
|
+
i18n (0.6.1)
|
18
|
+
multi_json (1.7.3)
|
19
|
+
rake (10.0.4)
|
20
|
+
rspec (2.13.0)
|
21
|
+
rspec-core (~> 2.13.0)
|
22
|
+
rspec-expectations (~> 2.13.0)
|
23
|
+
rspec-mocks (~> 2.13.0)
|
24
|
+
rspec-core (2.13.1)
|
25
|
+
rspec-expectations (2.13.0)
|
26
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
27
|
+
rspec-mocks (2.13.1)
|
28
|
+
simplecov (0.7.1)
|
29
|
+
multi_json (~> 1.0)
|
30
|
+
simplecov-html (~> 0.7.1)
|
31
|
+
simplecov-html (0.7.1)
|
32
|
+
sys-filesystem (1.1.0)
|
33
|
+
ffi
|
34
|
+
thor (0.18.1)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
rake (~> 10.0)
|
41
|
+
rspec (~> 2.12)
|
42
|
+
simplecov (~> 0.7)
|
43
|
+
yolo_backup!
|
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# yolo_backup
|
2
|
+
|
3
|
+
yolo_backup allows you to create incremental backups of multiple servers using rsync over SSH. You can specify which backups to keep (daily, weekly, monthly, …) on a per-server basis.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
### Configuration
|
8
|
+
|
9
|
+
yolo_backup automatically loads `/etc/yolo_backup.yml`. If you would like to use a different path, use the `--config` parameter.
|
10
|
+
|
11
|
+
Take a look at the [example configuration](docs/configuration_example.yml) to see what's possible.
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
storages:
|
2
|
+
default:
|
3
|
+
path: "/var/backups/%{hostname}"
|
4
|
+
external:
|
5
|
+
path: "/mnt/external/backups/%{hostname}"
|
6
|
+
|
7
|
+
rotations:
|
8
|
+
default:
|
9
|
+
daily: 10
|
10
|
+
monthly: 12
|
11
|
+
yearly: 3
|
12
|
+
extended:
|
13
|
+
hourly: 24
|
14
|
+
daily: 10
|
15
|
+
monthly: 12
|
16
|
+
yearly: 5
|
17
|
+
|
18
|
+
exclude_sets:
|
19
|
+
# be sure to define a default exclude set!
|
20
|
+
default:
|
21
|
+
- '/dev/*'
|
22
|
+
- '/proc/*'
|
23
|
+
- '/sys/*'
|
24
|
+
- '/tmp/*'
|
25
|
+
- '/run/*'
|
26
|
+
- '/mnt/*'
|
27
|
+
- '/media/*'
|
28
|
+
- '/lost+found'
|
29
|
+
alternative:
|
30
|
+
- '/dev/*'
|
31
|
+
- '/proc/*'
|
32
|
+
- '/sys/*'
|
33
|
+
- '/run/*'
|
34
|
+
- '/lost+found'
|
35
|
+
- '/var/log/*'
|
36
|
+
|
37
|
+
servers:
|
38
|
+
peter:
|
39
|
+
ssh_port: 2222
|
40
|
+
rotation: extended
|
41
|
+
excludes: # additional excludes
|
42
|
+
- '/home/dont_backup_this_user/'
|
43
|
+
nasibisibusi:
|
44
|
+
ssh_host: 'nasi.bisi.busi.example.com'
|
45
|
+
ssh_key: '/home/example/.ssh/id_dsa'
|
46
|
+
storage: external
|
47
|
+
exclude_set: alternative
|
data/lib/yolo_backup.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'yolo_backup/backup_runner/job'
|
2
|
+
require 'yolo_backup/helper/log'
|
3
|
+
|
4
|
+
module YOLOBackup
|
5
|
+
class BackupRunner
|
6
|
+
include Helper::Log
|
7
|
+
|
8
|
+
class Error < StandardError; end
|
9
|
+
class UnknownServerError < Error; end
|
10
|
+
|
11
|
+
OPTIONS = %w{servers verbose}
|
12
|
+
|
13
|
+
OPTIONS.each do |option|
|
14
|
+
attr_accessor option
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :verbose?, :verbose
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
OPTIONS.each do |option|
|
21
|
+
send("#{option}=", options[option]) if options[option]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def backup(server_name = nil)
|
26
|
+
if server_name.nil?
|
27
|
+
servers.keys.each do |server_name|
|
28
|
+
backup(server_name)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise UnknownServerError, "Server #{server_name} not defined" unless servers.key?(server_name)
|
32
|
+
server = servers[server_name]
|
33
|
+
log "Backup of #{server} requested" if verbose?
|
34
|
+
job = Job.new 'server' => server, 'verbose' => verbose?
|
35
|
+
job.start
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'yolo_backup/backup_runner/backend'
|
2
|
+
|
3
|
+
class YOLOBackup::BackupRunner::Backend::Rsync < YOLOBackup::BackupRunner::Backend
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
DEFAULT_RSYNC_OPTIONS = '-aAX --numeric-ids'
|
7
|
+
|
8
|
+
attr_reader :server
|
9
|
+
|
10
|
+
def initialize(server)
|
11
|
+
@server = server
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_backup
|
15
|
+
server.storage.initiate_backup(server) do |path|
|
16
|
+
options = [DEFAULT_RSYNC_OPTIONS]
|
17
|
+
if server.latest_backup
|
18
|
+
latest_backup = File.expand_path("#{path}/../#{server.latest_backup}")
|
19
|
+
options << "--link-dest='#{latest_backup}'"
|
20
|
+
end
|
21
|
+
ssh_options = []
|
22
|
+
ssh_options << "-i '#{server.ssh_key}'" unless server.ssh_key.nil?
|
23
|
+
ssh_options << "-p '#{server.ssh_port}'" unless server.ssh_port.nil?
|
24
|
+
options << "-e \"ssh #{ssh_options.join(' ')}\""
|
25
|
+
command = "rsync #{options.join(' ')} #{server.ssh_user || 'root'}@#{server.ssh_host || server}:/ '#{path}'"
|
26
|
+
puts command
|
27
|
+
output = `#{command}`
|
28
|
+
raise Error, "rsync command failed: #{$?}" unless $?.success?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'yolo_backup/helper/log'
|
2
|
+
require 'yolo_backup/backup_runner/backend/rsync'
|
3
|
+
|
4
|
+
module YOLOBackup
|
5
|
+
class BackupRunner
|
6
|
+
class Job
|
7
|
+
include Helper::Log
|
8
|
+
|
9
|
+
OPTIONS = %w{server verbose}
|
10
|
+
|
11
|
+
OPTIONS.each do |option|
|
12
|
+
attr_accessor option
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :verbose?, :verbose
|
16
|
+
|
17
|
+
def initialize(options)
|
18
|
+
OPTIONS.each do |option|
|
19
|
+
send("#{option}=", options[option]) if options[option]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
if backup_required?
|
25
|
+
log "Latest backup (#{server.latest_backup}) is older than maximum backup age (#{maximum_backup_age})" if verbose?
|
26
|
+
log "Starting backup of #{server}"
|
27
|
+
backend.start_backup
|
28
|
+
log "Backup completed" if verbose?
|
29
|
+
log "Cleaning up old backups" if verbose?
|
30
|
+
log "Deleted #{server.cleanup_backups.length} backups" if verbose?
|
31
|
+
else
|
32
|
+
log "Backup not required (latest backup = #{server.latest_backup}, maximum backup age = #{maximum_backup_age})" if verbose?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def backup_required?
|
37
|
+
latest_backup = server.latest_backup
|
38
|
+
return latest_backup.nil? || latest_backup < maximum_backup_age
|
39
|
+
end
|
40
|
+
|
41
|
+
def maximum_backup_age
|
42
|
+
case server.rotation.minimum_unit
|
43
|
+
when 'hourly'
|
44
|
+
1.hour.ago
|
45
|
+
when 'daily'
|
46
|
+
1.day.ago
|
47
|
+
when 'weekly'
|
48
|
+
1.week.ago
|
49
|
+
when 'monthly'
|
50
|
+
1.month.ago
|
51
|
+
when 'yearly'
|
52
|
+
1.year.ago
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def backend
|
57
|
+
@backend ||= YOLOBackup::BackupRunner::Backend::Rsync.new(server)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'yolo_backup/storage_pool/file'
|
5
|
+
require 'yolo_backup/rotation_plan'
|
6
|
+
require 'yolo_backup/backup_runner'
|
7
|
+
require 'yolo_backup/server'
|
8
|
+
require 'yolo_backup/exclude_set'
|
9
|
+
|
10
|
+
module YOLOBackup
|
11
|
+
class CLI < Thor
|
12
|
+
DEFAULT_CONFIG_FILE = '/etc/yolo_backup.yml'
|
13
|
+
DEFAULT_STORAGE_POOL_NAME = 'default'
|
14
|
+
DEFAULT_ROTATION_PLAN_NAME = 'default'
|
15
|
+
DEFAULT_EXCLUDE_SET_NAME = 'default'
|
16
|
+
|
17
|
+
class_option :verbose, :type => :boolean
|
18
|
+
class_option :config, :type => :string, :banner => "path to config file (default #{DEFAULT_CONFIG_FILE})"
|
19
|
+
|
20
|
+
desc 'status', 'Display backup status and statistics'
|
21
|
+
def status(storage = nil)
|
22
|
+
storage_pools.values.each do |storage_pool|
|
23
|
+
puts "#{storage_pool}:"
|
24
|
+
if storage_pool.ready?
|
25
|
+
storage_pool.statistics.tap do |stats|
|
26
|
+
puts " Capacity: #{'%4d' % (stats[:capacity] / 1000 / 1000 / 1000)} GB"
|
27
|
+
puts " Available: #{'%4d' % (stats[:available] / 1000 / 1000 / 1000)} GB"
|
28
|
+
end
|
29
|
+
else
|
30
|
+
puts " STORAGE NOT READY" unless storage_pool.ready?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'backup [SERVER]', 'Start backup process'
|
36
|
+
def backup(server = nil)
|
37
|
+
backup_runner = BackupRunner.new('servers' => servers, 'verbose' => verbose?)
|
38
|
+
backup_runner.backup(server)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def verbose?
|
43
|
+
!!options[:verbose]
|
44
|
+
end
|
45
|
+
|
46
|
+
def config
|
47
|
+
@config ||= YAML::load(File.open(options[:config] || DEFAULT_CONFIG_FILE))
|
48
|
+
end
|
49
|
+
|
50
|
+
def storage_pools
|
51
|
+
return @storage_pools unless @storage_pools.nil?
|
52
|
+
|
53
|
+
(@storage_pools = {}).tap do |storage_pools|
|
54
|
+
config['storages'].each do |name, options|
|
55
|
+
storage_pools[name] = StoragePool::File.new(name, options)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def rotation_plans
|
61
|
+
return @rotation_plans unless @rotation_plans.nil?
|
62
|
+
|
63
|
+
(@rotation_plans = {}).tap do |rotation_plans|
|
64
|
+
config['rotations'].each do |name, options|
|
65
|
+
rotation_plans[name] = RotationPlan.new(name, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def exclude_sets
|
71
|
+
return @exclude_sets unless @exclude_sets.nil?
|
72
|
+
|
73
|
+
(@exclude_sets = {}).tap do |exclude_sets|
|
74
|
+
config['exclude_sets'].each do |name, options|
|
75
|
+
exclude_sets[name] = ExcludeSet.new(name, options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def servers
|
81
|
+
return @servers unless @servers.nil?
|
82
|
+
|
83
|
+
(@servers = {}).tap do |servers|
|
84
|
+
config['servers'].each do |name, options|
|
85
|
+
options['storage'] = storage_by_name(options['storage'])
|
86
|
+
options['rotation'] = rotation_by_name(options['rotation'])
|
87
|
+
options['excludes'] = (options['excludes'] || []) + exlude_set_by_name(options['exclude_set']).excludes
|
88
|
+
servers[name] = Server.new(name, options)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def storage_by_name(name = nil)
|
94
|
+
name ||= DEFAULT_STORAGE_POOL_NAME
|
95
|
+
raise "Storage #{name} not defined" unless storage_pools.key?(name)
|
96
|
+
storage_pools[name]
|
97
|
+
end
|
98
|
+
|
99
|
+
def rotation_by_name(name = nil)
|
100
|
+
name ||= DEFAULT_ROTATION_PLAN_NAME
|
101
|
+
raise "Rotation plan #{name} not defined" unless rotation_plans.key?(name)
|
102
|
+
rotation_plans[name]
|
103
|
+
end
|
104
|
+
|
105
|
+
def exlude_set_by_name(name = nil)
|
106
|
+
name ||= DEFAULT_EXCLUDE_SET_NAME
|
107
|
+
raise "Exclude set #{name} not defined" unless exclude_sets.key?(name)
|
108
|
+
exclude_sets[name]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class String
|
2
|
+
INTERPOLATION_PATTERN = Regexp.union(
|
3
|
+
/%%/,
|
4
|
+
/%\{(\w+)\}/
|
5
|
+
)
|
6
|
+
def interpolate(values)
|
7
|
+
self.gsub(INTERPOLATION_PATTERN) do |match|
|
8
|
+
if match == '%%'
|
9
|
+
'%'
|
10
|
+
else
|
11
|
+
key = ($1 || $2).to_sym
|
12
|
+
unless values.key?(key)
|
13
|
+
raise "Mission interpolation argument: #{key}"
|
14
|
+
end
|
15
|
+
value = values[key]
|
16
|
+
value = value.call(values) if value.respond_to?(:call)
|
17
|
+
$3 ? sprintf("%#{$3}", value) : value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module YOLOBackup
|
2
|
+
class RotationPlan
|
3
|
+
SCHEDULE_OPTIONS = %w{ hourly daily weekly monthly yearly }
|
4
|
+
|
5
|
+
SCHEDULE_OPTIONS.each do |option|
|
6
|
+
attr_accessor option
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name, options)
|
12
|
+
@name = name
|
13
|
+
SCHEDULE_OPTIONS.each do |option|
|
14
|
+
send("#{option}=", options[option]) if options.key?(option)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def minimum_unit
|
19
|
+
SCHEDULE_OPTIONS.each do |option|
|
20
|
+
return option if send(option).to_i > 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module YOLOBackup
|
2
|
+
class Server
|
3
|
+
OPTIONS = %w{ excludes rotation ssh_host ssh_key ssh_port ssh_user storage }
|
4
|
+
|
5
|
+
OPTIONS.each do |option|
|
6
|
+
attr_accessor option
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name, options)
|
12
|
+
@name = name
|
13
|
+
OPTIONS.each do |option|
|
14
|
+
send("#{option}=", options[option]) if options.key?(option)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def latest_backup
|
19
|
+
storage.latest_backup(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cleanup_backups
|
23
|
+
storage.cleanup(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'sys/filesystem'
|
3
|
+
|
4
|
+
require 'yolo_backup/storage_pool'
|
5
|
+
require 'yolo_backup/core_ext/string'
|
6
|
+
|
7
|
+
class YOLOBackup::StoragePool::File < YOLOBackup::StoragePool
|
8
|
+
require 'yolo_backup/storage_pool/file/cleaner'
|
9
|
+
|
10
|
+
OPTIONS = %w{ path }
|
11
|
+
|
12
|
+
attr_accessor :path
|
13
|
+
|
14
|
+
def initialize(name, options = nil)
|
15
|
+
super
|
16
|
+
OPTIONS.each do |key|
|
17
|
+
send("#{key}=", options[key]) if options[key]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{name} (#{wildcard_path})"
|
23
|
+
end
|
24
|
+
|
25
|
+
def statistics
|
26
|
+
stats = Sys::Filesystem.stat(base_path)
|
27
|
+
{
|
28
|
+
capacity: stats.blocks * stats.block_size,
|
29
|
+
available: stats.blocks_available * stats.block_size
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def ready?
|
34
|
+
::File.directory?(base_path) && ::File.readable?(base_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def latest_backup(server)
|
38
|
+
server_path = server_path(server)
|
39
|
+
return nil unless ::File.directory?(server_path) && ::File.symlink?("#{server_path}/latest") && ::File.directory?("#{server_path}/latest")
|
40
|
+
target = ::File.basename(::File.readlink("#{server_path}/latest"))
|
41
|
+
Time.parse(target)
|
42
|
+
end
|
43
|
+
|
44
|
+
def initiate_backup(server, &block)
|
45
|
+
server_path = server_path(server)
|
46
|
+
path = "#{server_path}/#{Time.now.iso8601}/"
|
47
|
+
yield(path)
|
48
|
+
latest_path = "#{server_path}/latest"
|
49
|
+
::File.unlink(latest_path) if ::File.symlink?(latest_path)
|
50
|
+
::File.symlink(path, latest_path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def cleanup(server)
|
54
|
+
cleaner = Cleaner.new(self, server)
|
55
|
+
cleaner.cleanup
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def wildcard_path
|
60
|
+
path.interpolate :hostname => '*'
|
61
|
+
end
|
62
|
+
|
63
|
+
def base_path
|
64
|
+
path.interpolate(:hostname => '')
|
65
|
+
end
|
66
|
+
|
67
|
+
def server_path(hostname)
|
68
|
+
path.interpolate(:hostname => hostname)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require 'yolo_backup/storage_pool/file'
|
4
|
+
|
5
|
+
class YOLOBackup::StoragePool::File::Cleaner
|
6
|
+
attr_reader :storage_pool, :server
|
7
|
+
|
8
|
+
def initialize(storage_pool, server)
|
9
|
+
@storage_pool = storage_pool
|
10
|
+
@server = server
|
11
|
+
end
|
12
|
+
|
13
|
+
def cleanup
|
14
|
+
unused_backups = all_backups - required_backups
|
15
|
+
unused_backups.each do |unused_backup|
|
16
|
+
path = "#{server_path}/#{unused_backup.iso8601}"
|
17
|
+
p path
|
18
|
+
FileUtils.rm_r path, :force => true
|
19
|
+
end
|
20
|
+
unused_backups
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def required_backups
|
25
|
+
required_backups = {}
|
26
|
+
all_backups.each do |date|
|
27
|
+
{
|
28
|
+
:year => :yearly,
|
29
|
+
:month => :monthly,
|
30
|
+
:week => :weekly,
|
31
|
+
:day => :daily,
|
32
|
+
:hour => :hourly
|
33
|
+
}.each do |accuracy, schedule_option_name|
|
34
|
+
keep_count = server.rotation.send("#{schedule_option_name}").to_i
|
35
|
+
next if keep_count.nil? || keep_count.zero?
|
36
|
+
if required_backups[accuracy].nil?
|
37
|
+
required_backups[accuracy] = [date]
|
38
|
+
elsif !same_date(date, required_backups[accuracy].last, accuracy)
|
39
|
+
required_backups[accuracy] << date
|
40
|
+
end
|
41
|
+
if required_backups[accuracy].length > keep_count
|
42
|
+
required_backups[accuracy].shift
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
required_backups = required_backups.values.flatten
|
47
|
+
required_backups << server.latest_backup
|
48
|
+
required_backups.uniq
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_backups
|
52
|
+
backups = []
|
53
|
+
Dir.chdir(server_path) do
|
54
|
+
Dir.entries('.').sort.each do |file|
|
55
|
+
next if ['.', '..'].include?(file)
|
56
|
+
next unless ::File.directory?(file) && !::File.symlink?(file)
|
57
|
+
backups << Time.parse(file)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
backups
|
61
|
+
end
|
62
|
+
|
63
|
+
def server_path
|
64
|
+
storage_pool.path.interpolate(:hostname => server)
|
65
|
+
end
|
66
|
+
|
67
|
+
def same_date(time1, time2, accuracy)
|
68
|
+
end_of = "end_of_#{accuracy}"
|
69
|
+
time1.send(end_of) == time2.send(end_of)
|
70
|
+
end
|
71
|
+
end
|
data/yolo_backup.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'yolo_backup/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'yolo_backup'
|
7
|
+
s.version = YOLOBackup::VERSION
|
8
|
+
s.authors = ['Nils Caspar']
|
9
|
+
s.email = 'ncaspar@me.com'
|
10
|
+
s.homepage = 'http://github.org/pencil/yolo_backup'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.summary = 'A simple Ruby script to create incremental backups of multiple servers using rsync over SSH.'
|
13
|
+
s.description = 'yolo_backup allows you to create incremental backups of multiple servers using rsync over SSH. You can specify which backups to keep (daily, weekly, monthly, …) on a per-server basis.'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
21
|
+
s.add_development_dependency 'rspec', '~> 2.12'
|
22
|
+
s.add_development_dependency 'simplecov', '~> 0.7'
|
23
|
+
|
24
|
+
s.add_dependency 'thor', '~> 0.18'
|
25
|
+
s.add_dependency 'sys-filesystem', '~> 1.1'
|
26
|
+
s.add_dependency 'activesupport', '~> 3.2'
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yolo_backup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nils Caspar
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '10.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '10.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.12'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.12'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: simplecov
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.7'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.7'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: thor
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.18'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0.18'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: sys-filesystem
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.1'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.1'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: activesupport
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '3.2'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.2'
|
110
|
+
description: yolo_backup allows you to create incremental backups of multiple servers
|
111
|
+
using rsync over SSH. You can specify which backups to keep (daily, weekly, monthly,
|
112
|
+
…) on a per-server basis.
|
113
|
+
email: ncaspar@me.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .ruby-gemset
|
119
|
+
- .ruby-version
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- docs/configuration_example.yml
|
125
|
+
- lib/yolo_backup.rb
|
126
|
+
- lib/yolo_backup/backup_runner.rb
|
127
|
+
- lib/yolo_backup/backup_runner/backend.rb
|
128
|
+
- lib/yolo_backup/backup_runner/backend/rsync.rb
|
129
|
+
- lib/yolo_backup/backup_runner/job.rb
|
130
|
+
- lib/yolo_backup/cli.rb
|
131
|
+
- lib/yolo_backup/core_ext/string.rb
|
132
|
+
- lib/yolo_backup/exclude_set.rb
|
133
|
+
- lib/yolo_backup/helper/log.rb
|
134
|
+
- lib/yolo_backup/rotation_plan.rb
|
135
|
+
- lib/yolo_backup/server.rb
|
136
|
+
- lib/yolo_backup/storage_pool.rb
|
137
|
+
- lib/yolo_backup/storage_pool/file.rb
|
138
|
+
- lib/yolo_backup/storage_pool/file/cleaner.rb
|
139
|
+
- lib/yolo_backup/version.rb
|
140
|
+
- yolo_backup.gemspec
|
141
|
+
homepage: http://github.org/pencil/yolo_backup
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 1.8.23
|
163
|
+
signing_key:
|
164
|
+
specification_version: 3
|
165
|
+
summary: A simple Ruby script to create incremental backups of multiple servers using
|
166
|
+
rsync over SSH.
|
167
|
+
test_files: []
|