incremental_backup 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Guardfile +11 -0
- data/README.md +16 -8
- data/Rakefile +3 -0
- data/incremental_backup.gemspec +10 -2
- data/lib/incremental_backup/lock.rb +56 -0
- data/lib/incremental_backup/rsync.rb +62 -0
- data/lib/incremental_backup/task.rb +166 -0
- data/lib/incremental_backup/task_settings.rb +45 -0
- data/lib/incremental_backup/version.rb +1 -1
- data/lib/incremental_backup.rb +5 -5
- data/live_example/Gemfile +4 -0
- data/live_example/example_task.rb +36 -0
- data/live_example/exclude.file +11 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/unit/task_settings_spec.rb +8 -0
- data/spec/unit/task_spec.rb +62 -0
- data/spec/unit/version_spec.rb +7 -0
- metadata +135 -7
data/.gitignore
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,23 +1,31 @@
|
|
1
|
-
#
|
1
|
+
# incremental\_backup
|
2
2
|
|
3
|
-
|
3
|
+
Creates incremental backups via ssh and rsync.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Description
|
6
6
|
|
7
|
-
|
7
|
+
incremental\_backup is a tool to easily create incremental backups to a local directory or to a remote server using ssh and rsync.
|
8
8
|
|
9
|
-
|
9
|
+
The backup is by default performed every hour of a determined set of files and folders. The backup is easily restorable from every hour in the last day, every day in the last week, every week in the last month, every month in the past three months and every year forever. These default settings can be changed to suit your needs.
|
10
10
|
|
11
|
-
|
11
|
+
## Dependencies
|
12
12
|
|
13
|
-
|
13
|
+
* Ruby (tested on 1.9.3)
|
14
|
+
* ssh
|
15
|
+
* rsync
|
14
16
|
|
15
|
-
|
17
|
+
## Installation
|
16
18
|
|
17
19
|
$ gem install incremental_backup
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
23
|
+
The recommended usage is to create a small ruby application with a gemfile. Take a look at [the example](https://github.com/lasseebert/incremental_backup/tree/master/live_example).
|
24
|
+
|
25
|
+
## Cron
|
26
|
+
It's recommended to setup a cron job to handle scheduling of the backup.
|
27
|
+
|
28
|
+
The cron job can be triggered as often as you would like to, the script still only at most perform one backup per hour. This is useful if your computer is usually not online for more than an hour.
|
21
29
|
|
22
30
|
## Contributing
|
23
31
|
|
data/Rakefile
CHANGED
data/incremental_backup.gemspec
CHANGED
@@ -10,10 +10,18 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.email = ["lasseebert@gmail.com"]
|
11
11
|
gem.description = %q{(NOT YET WORKING...) incremental_backup can make incremental backups by hour/day/week/month remotely through ssh and rsync}
|
12
12
|
gem.summary = %q{(NOT YET WORKING...) incremental_backup can make incremental backups by hour/day/week/month remotely through ssh and rsync}
|
13
|
-
gem.homepage =
|
13
|
+
gem.homepage = 'https://github.com/lasseebert/incremental_backup'
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
17
|
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_development_dependency 'rspec', '~> 2.12'
|
20
|
+
gem.add_development_dependency 'rspec-mocks', '~> 2.12'
|
21
|
+
gem.add_development_dependency 'guard-rspec', '~> 2.4'
|
22
|
+
gem.add_development_dependency 'rb-inotify', '~> 0.8.8'
|
23
|
+
gem.add_development_dependency 'rake', '~> 10.0.3'
|
24
|
+
|
25
|
+
gem.add_dependency 'active_attr', '~> 0.7'
|
26
|
+
gem.add_dependency 'net-ssh', '~> 2.6'
|
19
27
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module IncrementalBackup
|
4
|
+
class Lock
|
5
|
+
|
6
|
+
LOCK_FILENAME = 'lock_{task_id}'
|
7
|
+
|
8
|
+
attr_accessor :failed
|
9
|
+
|
10
|
+
# Should not be called from other places than Lock.create
|
11
|
+
def initialize task
|
12
|
+
@task = task
|
13
|
+
|
14
|
+
if File.exists? path
|
15
|
+
self.failed = true
|
16
|
+
else
|
17
|
+
FileUtils.touch path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Obtains lock, run block, release lock
|
22
|
+
def self.create task, &block
|
23
|
+
task.logger.info 'Obtaining lock...'
|
24
|
+
|
25
|
+
lock = Lock.new task
|
26
|
+
|
27
|
+
if lock.failed
|
28
|
+
task.logger.info 'Lock can not be obtained. Exiting!'
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
task.logger.info 'Obtained lock'
|
33
|
+
|
34
|
+
begin
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
task.logger.info 'Releasing lock...'
|
38
|
+
lock.release
|
39
|
+
task.logger.info 'Released lock'
|
40
|
+
end
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Release lock
|
46
|
+
def release
|
47
|
+
FileUtils.rm path
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def path
|
53
|
+
@path ||= File.join @task.settings.settings_path, LOCK_FILENAME.sub(/\{task_id\}/, @task.settings.task_id)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'pty'
|
2
|
+
|
3
|
+
module IncrementalBackup
|
4
|
+
class Rsync
|
5
|
+
def self.execute(logger, local_path, remote_path, options)
|
6
|
+
# TODO: Only use long options
|
7
|
+
rsync_options = {
|
8
|
+
"-azprvP" => nil,
|
9
|
+
"--delete" => nil,
|
10
|
+
"--delete-excluded" => nil,
|
11
|
+
"--modify-window" => '2',
|
12
|
+
"--force" => nil,
|
13
|
+
"--ignore-errors" => nil,
|
14
|
+
"--stats" => nil
|
15
|
+
}
|
16
|
+
rsync_options["--exclude-from"] = options[:exclude_file] if options[:exclude_file]
|
17
|
+
rsync_options["--link-dest"] = options[:link_dest] if options[:link_dest]
|
18
|
+
|
19
|
+
rsync_options = rsync_options.map{|key, value| "#{key}#{value ? "=#{value}" : ''}" }.join(' ')
|
20
|
+
|
21
|
+
rsync_command = "rsync #{rsync_options} -e ssh #{local_path} #{remote_path}"
|
22
|
+
if options[:max_download_speed] || options[:max_upload_speed]
|
23
|
+
trickle = "trickle"
|
24
|
+
trickle += " -d #{options[:max_download_speed]}" if options[:max_download_speed]
|
25
|
+
trickle += " -u #{options[:max_upload_speed]}" if options[:max_upload_speed]
|
26
|
+
rsync_command = "#{trickle} #{rsync_command}"
|
27
|
+
end
|
28
|
+
|
29
|
+
execute_shell logger, rsync_command
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.execute_shell(logger, command)
|
36
|
+
begin
|
37
|
+
last_line = nil
|
38
|
+
PTY.spawn(command) do |stdout, stdin, pid|
|
39
|
+
begin
|
40
|
+
stdout.each do |line|
|
41
|
+
logger.info line.gsub(/\n/, '')
|
42
|
+
last_line = line
|
43
|
+
end
|
44
|
+
rescue Errno::EIO => e
|
45
|
+
# Can't tell the difference between process being killed
|
46
|
+
# or process ended because it was done
|
47
|
+
unless last_line =~ /total size is/
|
48
|
+
logger.error "Errno::EIO"
|
49
|
+
logger.error e
|
50
|
+
raise e
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue PTY::ChildExited => e
|
55
|
+
logger.error "PTY::ChildExited"
|
56
|
+
logger.error e
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'net/ssh'
|
3
|
+
|
4
|
+
module IncrementalBackup
|
5
|
+
class Task
|
6
|
+
|
7
|
+
LOG_FILENAME = 'log'
|
8
|
+
NUM_LOG_FILES = 10
|
9
|
+
|
10
|
+
attr_accessor :settings
|
11
|
+
|
12
|
+
def initialize(&block)
|
13
|
+
self.settings = TaskSettings.new
|
14
|
+
yield settings
|
15
|
+
end
|
16
|
+
|
17
|
+
# Perform the backup
|
18
|
+
def run
|
19
|
+
|
20
|
+
# Validate - this will throw an exception if settings are not valid
|
21
|
+
validate_settings
|
22
|
+
|
23
|
+
# Run everything inside a lock, ensuring that only one instance of this
|
24
|
+
# task is running.
|
25
|
+
Lock.create(self) do
|
26
|
+
|
27
|
+
# Find the schedule to run
|
28
|
+
schedule = find_schedule
|
29
|
+
unless schedule
|
30
|
+
logger.info "No backup needed - exiting"
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
logger.info "Starting #{schedule} backup to #{settings.remote_server}"
|
35
|
+
|
36
|
+
# Paths and other options
|
37
|
+
timestamp = Time.now.strftime('backup_%Y-%m-%d-T%H-%M-%S')
|
38
|
+
current_path = File.join(settings.remote_path, 'current')
|
39
|
+
progress_path = File.join(settings.remote_path, 'incomplete')
|
40
|
+
complete_path = File.join(schedule_path(schedule), timestamp)
|
41
|
+
login = "#{settings.remote_user}@#{settings.remote_server}"
|
42
|
+
rsync_path = "#{login}:#{progress_path}"
|
43
|
+
|
44
|
+
# Make schedule folder
|
45
|
+
execute_ssh "mkdir --verbose --parents #{schedule_path schedule}"
|
46
|
+
|
47
|
+
# Rsync
|
48
|
+
Rsync.execute(logger, settings.local_path, rsync_path, {
|
49
|
+
exclude_file: settings.exclude_file,
|
50
|
+
link_dest: current_path,
|
51
|
+
max_upload_speed: settings.max_upload_speed,
|
52
|
+
max_download_speed: settings.max_download_speed
|
53
|
+
})
|
54
|
+
|
55
|
+
# shuffle backups around
|
56
|
+
logger.info "Do the backup shuffle"
|
57
|
+
execute_ssh [
|
58
|
+
"mv --verbose #{progress_path} #{complete_path}",
|
59
|
+
"rm --verbose --force #{current_path}",
|
60
|
+
"ln --verbose --symbolic #{complete_path} #{current_path}",
|
61
|
+
]
|
62
|
+
|
63
|
+
delete_old_backups schedule
|
64
|
+
|
65
|
+
logger.info "#{schedule} backup done"
|
66
|
+
end
|
67
|
+
|
68
|
+
rescue Exception => exception
|
69
|
+
logger.error exception.message
|
70
|
+
logger.error exception.backtrace
|
71
|
+
end
|
72
|
+
|
73
|
+
def logger
|
74
|
+
@logger ||= Logger.new(File.join(settings.settings_path, LOG_FILENAME), NUM_LOG_FILES)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def schedule_path(schedule)
|
80
|
+
File.join(settings.remote_path, schedule.to_s)
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_old_backups(schedule)
|
84
|
+
backups = list_backup_dir schedule
|
85
|
+
backups_to_keep = settings.send("#{schedule}_backups")
|
86
|
+
backups.sort!
|
87
|
+
backups_to_delete = backups - backups.last(backups_to_keep)
|
88
|
+
if backups_to_delete.any?
|
89
|
+
if backups_to_delete.length == 1
|
90
|
+
logger.info "Deleting old backup #{backups_to_delete.first}"
|
91
|
+
else
|
92
|
+
logger.info "Deleting #{backups_to_delete.length} old backups"
|
93
|
+
end
|
94
|
+
execute_ssh(backups_to_delete.map { |path| "rm --force --recursive #{path}" })
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def list_backup_dir(schedule)
|
99
|
+
execute_ssh("mkdir -p #{schedule_path schedule}")
|
100
|
+
execute_ssh("find #{schedule_path schedule} -maxdepth 1 -mindepth 1").split("\n")
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_settings
|
104
|
+
unless settings.valid?
|
105
|
+
logger.error "Invalid settings:"
|
106
|
+
settings.errors.full_messages.each do |message|
|
107
|
+
logger.error message
|
108
|
+
end
|
109
|
+
throw "Invalid settings. Check the log file"
|
110
|
+
end
|
111
|
+
logger.info "Settings validated"
|
112
|
+
end
|
113
|
+
|
114
|
+
# Runs one ore more commands remotely via ssh
|
115
|
+
def execute_ssh(commands)
|
116
|
+
commands = [commands] unless commands.is_a? Array
|
117
|
+
result = ""
|
118
|
+
Net::SSH.start settings.remote_server, settings.remote_user do |ssh|
|
119
|
+
commands.each do |command|
|
120
|
+
was_error = false
|
121
|
+
logger.info "ssh: #{command}"
|
122
|
+
ssh.exec! command do |channel, stream, data|
|
123
|
+
case stream
|
124
|
+
when :stdout
|
125
|
+
logger.info data
|
126
|
+
result += "#{data}\n" unless data.empty?
|
127
|
+
when :stderr
|
128
|
+
logger.error data
|
129
|
+
was_error = true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
throw "Exception during ssh, look in log file" if was_error
|
133
|
+
end
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
# Find out which schedule to run
|
139
|
+
def find_schedule
|
140
|
+
minutes = {
|
141
|
+
# If a cron job is run hourly it can be off by a couple of seconds
|
142
|
+
# from the last run. Set hourly to 58 minutes
|
143
|
+
hourly: 58,
|
144
|
+
daily: 24*60,
|
145
|
+
weekly: 7*24*60,
|
146
|
+
monthly: 30*24*60,
|
147
|
+
yearly: 365*24*60
|
148
|
+
}
|
149
|
+
|
150
|
+
now = DateTime.now
|
151
|
+
[:yearly, :monthly, :weekly, :daily, :hourly].each do |schedule|
|
152
|
+
list = list_backup_dir schedule
|
153
|
+
date = list.map { |path| parse_backup_dir_name path, now.offset }.max
|
154
|
+
return schedule if !date || (now - date) * 24 * 60 >= minutes[schedule]
|
155
|
+
end
|
156
|
+
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def parse_backup_dir_name(dir, offset)
|
161
|
+
if dir =~ /(\d{4})-(\d{2})-(\d{2})-T(\d{2})-(\d{2})-(\d{2})$/
|
162
|
+
DateTime.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, offset)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'active_attr'
|
2
|
+
module IncrementalBackup
|
3
|
+
class TaskSettings
|
4
|
+
include ActiveAttr::Model
|
5
|
+
|
6
|
+
# Max backups to keep
|
7
|
+
attribute :hourly_backups, default: 24
|
8
|
+
attribute :daily_backups, default: 7
|
9
|
+
attribute :weekly_backups, default: 5
|
10
|
+
attribute :monthly_backups, default: 12
|
11
|
+
attribute :yearly_backups, default: 100
|
12
|
+
|
13
|
+
# Unique task id, must be a valid file name
|
14
|
+
attribute :task_id
|
15
|
+
|
16
|
+
# Path to store log file, lock file and more
|
17
|
+
attribute :settings_path
|
18
|
+
|
19
|
+
# Path to the exclude file, containing all files and dirs not to include
|
20
|
+
attribute :exclude_file
|
21
|
+
|
22
|
+
# Paths
|
23
|
+
attribute :local_path
|
24
|
+
attribute :remote_path
|
25
|
+
|
26
|
+
# Login
|
27
|
+
attribute :remote_server
|
28
|
+
attribute :remote_user
|
29
|
+
|
30
|
+
# Throttle
|
31
|
+
attribute :max_upload_speed
|
32
|
+
attribute :max_download_speed
|
33
|
+
|
34
|
+
# Validation
|
35
|
+
validates :task_id, presence: true
|
36
|
+
validates :settings_path, presence: true
|
37
|
+
validates :remote_server, presence: true
|
38
|
+
validates :local_path, presence: true
|
39
|
+
validates :remote_path, presence: true
|
40
|
+
validates :remote_user, presence: true
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/incremental_backup.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require_relative 'incremental_backup/version'
|
2
|
+
require_relative 'incremental_backup/task'
|
3
|
+
require_relative 'incremental_backup/task_settings'
|
4
|
+
require_relative 'incremental_backup/lock'
|
5
|
+
require_relative 'incremental_backup/rsync'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'incremental_backup'
|
4
|
+
|
5
|
+
task = IncrementalBackup::Task.new do |settings|
|
6
|
+
|
7
|
+
# Defines the id of the task. This is used to ensure that the backup cannot
|
8
|
+
# be running simultaneously in two processes. The name must be a valid file
|
9
|
+
# name
|
10
|
+
settings.task_id = 'my_backup_task'
|
11
|
+
|
12
|
+
# Defines how many backups to keep
|
13
|
+
settings.hourly_backups= 24
|
14
|
+
settings.daily_backups = 7
|
15
|
+
settings.weekly_backups = 5
|
16
|
+
settings.monthly_backups = 12
|
17
|
+
settings.yearly_backups = 100
|
18
|
+
|
19
|
+
# This is where all helper files are saved. These include a log file, a lock
|
20
|
+
# file and a file remembering the dates of the the last backups. This would
|
21
|
+
# typically be a hidden directory in the home folder, e.g. ~/.incremental_backup
|
22
|
+
settings.settings_path = File.dirname(__FILE__)
|
23
|
+
|
24
|
+
# Login options
|
25
|
+
settings.remote_server = 'hostname or ip'
|
26
|
+
settings.remote_user = 'username on server'
|
27
|
+
|
28
|
+
# Paths
|
29
|
+
settings.local_path = '~'
|
30
|
+
settings.remote_path = '~/backup'
|
31
|
+
|
32
|
+
# Exclude all the files and folder listed in this file. This file is in default rsync syntax
|
33
|
+
settings.exclude_file = File.join File.dirname(__FILE__), 'exclude.file'
|
34
|
+
end
|
35
|
+
|
36
|
+
task.run
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe IncrementalBackup::Task do
|
4
|
+
it 'should be configured' do
|
5
|
+
task = IncrementalBackup::Task.new do |config|
|
6
|
+
config.hourly_backups = 1
|
7
|
+
config.daily_backups = 2
|
8
|
+
config.weekly_backups = 3
|
9
|
+
config.monthly_backups = 4
|
10
|
+
end
|
11
|
+
task.settings.hourly_backups.should == 1
|
12
|
+
task.settings.daily_backups.should == 2
|
13
|
+
task.settings.weekly_backups.should == 3
|
14
|
+
task.settings.monthly_backups.should == 4
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'throws error with invalid settings do' do
|
18
|
+
|
19
|
+
task = IncrementalBackup::Task.new do
|
20
|
+
end
|
21
|
+
|
22
|
+
logger_double = double('logger')
|
23
|
+
logger_double.should_receive(:error).at_least(:once)
|
24
|
+
task.stub(:logger).and_return logger_double
|
25
|
+
|
26
|
+
task.run
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'runs' do
|
30
|
+
task = IncrementalBackup::Task.new do |settings|
|
31
|
+
settings.task_id = "test_test"
|
32
|
+
settings.settings_path = "some/path"
|
33
|
+
settings.remote_server = "myserver"
|
34
|
+
settings.local_path = "some/path"
|
35
|
+
settings.remote_path = "some/path"
|
36
|
+
settings.remote_user = "me"
|
37
|
+
end
|
38
|
+
|
39
|
+
logger_double = double('logger')
|
40
|
+
logger_errors = []
|
41
|
+
logger_double.stub(:error) { |message| logger_errors << message }
|
42
|
+
logger_double.stub(:info)
|
43
|
+
task.stub(:logger).and_return logger_double
|
44
|
+
|
45
|
+
task.stub(:execute_ssh)
|
46
|
+
task.stub(:list_backup_dir).and_return([
|
47
|
+
"/hourly/backup_2013-02-14-T10-39-27",
|
48
|
+
"/hourly/backup_2013-02-14-T10-21-43",
|
49
|
+
"/hourly/backup_2013-02-14-T11-02-46",
|
50
|
+
"/hourly/backup_2013-02-14-T10-35-04",
|
51
|
+
"/hourly/backup_2013-02-14-T10-28-11",
|
52
|
+
"/hourly/backup_2013-02-14-T10-33-24"
|
53
|
+
])
|
54
|
+
|
55
|
+
IncrementalBackup::Lock.stub(:create).and_yield
|
56
|
+
IncrementalBackup::Rsync.stub(:execute)
|
57
|
+
|
58
|
+
task.run
|
59
|
+
|
60
|
+
throw logger_errors.join("\n") if logger_errors.any?
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: incremental_backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,120 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
13
|
-
dependencies:
|
12
|
+
date: 2013-03-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.12'
|
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: '2.12'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec-mocks
|
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: guard-rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.4'
|
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: '2.4'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rb-inotify
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.8.8
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.8.8
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 10.0.3
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 10.0.3
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: active_attr
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.7'
|
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: '0.7'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: net-ssh
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.6'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '2.6'
|
14
126
|
description: (NOT YET WORKING...) incremental_backup can make incremental backups
|
15
127
|
by hour/day/week/month remotely through ssh and rsync
|
16
128
|
email:
|
@@ -21,13 +133,25 @@ extra_rdoc_files: []
|
|
21
133
|
files:
|
22
134
|
- .gitignore
|
23
135
|
- Gemfile
|
136
|
+
- Guardfile
|
24
137
|
- LICENSE.txt
|
25
138
|
- README.md
|
26
139
|
- Rakefile
|
27
140
|
- incremental_backup.gemspec
|
28
141
|
- lib/incremental_backup.rb
|
142
|
+
- lib/incremental_backup/lock.rb
|
143
|
+
- lib/incremental_backup/rsync.rb
|
144
|
+
- lib/incremental_backup/task.rb
|
145
|
+
- lib/incremental_backup/task_settings.rb
|
29
146
|
- lib/incremental_backup/version.rb
|
30
|
-
|
147
|
+
- live_example/Gemfile
|
148
|
+
- live_example/example_task.rb
|
149
|
+
- live_example/exclude.file
|
150
|
+
- spec/spec_helper.rb
|
151
|
+
- spec/unit/task_settings_spec.rb
|
152
|
+
- spec/unit/task_spec.rb
|
153
|
+
- spec/unit/version_spec.rb
|
154
|
+
homepage: https://github.com/lasseebert/incremental_backup
|
31
155
|
licenses: []
|
32
156
|
post_install_message:
|
33
157
|
rdoc_options: []
|
@@ -41,7 +165,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
165
|
version: '0'
|
42
166
|
segments:
|
43
167
|
- 0
|
44
|
-
hash: -
|
168
|
+
hash: -952483362192499202
|
45
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
170
|
none: false
|
47
171
|
requirements:
|
@@ -50,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
174
|
version: '0'
|
51
175
|
segments:
|
52
176
|
- 0
|
53
|
-
hash: -
|
177
|
+
hash: -952483362192499202
|
54
178
|
requirements: []
|
55
179
|
rubyforge_project:
|
56
180
|
rubygems_version: 1.8.23
|
@@ -58,4 +182,8 @@ signing_key:
|
|
58
182
|
specification_version: 3
|
59
183
|
summary: (NOT YET WORKING...) incremental_backup can make incremental backups by hour/day/week/month
|
60
184
|
remotely through ssh and rsync
|
61
|
-
test_files:
|
185
|
+
test_files:
|
186
|
+
- spec/spec_helper.rb
|
187
|
+
- spec/unit/task_settings_spec.rb
|
188
|
+
- spec/unit/task_spec.rb
|
189
|
+
- spec/unit/version_spec.rb
|