lsync 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script takes a given path, and renames it with the given format.
4
+ # It then ensures that there is a symlink called "latest" that points
5
+ # to the renamed directory.
6
+
7
+ require 'pathname'
8
+ require 'fileutils'
9
+ require 'optparse'
10
+
11
+ OPTIONS = {
12
+ :Format => "%Y.%m.%d-%H.%M.%S",
13
+ :Latest => "latest",
14
+ :UseGMT => true,
15
+ :Destination => nil
16
+ }
17
+
18
+ ARGV.options do |o|
19
+ script_name = File.basename($0)
20
+
21
+ o.set_summary_indent(' ')
22
+ o.banner = "Usage: #{script_name} [options] [directory]"
23
+ o.define_head "This script is used to rotate directories."
24
+
25
+ o.separator ""
26
+ o.separator "Help and Copyright information"
27
+
28
+ o.on("-f format", String, "Set the format of the rotated directory names. See Time$strftime") do |format|
29
+ OPTIONS[:Format] = format
30
+ end
31
+
32
+ o.on("-l latest", String, "Set the name for the latest rotation symlink.") do |latest|
33
+ OPTIONS[:Latest] = latest
34
+ end
35
+
36
+ o.on("-d destination", String, "Set the directory to move rotated backups.") do |destination|
37
+ OPTIONS[:Destination] = destination
38
+ end
39
+
40
+ o.on_tail("--copy", "Display copyright information") do
41
+ puts "#{script_name}. Copyright (c) 2008-2009 Samuel Williams. Released under the GPLv3."
42
+ puts "See http://www.oriontransfer.co.nz/ for more information."
43
+
44
+ exit
45
+ end
46
+
47
+ o.on_tail("-h", "--help", "Show this help message.") do
48
+ puts o
49
+ exit
50
+ end
51
+ end.parse!
52
+
53
+ time = Time.now
54
+
55
+ if OPTIONS[:UseUTC]
56
+ time = time.utc
57
+ end
58
+
59
+ dir = Pathname.new(ARGV[0] || ".inprogress")
60
+ rotation_dir = Pathname.new(OPTIONS[:Destination] ? OPTIONS[:Destination] : dir.dirname)
61
+ rotated_name = time.strftime(OPTIONS[:Format])
62
+ rotated_dir = rotation_dir + rotated_name
63
+ latest_link = rotation_dir + OPTIONS[:Latest]
64
+
65
+ # Move rotated dir
66
+ FileUtils.mv(dir, rotated_dir)
67
+
68
+ # Recreate latest symlink
69
+ if File.symlink?(latest_link)
70
+ puts "Removing old latest link..."
71
+ FileUtils.rm(latest_link)
72
+ end
73
+
74
+ puts "Creating latest symlink to #{rotated_name}"
75
+ FileUtils.ln_s(rotated_name, latest_link)
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Commands = {
4
+ "mount" => "mount",
5
+ "unmount" => "umount"
6
+ }
7
+
8
+ DevicePaths = [
9
+ "/dev/disk/by-label",
10
+ "/dev/disk/by-uuid",
11
+ "/dev"
12
+ ]
13
+
14
+ action = ARGV[0]
15
+ disk_name = ARGV[1]
16
+
17
+ mountpoint = File.join('', 'mnt', disk_name)
18
+
19
+ if (action == 'mountpoint')
20
+ puts File.join(mountpoint, ARGV[2..-1])
21
+ else
22
+ puts "#{action.capitalize}ing #{mountpoint}..."
23
+ system Commands[action], mountpoint
24
+
25
+ if $?.exitstatus != 0 or $?.exitstatus != 3383
26
+ exit 5
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'base64'
4
+ cmd = Base64.decode64(ARGV[0])
5
+ exec cmd
6
+
@@ -0,0 +1,33 @@
1
+
2
+ module LSync
3
+
4
+ class BackupError < StandardError
5
+ def initialize(reason, components = {})
6
+ @reason = reason
7
+ @components = components
8
+ end
9
+
10
+ def to_s
11
+ @reason
12
+ end
13
+
14
+ attr :reason
15
+ attr :components
16
+ end
17
+
18
+ class BackupScriptError < BackupError
19
+ end
20
+
21
+ class BackupMethodError < BackupError
22
+ end
23
+
24
+ class ConfigurationError < BackupError
25
+ end
26
+
27
+ class BackupActionError < BackupError
28
+ def initialize(server, action, exception)
29
+ super("Backup action failed: #{action} (#{exception.to_s})", :action => action, :exception => exception)
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,249 @@
1
+
2
+ # A backup plan is a rule-based engine to process individual scripts.
3
+ # Failure and success can be delt with over multiple scripts.
4
+
5
+ require 'ruleby'
6
+
7
+ module Ruleby
8
+ def self.engine(name, &block)
9
+ e = Core::Engine.new
10
+ yield e if block_given?
11
+ return e
12
+ end
13
+ end
14
+
15
+ module LSync
16
+
17
+ BuiltInCommands = {
18
+ "ping-host" => "ping -c 4 -t 5 -o"
19
+ }
20
+
21
+ module Facts
22
+ class Initial
23
+ end
24
+
25
+ class StageSucceeded
26
+ def initialize(stage)
27
+ @stage = stage
28
+ puts "Stage Succeeded: #{@stage.name}"
29
+ end
30
+
31
+ attr :stage
32
+
33
+ def name
34
+ @stage.name
35
+ end
36
+ end
37
+
38
+ class StageFailed
39
+ def initialize(stage)
40
+ @stage = stage
41
+ end
42
+
43
+ attr :stage
44
+
45
+ def name
46
+ @stage.name
47
+ end
48
+ end
49
+
50
+ class ScriptSucceeded
51
+ def initialize(stage, script)
52
+ @stage = stage
53
+ @script = script
54
+ end
55
+
56
+ attr :stage
57
+ attr :script
58
+ end
59
+
60
+ class ScriptFailed
61
+ def initialize(stage, script)
62
+ @stage = stage
63
+ @script = script
64
+ end
65
+
66
+ attr :stage
67
+ attr :script
68
+ end
69
+ end
70
+
71
+ class BackupPlanRulebook < Ruleby::Rulebook
72
+ include Facts
73
+
74
+ def rules
75
+ #rule [ScriptSucceeded, :m] do |v|
76
+ # script = v[:m].script
77
+ # puts "Backup #{script.dump} successful"
78
+ #end
79
+
80
+ rule [ScriptFailed, :m] do |v|
81
+ script = v[:m].script
82
+ puts "*** Script #{script} failed"
83
+ end
84
+
85
+ #rule [StageSucceeded, :m] do |v|
86
+ # stage = v[:m].stage
87
+ # puts "Stage #{stage.name.dump} successful"
88
+ #end
89
+ end
90
+ end
91
+
92
+ class StageRulebook < Ruleby::Rulebook
93
+ include Facts
94
+
95
+ def initialize(engine, stage)
96
+ super(engine)
97
+ @stage = stage
98
+ end
99
+
100
+ def rules
101
+ # Does this stage have any rules? (i.e. can it run in any case?)
102
+ if @stage.rules.size > 0
103
+ puts "Loading rules for stage #{@stage.name.dump}..."
104
+ @stage.rules.each do |name, r|
105
+ puts "\t#{name}..."
106
+
107
+ r["when"].each do |s|
108
+ puts "\t\t#{s.dump}"
109
+ end
110
+
111
+ options = r.dup
112
+ wh = options.delete("when")
113
+
114
+ # Build rule
115
+ rule("#{@stage.name}_#{name}".to_sym, options, *wh) do |v|
116
+ @stage.run_scripts
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ # Bring names into the right scope (i.e. Facts)
123
+ def __eval__(x)
124
+ eval(x)
125
+ end
126
+ end
127
+
128
+ class Stage
129
+ protected
130
+ RuleConfigKeys = Set.new(["priority", "when"])
131
+
132
+ def process_rules config
133
+ rules = config.keys_matching(/^rule\.(.*)$/)
134
+
135
+ if rules.size > 0
136
+ # Okay
137
+ elsif config.key? "when"
138
+ rules = {
139
+ "rule.default" => config.delete_if { |k,v| !RuleConfigKeys.include?(k) }
140
+ }
141
+ else
142
+ return {}
143
+ end
144
+
145
+ rules.keys.each do |rule_name|
146
+ options = {}
147
+ w = rules[rule_name].delete("when") || []
148
+ w = [w] if w.is_a? String
149
+
150
+ rules[rule_name].each { |k,v| options[k.to_sym] = v }
151
+ rules[rule_name] = options
152
+ rules[rule_name]["when"] = w.collect { |s| s.gsub('@', '#') }
153
+ end
154
+
155
+ rules
156
+ end
157
+
158
+ def process_scripts config
159
+ config["scripts"].collect do |s|
160
+ s.match(/^([^\s]+)(.*)$/)
161
+
162
+ if BuiltInCommands.key? $1
163
+ BuiltInCommands[$1] + $2
164
+ else
165
+ s
166
+ end
167
+ end
168
+ end
169
+
170
+ public
171
+ def initialize(plan, name, config)
172
+ @plan = plan
173
+ @name = name
174
+
175
+ @scripts = process_scripts(config)
176
+ @rules = process_rules(config)
177
+ end
178
+
179
+ def run_scripts
180
+ failed = false
181
+
182
+ puts "Running stage #{@name}..."
183
+ @scripts.each do |script|
184
+ puts "\tRunning Script #{script}..."
185
+
186
+ if system(script)
187
+ @plan.engine.assert Facts::ScriptSucceeded.new(self, script)
188
+ else
189
+ @plan.engine.assert Facts::ScriptFailed.new(self, script)
190
+ failed = true
191
+ end
192
+ end
193
+
194
+ if failed
195
+ @plan.engine.assert Facts::StageFailed.new(self)
196
+ else
197
+ @plan.engine.assert Facts::StageSucceeded.new(self)
198
+ end
199
+ end
200
+
201
+ attr :name
202
+ attr :scripts
203
+ attr :rules
204
+ end
205
+
206
+ class BackupPlan
207
+ def initialize(config, logger = nil)
208
+ @logger = logger || Logger.new(STDOUT)
209
+
210
+ @config = config.keys_matching(/^scripts\.(.*)$/)
211
+ @stages = config.keys_matching(/^stage\.(.*)$/) { |c,name| Stage.new(self, name, c) }
212
+ end
213
+
214
+ attr :logger, true
215
+ attr :config
216
+
217
+ def run_backup
218
+ Ruleby.engine :engine do |e|
219
+ @engine = e
220
+
221
+ puts " Loading Rules ".center(80, "=")
222
+
223
+ BackupPlanRulebook.new(e).rules
224
+
225
+ @stages.each do |k,s|
226
+ StageRulebook.new(e, s).rules
227
+ end
228
+
229
+ puts " Processing Rules ".center(80, "=")
230
+
231
+ e.assert Facts::Initial.new
232
+
233
+ e.match
234
+ @engine = nil
235
+ end
236
+
237
+ puts " Finished ".center(80, "=")
238
+ end
239
+
240
+ attr :engine
241
+ attr :config
242
+ attr :stages
243
+
244
+ def self.load_from_file(path)
245
+ new(YAML::load(File.read(path)))
246
+ end
247
+ end
248
+
249
+ end
@@ -0,0 +1,136 @@
1
+
2
+ # A backup script coordinates "one" backup as a unit.
3
+
4
+ require 'lsync/action'
5
+ require 'lsync/method'
6
+ require 'lsync/server'
7
+ require 'lsync/directory'
8
+
9
+ module LSync
10
+
11
+ class BackupScript
12
+ private
13
+ # Given a name, find out which server config matches it
14
+ def find_named_server name
15
+ if @servers.key? name
16
+ return @servers[name]
17
+ else
18
+ hostname = Socket.gethostbyname(name)[0] rescue name
19
+ return @servers.values.find { |s| s["host"] == hostname }
20
+ end
21
+ end
22
+
23
+ # Find out the config section for the current server
24
+ def find_current_server
25
+ server = nil
26
+
27
+ # Find out if the master server is local...
28
+ if @master.is_local?
29
+ server = @master
30
+ else
31
+ # Find a server config that specifies the local host
32
+ server = @servers.values.find { |s| s.is_local? }
33
+ end
34
+
35
+ return server
36
+ end
37
+
38
+ def script_logger
39
+ if @config["log-file"]
40
+ return Logger.new(@config["log-file"], 'weekly')
41
+ end
42
+ end
43
+
44
+ public
45
+ def initialize(config, logger = nil)
46
+ @config = config
47
+
48
+ @logger = logger || Logger.new(STDOUT)
49
+
50
+ @servers = config.keys_matching(/^server\./) { |c,n| Server.new(c) }
51
+ @directories = config.keys_matching(/^directory\./) { |c,n| Directory.new(c) }
52
+
53
+ @master = find_named_server(config["master"])
54
+
55
+ if @master == nil
56
+ raise ConfigurationError.new("Could not determine master server!", :script => self)
57
+ end
58
+
59
+ @method = Method.new(config["method"])
60
+ @log_buffer = nil
61
+ end
62
+
63
+ attr :logger, true
64
+ attr :master
65
+ attr :method
66
+ attr :servers
67
+ attr :directories
68
+ attr :log_buffer
69
+
70
+ def run_backup
71
+ # We buffer the log data so that if there is an error it is available to the notification sub-system
72
+ @log_buffer = StringIO.new
73
+ logger = @logger.tee(script_logger, Logger.new(@log_buffer))
74
+
75
+ current = find_current_server
76
+
77
+ # At this point we must know the current server or we can't continue
78
+ if current == nil
79
+ raise BackupScriptError.new("Could not determine current server!", :script => self, :master => @master)
80
+ end
81
+
82
+ if @master.is_local?
83
+ logger.info "We are the master server..."
84
+ else
85
+ logger.info "We are not the master server..."
86
+ logger.info "Master server is #{@master}..."
87
+ end
88
+
89
+ # Run server pre-scripts.. if these fail then we abort the whole backup
90
+ begin
91
+ @method.run_actions(:before, logger)
92
+ @master.run_actions(:before, logger)
93
+ rescue AbortBackupException
94
+ return
95
+ end
96
+
97
+ logger.info "Running backups for server #{current}..."
98
+
99
+ @servers.each do |name, s|
100
+ # S is always a data destination, therefore s can't be @master
101
+ next if s == @master
102
+
103
+ # Skip servers that shouldn't be processed
104
+ unless @method.should_run?(@master, current, s)
105
+ logger.info "\t" + "Skipping".rjust(20) + " : #{s}"
106
+ next
107
+ end
108
+
109
+ # Run pre-scripts for a particular server
110
+ begin
111
+ s.run_actions(:before, logger)
112
+ rescue AbortBackupException
113
+ next
114
+ end
115
+
116
+ @directories.each do |name, d|
117
+ logger.info "\t" + ("Processing " + d.to_s).rjust(20) + " : #{s}"
118
+
119
+ @method.logger = logger
120
+ @method.run(@master, s, d)
121
+ end
122
+
123
+ # Run post-scripts for a particular server
124
+ s.run_actions(:after, logger)
125
+ end
126
+
127
+ @method.run_actions(:after, logger)
128
+ @master.run_actions(:after, logger)
129
+ end
130
+
131
+ def self.load_from_file(path)
132
+ new(YAML::load(File.read(path)))
133
+ end
134
+ end
135
+
136
+ end