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 CHANGED
@@ -15,3 +15,7 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+
19
+ live_example/log
20
+ live_example/log.0
21
+ live_example/log.1
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard 'rspec' do
2
+
3
+ # Run spec if spec changes
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+
6
+ # Run all specs if anything changes
7
+ watch(%r{^lib/(.+)\.rb$}) { 'spec' }
8
+ watch(%w[Gemfile.lock]) { 'spec' }
9
+ watch('spec/spec_helper.rb') { 'spec' }
10
+ end
11
+
data/README.md CHANGED
@@ -1,23 +1,31 @@
1
- # IncrementalBackup (NOT YET WORKING...)
1
+ # incremental\_backup
2
2
 
3
- Create incremental backups via ssh and rsync
3
+ Creates incremental backups via ssh and rsync.
4
4
 
5
- ## Installation
5
+ ## Description
6
6
 
7
- Add this line to your application's Gemfile:
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
- gem 'incremental_backup'
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
- And then execute:
11
+ ## Dependencies
12
12
 
13
- $ bundle
13
+ * Ruby (tested on 1.9.3)
14
+ * ssh
15
+ * rsync
14
16
 
15
- Or install it yourself as:
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
@@ -1 +1,4 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
@@ -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
@@ -1,3 +1,3 @@
1
1
  module IncrementalBackup
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,5 +1,5 @@
1
- require "incremental_backup/version"
2
-
3
- module IncrementalBackup
4
- # Your code goes here...
5
- end
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,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'incremental_backup', '~> 0.0'
4
+
@@ -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
@@ -0,0 +1,11 @@
1
+ .cache/
2
+ .rbenv/
3
+ .rvm/
4
+ Downloads/
5
+ Dropbox/
6
+
7
+ tmp
8
+ cache
9
+ development.log
10
+ development.sqlite3
11
+ test.log
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'incremental_backup'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ config.formatter = 'documentation'
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe IncrementalBackup::TaskSettings do
4
+ it 'should not be valid without settings_path' do
5
+ settings = IncrementalBackup::TaskSettings.new
6
+ settings.should_not be_valid
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'version' do
4
+ it 'should have a version' do
5
+ IncrementalBackup::VERSION.should_not be_nil
6
+ end
7
+ 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.1
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-01-21 00:00:00.000000000 Z
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
- homepage: ''
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: -3434899829866863485
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: -3434899829866863485
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