anjea_backup 0.0.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.
- checksums.yaml +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +89 -0
- data/Rakefile +2 -0
- data/anjea.conf.example +5 -0
- data/anjea_backup.gemspec +23 -0
- data/backups.conf.example +20 -0
- data/bin/anjea +32 -0
- data/lib/anjea_backup/anjea_backup.rb +151 -0
- data/lib/anjea_backup/inifile.rb +39 -0
- data/lib/anjea_backup/version.rb +3 -0
- data/lib/anjea_backup.rb +7 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MmZjYWM2NDI3ZGUyOTllZDUxYTZhOTRjNDkwMjk5MTczYmMwNzYyYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NGNjYTA3OWVmYWZhOTk2OWVlNTc0ZmU4MzQ0ZjgxMzJkZTI5OGIxYQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmM4NTQ1ODI2MzBiYjQ3YjY1Mjg5ZWQzYjM3MWEwZmUxOTNiN2Q1NmUwOGQ0
|
10
|
+
OWI0OWZlNWM4OGRlNDUwNTExMzJlYjU4MjMxYWI4NDkxMjBmZTg4MjcxYThh
|
11
|
+
NzUwNzMzYjk4NWU4YmY1YWVhMTQzODk2OTczMzBhMTgyZGQ3ZDc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjE3NTJjMzA1Yzg1OGU0YTBmMTczMzFjYTQ5YTVkNWRlOTRiY2QxNDJmZjNj
|
14
|
+
OGQxMTRjNDZlYzdmNWM1ZTYxZWY4MmZlMDdiZGMyOWQxYmNlMzlmMDRiNjMx
|
15
|
+
MzQ1MGM3NTdmOTkwZjZiNjZjOGE3MTk5MzU0YzE5OWU3ZjA5NDY=
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Felix Wolfsteller
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# AnjeaBackup
|
2
|
+
|
3
|
+
Warning: *I do not consider AnjeaBackup anything close to production-ready* .
|
4
|
+
|
5
|
+
It is however a fun toy, and some things do work.
|
6
|
+
|
7
|
+
AnjeaBackup will create copies of local or remote (via ssh) files and directories, hard-linked to former versions of the same files and directories.
|
8
|
+
|
9
|
+
The approach has been beautifully implemented by numerous people using e.g. bash or perl scripts.
|
10
|
+
I aimed for quick and easy configurability and some oppinionated choices that were given my usage scenario.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
You'll probably need a decent linux installation and have rsync installed.
|
15
|
+
|
16
|
+
$ gem install anjea_backup
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
AnjeaBackup is meant to be a script. If you find 'library' usage for it, get in touch. Also, I think if you want to use it, get in touch.
|
21
|
+
|
22
|
+
The script is in `/bin/anjea` .
|
23
|
+
|
24
|
+
The working basic use case is to just call it. It will pick up two configuration files (see next sections) and immediately start a backup-attempt.
|
25
|
+
|
26
|
+
## Configuration
|
27
|
+
|
28
|
+
There are two important configuration files, `anjea.conf` and `backups.conf` .
|
29
|
+
|
30
|
+
Example configuration files are included.
|
31
|
+
The files somewhat follow the ini/Desktop-File conventions, but a *very* limited parser is used.
|
32
|
+
|
33
|
+
### anjea.conf
|
34
|
+
|
35
|
+
Defines *where* to save backups, logs, etc. Example:
|
36
|
+
|
37
|
+
[system]
|
38
|
+
dst=/backup/anjea-backups/
|
39
|
+
vault=/vault/anjea-vault/
|
40
|
+
log=/var/log/anjea/
|
41
|
+
lock=/var/lock/anjea.lock
|
42
|
+
|
43
|
+
`dst` is the root folder for backups, `vault` currently unused, `log` folder for log files, `lock` file to ensure anjea is just running once.
|
44
|
+
|
45
|
+
### backups.conf
|
46
|
+
|
47
|
+
Defines *what* to backup and how to access it. Example:
|
48
|
+
|
49
|
+
[files]
|
50
|
+
src=/home/anjea/files/
|
51
|
+
description=other directory with files
|
52
|
+
|
53
|
+
[remotebox]
|
54
|
+
src=/home/remoteanjea/stuff/
|
55
|
+
description=stuff on other host
|
56
|
+
host=otherhost.mynetwork
|
57
|
+
key=/home/anjea/.anjea/keys/otherhostkey_rsa
|
58
|
+
user=anjea
|
59
|
+
|
60
|
+
Second group saves files from a remote box (`anjea@otherhost.mynetwork:/home/remoteanjea/stuff`) using public key as defined in `key` .
|
61
|
+
|
62
|
+
## Example
|
63
|
+
|
64
|
+
* Dedicated Backup-Server running anjea.
|
65
|
+
* Backup-Server wakes up on lan, triggered by cron job from outside.
|
66
|
+
* Backup-Server has a crontab-entry to do the backup
|
67
|
+
0 3 * * * anjea | tee /backups/current/log | ssmtp myemailadress@geemail.com
|
68
|
+
* last log goes to /backup/current/log, is sent to email address (requires configured ssmtp, alternatively define MAILTO in crontab).
|
69
|
+
|
70
|
+
## TODOs
|
71
|
+
|
72
|
+
This will need work.
|
73
|
+
|
74
|
+
* optparse bin/anjea, add help
|
75
|
+
* trap and handle signals
|
76
|
+
* Rescue from malconditions, move incomplete backups to a designated location
|
77
|
+
* Continue with other backups in case one faults
|
78
|
+
* Allow manual targeted backups of single sources
|
79
|
+
* Revive tests
|
80
|
+
* (missing: send mail) -> easy to do from outside
|
81
|
+
* (missing: do full, non incremental, non hardlinked backups into vault from time to time)
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
1. Fork it ( https://github.com/[my-github-username]/anjea_backup/fork )
|
86
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
87
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
88
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
89
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/anjea.conf.example
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'anjea_backup/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "anjea_backup"
|
8
|
+
spec.version = AnjeaBackup::VERSION
|
9
|
+
spec.authors = ["Felix Wolfsteller"]
|
10
|
+
spec.email = ["felix.wolfsteller@gmail.com"]
|
11
|
+
spec.summary = %q{Backup with ruby, rsync, hardlinks, hacks, without style but with aboriginal mystery.}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = "https://github.com/fwolfst/anjea_backup/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
[datatest]
|
2
|
+
src=/home/anjea/data/
|
3
|
+
description=Short test
|
4
|
+
|
5
|
+
[files]
|
6
|
+
src=/home/anjea/files/
|
7
|
+
description=other directory with files
|
8
|
+
|
9
|
+
[remotebox]
|
10
|
+
src=/home/remoteanjea/stuff/
|
11
|
+
description=stuff on other host
|
12
|
+
host=otherhost.mynetwork
|
13
|
+
key=/home/anjea/.anjea/keys/otherhostkey_rsa
|
14
|
+
user=anjea
|
15
|
+
|
16
|
+
# Always good idea to backup myself
|
17
|
+
[self]
|
18
|
+
src=/home/installed/backup
|
19
|
+
description=backup script and conf
|
20
|
+
|
data/bin/anjea
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'anjea_backup'
|
4
|
+
|
5
|
+
action = :backup
|
6
|
+
if ARGV.length > 0
|
7
|
+
case ARGV[0].downcase
|
8
|
+
when 'backup'
|
9
|
+
action = :backup
|
10
|
+
when 'vault'
|
11
|
+
action = :vault
|
12
|
+
when 'list'
|
13
|
+
action = :list
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
case action
|
19
|
+
when :backup
|
20
|
+
AnjeaBackup::Backup.new.backup
|
21
|
+
when :list
|
22
|
+
AnjeaBackup.new.cleanup
|
23
|
+
when :vault
|
24
|
+
AnjeaBackup.new.to_vault
|
25
|
+
else
|
26
|
+
STDERR.puts "unknown action"
|
27
|
+
exit 3
|
28
|
+
end
|
29
|
+
rescue NoIniFileError => ex
|
30
|
+
STDERR.puts "#{ex.inspect}"
|
31
|
+
exit 4
|
32
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pathname'
|
4
|
+
require_relative 'inifile'
|
5
|
+
|
6
|
+
module AnjeaBackup
|
7
|
+
class BackupItem
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :description
|
10
|
+
attr_accessor :src_dir
|
11
|
+
attr_accessor :ssh_url
|
12
|
+
attr_accessor :ssh_key
|
13
|
+
|
14
|
+
def initialize hash
|
15
|
+
@name = hash[:name]
|
16
|
+
@description = hash['description']
|
17
|
+
@src_dir = hash['src']
|
18
|
+
if hash['host'] && hash['user'] && hash['key']
|
19
|
+
@ssh_url = "#{hash['user']}@#{hash['host']}:#{@src_dir}"
|
20
|
+
@ssh_key = hash['key']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Backup
|
26
|
+
def initialize
|
27
|
+
read_system_conf
|
28
|
+
if !lock!
|
29
|
+
log_err "Aborting, anjea already running. Delete #{@lock_file} if not."
|
30
|
+
exit 2
|
31
|
+
end
|
32
|
+
read_backups_conf
|
33
|
+
setup_dirs
|
34
|
+
end
|
35
|
+
|
36
|
+
def backup
|
37
|
+
yyyymmdd = DateTime.now.strftime("%Y-%m-%d-%H")
|
38
|
+
|
39
|
+
# TODO work in a tmp work dir
|
40
|
+
@backup_items.each do |item|
|
41
|
+
last_backup = File.join(@last, item.name)
|
42
|
+
today_backup = create_now_backup_path(yyyymmdd, item)
|
43
|
+
work_dir = File.join(@partial, item.name, yyyymmdd)
|
44
|
+
FileUtils.mkdir_p work_dir
|
45
|
+
|
46
|
+
source = item.ssh_url ? "-e \"ssh -i #{item.ssh_key}\" #{item.ssh_url}"
|
47
|
+
: item.src_dir
|
48
|
+
|
49
|
+
rsync_cmd = "rsync -avz --delete --relative --stats --log-file #{log_file_for(yyyymmdd, item)} --link-dest #{last_backup} #{source} #{today_backup}"
|
50
|
+
|
51
|
+
log item, "rsync start"
|
52
|
+
if system(rsync_cmd)
|
53
|
+
log item, "rsync finished"
|
54
|
+
# 'finish', move partial to backup-dest
|
55
|
+
link_last_backup today_backup, last_backup
|
56
|
+
log item, "linked"
|
57
|
+
else
|
58
|
+
# TODO Move this one into quarantaine/incomplete!
|
59
|
+
log_err item, "rsync failed?"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def cleanup
|
66
|
+
now = DateTime.now
|
67
|
+
@backup_items.each do |item|
|
68
|
+
puts "[#{item.name}] backups:"
|
69
|
+
puts "-- #{item.description} --"
|
70
|
+
ages = Dir.glob("#{@destination}/[^c]*/#{item.name}").map do |dir|
|
71
|
+
date_dir = Pathname.new(dir).parent.basename.to_s
|
72
|
+
dtdiff = 0
|
73
|
+
begin
|
74
|
+
stamp = DateTime.strptime(date_dir, "%Y-%m-%d-%H")
|
75
|
+
dtdiff = now - stamp
|
76
|
+
rescue
|
77
|
+
STDERR.puts "Do not understand timestamp in #{dir}"
|
78
|
+
end
|
79
|
+
[dtdiff, dir]
|
80
|
+
end
|
81
|
+
ages.sort.each do |age,dir|
|
82
|
+
puts "(#{(age*24).to_i}) #{dir}"
|
83
|
+
end
|
84
|
+
puts
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_vault
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def setup_dirs
|
94
|
+
Dir.mkdir @destination if !File.directory? @destination
|
95
|
+
Dir.mkdir @vault if !File.directory? @vault
|
96
|
+
Dir.mkdir @log if !File.directory? @log
|
97
|
+
Dir.mkdir @last if !File.directory? @last
|
98
|
+
Dir.mkdir @partial if !File.directory? @partial
|
99
|
+
Dir.mkdir @failed if !File.directory? @failed
|
100
|
+
end
|
101
|
+
|
102
|
+
def log_err item=nil, msg
|
103
|
+
if item
|
104
|
+
STDERR.puts "[#{item.name}] #{DateTime.now.strftime("%Y-%m-%d-%H:%M")} - #{msg}"
|
105
|
+
else
|
106
|
+
STDERR.puts "#{DateTime.now.strftime("%Y-%m-%d-%H:%M")} - #{msg}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def log item, msg
|
111
|
+
puts "[#{item.name}] #{DateTime.now.strftime("%Y-%m-%d-%H-%M")} - #{msg}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def lock!
|
115
|
+
File.new(@lock_file,'w').flock( File::LOCK_NB | File::LOCK_EX )
|
116
|
+
end
|
117
|
+
|
118
|
+
def link_last_backup today_backup, last_backup
|
119
|
+
FileUtils.rm_f last_backup
|
120
|
+
FileUtils.ln_s(today_backup, last_backup, :force => true)
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_backups_conf
|
124
|
+
@backup_items = read_ini_file('backups.conf').map {|group| BackupItem.new group }
|
125
|
+
end
|
126
|
+
|
127
|
+
def read_system_conf
|
128
|
+
system_conf = read_ini_file 'anjea.conf'
|
129
|
+
|
130
|
+
@destination = system_conf[0]['dst']
|
131
|
+
@vault = system_conf[0]['vault']
|
132
|
+
@log = system_conf[0]['log']
|
133
|
+
@last = File.join(@destination, 'current')
|
134
|
+
@failed = File.join(@destination, 'failed')
|
135
|
+
@partial = File.join(@destination, 'partial')
|
136
|
+
@lock_file = system_conf[0]['lock']
|
137
|
+
end
|
138
|
+
|
139
|
+
def log_file_for yyyymmdd, item
|
140
|
+
FileUtils.mkdir_p File.join(@log, item.name)
|
141
|
+
File.join(@log, item.name, "#{yyyymmdd}.log")
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_now_backup_path yyyymmdd, item
|
145
|
+
today_backup = File.join(@destination, yyyymmdd, item.name)
|
146
|
+
FileUtils.mkdir_p today_backup
|
147
|
+
today_backup
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class NoIniFileError < StandardError
|
2
|
+
end
|
3
|
+
|
4
|
+
def is_head? line
|
5
|
+
match_data = /(\[)([\w-]*)(\])/.match line
|
6
|
+
match_data
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_category line
|
10
|
+
match_data = /(\[)([\w-]*)(\])/.match line
|
11
|
+
match_data.captures[1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_kv line
|
15
|
+
match_data = /([A-Za-z0-9]*) *= *([A-Za-z0-9\/ .\-_]*)/.match line
|
16
|
+
match_data.captures
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_ini_file filename
|
20
|
+
ini_objs = []
|
21
|
+
file_contents = File.readlines(filename)
|
22
|
+
rescue Errno::ENOENT
|
23
|
+
raise NoIniFileError, "#{filename} config file error"
|
24
|
+
ini_obj = {}
|
25
|
+
file_contents.each do |line|
|
26
|
+
next if(line.strip.empty? || line.start_with?("#"))
|
27
|
+
if is_head? line
|
28
|
+
ini_objs << ini_obj if !ini_obj.empty?
|
29
|
+
ini_obj = {}
|
30
|
+
ini_obj[:name] = get_category line
|
31
|
+
next
|
32
|
+
end
|
33
|
+
kv = get_kv line
|
34
|
+
ini_obj[kv[0]] = kv[1]
|
35
|
+
end
|
36
|
+
ini_objs << ini_obj
|
37
|
+
ini_objs
|
38
|
+
end
|
39
|
+
|
data/lib/anjea_backup.rb
ADDED
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: anjea_backup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Felix Wolfsteller
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: ''
|
42
|
+
email:
|
43
|
+
- felix.wolfsteller@gmail.com
|
44
|
+
executables:
|
45
|
+
- anjea
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- anjea.conf.example
|
55
|
+
- anjea_backup.gemspec
|
56
|
+
- backups.conf.example
|
57
|
+
- bin/anjea
|
58
|
+
- lib/anjea_backup.rb
|
59
|
+
- lib/anjea_backup/anjea_backup.rb
|
60
|
+
- lib/anjea_backup/inifile.rb
|
61
|
+
- lib/anjea_backup/version.rb
|
62
|
+
homepage: https://github.com/fwolfst/anjea_backup/
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.2.2
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Backup with ruby, rsync, hardlinks, hacks, without style but with aboriginal
|
86
|
+
mystery.
|
87
|
+
test_files: []
|