phusion-backup 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.TXT ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Phusion
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: binary
3
+ require 'etc'
4
+ require 'optparse'
5
+
6
+ module PhusionBackup
7
+
8
+ module TUI
9
+ protected
10
+ DEFAULT_TERMINAL_COLORS = "\e[0m\e[37m\e[40m"
11
+
12
+ def stdout
13
+ @stdout || STDOUT
14
+ end
15
+
16
+ def print(text)
17
+ stdout.write(substitute_color_tags(text))
18
+ stdout.flush
19
+ end
20
+
21
+ def puts(text = nil)
22
+ if text
23
+ stdout.puts(substitute_color_tags(text))
24
+ else
25
+ stdout.puts
26
+ end
27
+ stdout.flush
28
+ end
29
+
30
+ def puts_error(text)
31
+ STDERR.puts(substitute_color_tags("<red>#{text}</red>"))
32
+ STDERR.flush
33
+ end
34
+
35
+ def substitute_color_tags(data)
36
+ data = data.to_s
37
+ data = data.gsub(%r{<b>(.*?)</b>}m, "\e[1m\\1#{DEFAULT_TERMINAL_COLORS}")
38
+ data.gsub!(%r{<red>(.*?)</red>}m, "\e[1m\e[31m\\1#{DEFAULT_TERMINAL_COLORS}")
39
+ data.gsub!(%r{<green>(.*?)</green>}m, "\e[1m\e[32m\\1#{DEFAULT_TERMINAL_COLORS}")
40
+ data.gsub!(%r{<yellow>(.*?)</yellow>}m, "\e[1m\e[33m\\1#{DEFAULT_TERMINAL_COLORS}")
41
+ data.gsub!(%r{<banner>(.*?)</banner>}m, "\e[33m\e[44m\e[1m\\1#{DEFAULT_TERMINAL_COLORS}")
42
+ return data
43
+ end
44
+ end
45
+
46
+ class Server < Struct.new(:hostname, :dir)
47
+ end
48
+
49
+ class Core
50
+ ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
51
+
52
+ include TUI
53
+
54
+ def initialize(backupdirs)
55
+ @backupdirs = backupdirs
56
+ end
57
+
58
+ def servers
59
+ results = []
60
+ @backupdirs.each do |dir|
61
+ next if !File.exist?(dir)
62
+ Dir.foreach(dir) do |hostname|
63
+ next if hostname =~ /^\./
64
+ fullname = "#{dir}/#{hostname}"
65
+ if File.directory?(fullname)
66
+ results << Server.new(hostname, fullname)
67
+ end
68
+ end
69
+ end
70
+ return results
71
+ end
72
+
73
+ def find_server(hostname_or_dir)
74
+ return servers.find { |s| s.hostname == hostname_or_dir || s.dir == hostname_or_dir }
75
+ end
76
+
77
+ def generate(dir)
78
+ copy_file_no_overwrite("#{ROOT}/resources/default-files.txt", "#{dir}/files.txt")
79
+ copy_file_no_overwrite("#{ROOT}/resources/default-install-script.txt", "#{dir}/install-script.txt")
80
+ end
81
+
82
+ def run_backup(server)
83
+ if !File.exist?("#{server.dir}/files.txt")
84
+ puts "<red>No `files.txt` found in this backup directory.</red>"
85
+ puts
86
+ puts "Each backup directory must have such a file, in which you " +
87
+ "specify which files must be backed up. You can create " +
88
+ "a default one with:"
89
+ puts
90
+ puts " <b>phusion-backup --generate #{server.dir}</b>"
91
+ exit 1
92
+ end
93
+
94
+ filename = create_globbing_filelist(server)
95
+ begin
96
+ sh!('rdiff-backup',
97
+ '-v6',
98
+ '--exclude-sockets',
99
+ '--include-globbing-filelist', filename,
100
+ "root@#{server.hostname}::/",
101
+ "#{server.dir}/data")
102
+ ensure
103
+ File.unlink(filename)
104
+ end
105
+ end
106
+
107
+ private
108
+ class CommandError < StandardError
109
+ end
110
+
111
+ def sh(*args)
112
+ puts "# #{args.join(' ')}"
113
+ result = system(*args)
114
+ if result
115
+ return true
116
+ elsif $?.signaled? && $?.termsig == Signal.list["INT"]
117
+ raise Interrupt
118
+ else
119
+ return false
120
+ end
121
+ end
122
+
123
+ def sh!(*args)
124
+ if !sh(*args)
125
+ puts_error "*** Command failed: #{args.join(' ')}"
126
+ raise CommandError
127
+ end
128
+ end
129
+
130
+ def copy_file_no_overwrite(source, target)
131
+ if File.exist?(target)
132
+ puts " [CREATE] #{target}"
133
+ puts " <yellow>File already exists. Not overwritten.</yellow>"
134
+ else
135
+ dir = File.dirname(target)
136
+ if !File.exist?(dir)
137
+ puts " [MKDIR] #{dir}"
138
+ Dir.mkdir(dir)
139
+ end
140
+ puts " [CREATE] #{target}"
141
+ File.open(target, "w") do |f|
142
+ f.write(File.read(source))
143
+ end
144
+ end
145
+ end
146
+
147
+ def create_globbing_filelist(server)
148
+ filename = "/tmp/phusion-backup-#{Process.pid}.txt"
149
+ File.open(filename, 'a') do |target|
150
+ File.open("#{server.dir}/files.txt", 'r') do |source|
151
+ while !source.eof?
152
+ line = source.readline.sub(/^#.*/, '')
153
+ target.puts(line)
154
+ end
155
+ end
156
+ target.puts "- **"
157
+ end
158
+ return filename
159
+ end
160
+ end
161
+
162
+ class App
163
+ include TUI
164
+
165
+ def initialize(argv)
166
+ @argv = argv.dup
167
+ @stdout = STDOUT
168
+ end
169
+
170
+ def run
171
+ options = {}
172
+ parser = OptionParser.new do |opts|
173
+ nl = "\n" << (" " * 37)
174
+ opts.banner = "General usage: phusion-backup <options...>"
175
+ opts.separator ""
176
+
177
+ opts.separator "Backup one or more servers:"
178
+ opts.separator " phusion-backup hostname1 [hostname2 ...]"
179
+ opts.separator ""
180
+ opts.separator " `hostnameX' is either a host name or directory name."
181
+ opts.separator ""
182
+
183
+ opts.separator "Display all servers that phusion-backup knows about:"
184
+ opts.separator " phusion-backup --list"
185
+ opts.separator ""
186
+
187
+ opts.separator "Please read the README for tutorial!"
188
+ opts.separator ""
189
+ opts.separator "Available options:"
190
+ opts.on("--list",
191
+ "Show all servers that phusion-backup knows#{nl}" +
192
+ "about.") do
193
+ options[:list] = true
194
+ end
195
+ opts.on("--generate DIR", String,
196
+ "Generate default files (like files.txt)#{nl}" +
197
+ "in the given directory.") do |dir|
198
+ options[:generate] = dir
199
+ end
200
+ opts.on("-h", "--help", "Show this help message.") do
201
+ options[:help] = true
202
+ end
203
+ end
204
+ begin
205
+ parser.parse!(@argv)
206
+ rescue OptionParser::ParseError => e
207
+ puts e
208
+ puts
209
+ puts "Please see '--help' for valid options."
210
+ exit 1
211
+ end
212
+
213
+ if options[:help]
214
+ puts parser
215
+ return 0
216
+ end
217
+
218
+ core = Core.new(["#{Core::ROOT}/backups", "#{home_dir}/Backups"])
219
+
220
+ if options[:list]
221
+ core.servers.each do |server|
222
+ printf "%-35s %s\n", server.hostname, server.dir
223
+ end
224
+ return 0
225
+ elsif options[:generate]
226
+ core.generate(options[:generate])
227
+ return 0
228
+ end
229
+
230
+ if @argv.empty?
231
+ if core.servers.empty?
232
+ puts_error "You must create a backup specification first. Please read README for tutorial."
233
+ else
234
+ puts_error "Please specify the server you want to backup:"
235
+ puts
236
+ core.servers.each do |server|
237
+ puts " phusion-backup #{server.hostname}"
238
+ end
239
+ puts
240
+ puts "For full usage please see `phusion-backup --help` and the README."
241
+ end
242
+ return 1
243
+ end
244
+
245
+ servers = []
246
+ @argv.each do |name|
247
+ server = core.find_server(name)
248
+ if !server
249
+ puts_error "This program doesn't know anything about the server '#{name}'."
250
+ puts "If you've misspelled the name, please correct it."
251
+ puts "If you want to see a list of available servers that this programs knows about, run <b>phusion-backup --list</b>."
252
+ puts "For general, please refer to the README."
253
+ exit 1
254
+ end
255
+ servers << server
256
+ end
257
+
258
+ servers.each do |server|
259
+ puts "<banner>Backing up #{server.hostname} - #{server.dir}</banner>"
260
+ core.run_backup(server)
261
+ puts
262
+ end
263
+
264
+ return 0
265
+ rescue Core::CommandError
266
+ return 1
267
+ end
268
+
269
+ private
270
+ def home_dir
271
+ return Etc.getpwuid(Process.uid).dir
272
+ end
273
+ end
274
+
275
+ end
276
+
277
+ exit(PhusionBackup::App.new(ARGV).run)
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "phusion-backup"
3
+ s.version = "1.0.0"
4
+ s.authors = ["Hongli Lai"]
5
+ s.date = "2011-03-30"
6
+ s.description = "Simple backup tool utilizing rdiff-backup."
7
+ s.summary = "Simple backup tool utilizing rdiff-backup."
8
+ s.email = "hongli@phusion.nl"
9
+ s.files = Dir[
10
+ "LICENSE.TXT",
11
+ "phusion-backup.gemspec",
12
+ "bin/*",
13
+ "resources/*"
14
+ ]
15
+ s.homepage = "https://github.com/FooBarWidget/crash-watch"
16
+ s.rdoc_options = ["--charset=UTF-8"]
17
+ s.executables = ["phusion-backup"]
18
+ s.require_paths = ["lib"]
19
+ end
20
+
@@ -0,0 +1,33 @@
1
+ # Standard system stuff.
2
+ /etc/skel
3
+ /etc/profile
4
+ /etc/bash.bashrc
5
+ /etc/hostname
6
+ /etc/nanorc
7
+ /etc/screenrc
8
+ /etc/mailname
9
+ /etc/rc.local
10
+ /var/spool/cron
11
+
12
+ # Daemon tools.
13
+ - /etc/service/*/supervise
14
+ /etc/service
15
+
16
+ # Backup all home directories, excluding non-critical things like caches.
17
+ /root
18
+ - /home/*/.gem
19
+ - /home/*/.bundle
20
+ - /home/*/.*/bundle
21
+ /home
22
+
23
+ # Backup all Capistrano-deployed web applications, excluding non-critical things like caches.
24
+ - /u/*/*/shared/cached-copy
25
+ - /u/*/*/shared/bundle
26
+ - /u/*/*/releases/*/.git
27
+ - /u/*/*/releases/*/vendor/bundle
28
+ - /u/*/**/*.log
29
+ /u
30
+
31
+ # Phusion stuff.
32
+ /etc/init.d/firewall
33
+ /opt/production/nginx/conf
@@ -0,0 +1,5 @@
1
+ # Standard, essential tools
2
+ apt-get install bash-completion screen
3
+
4
+ # Compiler toolchain
5
+ apt-get install build-essential gdb
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phusion-backup
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Hongli Lai
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-30 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Simple backup tool utilizing rdiff-backup.
23
+ email: hongli@phusion.nl
24
+ executables:
25
+ - phusion-backup
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - LICENSE.TXT
32
+ - phusion-backup.gemspec
33
+ - bin/phusion-backup
34
+ - resources/default-files.txt
35
+ - resources/default-install-script.txt
36
+ has_rdoc: true
37
+ homepage: https://github.com/FooBarWidget/crash-watch
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --charset=UTF-8
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.5.2
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Simple backup tool utilizing rdiff-backup.
70
+ test_files: []
71
+