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 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in anjea_backup.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ anjea_backup (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (10.3.2)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ anjea_backup!
16
+ bundler (~> 1.6)
17
+ rake
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,5 @@
1
+ [system]
2
+ dst=/backup/anjea-backups/
3
+ vault=/vault/anjea-vault/
4
+ log=/var/log/anjea/
5
+ lock=/var/lock/anjea.lock
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module AnjeaBackup
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "anjea_backup/version"
2
+ require "anjea_backup/anjea_backup"
3
+ require "anjea_backup/inifile"
4
+
5
+ module AnjeaBackup
6
+ # Our code goes here...
7
+ end
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: []