anjea_backup 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []