-
Notifications
You must be signed in to change notification settings - Fork 6
/
drupal_db_dump
executable file
·217 lines (182 loc) · 6.42 KB
/
drupal_db_dump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/usr/bin/env ruby
############################################################
#
# Title: Drupal Database Backup Utility
# Created: Nov 04, 2013
# Author: rshott@sfu.ca
# Version: 1.0
############################################################
### GLOBAL VARIABLES ###
VERS = 1.0
DAY = `date +\%d`.chomp
MONTH = `date +\%m`.chomp
YEAR = `date +\%Y`.chomp
DATE = "#{YEAR}-#{MONTH}-#{DAY}"
### LIBRARIES ###
require 'fileutils'
require 'optparse'
### CLASSES ###
class Drupal
attr_reader :site_name, :site_path, :dump_path, :active
def initialize(sites_path, dump_path, archive)
@site_name = sites_path.split('/').last
@site_path = sites_path
@dump_path = "#{dump_path}/#{@site_name}"
@dump_folder = "#{@dump_path}/#{DATE}_#{@site_name}"
@archive = archive
@active = File.exists?("#{@site_path}/settings.php") ? true : false
end
def backup
unless File.exists?("#{@dump_path}/#{DATE}_#{@site_name}.tar.gz")
begin
Dir.chdir(@site_path)
FileUtils.mkdir_p("#{@dump_folder}", :mode => 0700)
return unless dump
return unless create_md5
Dir.chdir(@dump_path)
return unless compress
puts "The database dump for #{@site_name} was successful."
rescue Errno::ENOENT => e
message = 'An error occured while trying to dump the database for #{@site_name}:' + "\n\n" + "Error Message: " + e.message + "\n" + "Backtrace: " + e.backtrace.inspect
puts message
return
end
cleanup if DAY.to_i == 1 && @archive
else
puts "A dump today already exists for #{@site_name}."
end
end
private
def dump
puts @dump_folder
unless system(%Q{drush --result-file='#{@dump_folder}/#{@site_name}.sql' --quiet sql-dump})
puts "Could not dump the database for #{@site_name}."
`rm -rf "#{@dump_folder}"`
return false
end
true
end
def create_md5
md5 = %x{openssl md5 "#{@dump_folder}/#{@site_name}.sql" | awk '{print $NF}'}
unless $?.exitstatus == 0
puts "Could not create an md5 checksum for the database of #{@site_name}, leaving dump as is."
return false
else
File.open("#{@dump_folder}/checksum.txt", 'w') { |f| f.write "#{@site_name} #{md5}" }
true
end
end
def compress
unless system('bash', '-c', %Q{tar czf #{DATE}_#{@site_name}.tar.gz #{DATE}_#{@site_name} &> /dev/null && rm -rf "#{@dump_folder}"})
puts "Could not compress dump for #{@site_name}, leaving dump as is."
return false
end
true
end
def cleanup
files = Dir.glob("*_#{@site_name}.tar.gz").sort
month_count = files.collect { |x| x.split('-')[1] }.uniq.length
if month_count == 2
begin
year, month = last_backup_period
FileUtils.mkdir('monthly_archives', :mode => 0700 ) unless File.directory?('monthly_archives')
last_months_files = Dir.glob("#{@dump_path}/#{year}-#{month}-*_#{@site_name}.tar.gz").sort
unless last_months_files.empty?
FileUtils.mv(last_months_files.pop, 'monthly_archives/')
FileUtils.rm(last_months_files, :force => true)
else
puts "There was nothing to archive for #{@site_name}."
end
rescue Errno::ENOENT => e
message = 'An error occured while trying to archive for #{@site_name}:' + "\n\n" + "Error Message: " + e.message + "\n" + "Backtrace: " + e.backtrace.inspect
puts message
return
end
elsif month_count >= 3
puts "Backups span more than two months for #{@site_name}. Please manually resolve this if you want archiving to work."
end
end
def last_backup_period
if MONTH.to_i == 1
return YEAR.to_i - 1, 12
else
return YEAR, sprintf('%02d', MONTH.to_i - 1)
end
end
end
### METHODS ###
def sanity_check(options)
error_messages = []
error_messages << "The Drupal sites path needs to be an existing directory - #{options[:sites_location]}" unless File.directory?(options[:sites_location])
error_messages << "The dump path needs to be an existing directory - #{options[:dump_path]}" unless File.directory?(options[:dump_path])
error_messages << "Drush requires the dump path to have no spaces - #{options[:dump_path]}" if options[:dump_path].index(/\s/)
check = {}
check['drush'] = system('bash', '-c', 'which drush &> /dev/null')
check['openssl'] = system('bash', '-c', 'which openssl &> /dev/null')
check['tar'] = system('bash', '-c', 'which tar &> /dev/null')
check['rm'] = system('bash', '-c', 'which rm &> /dev/null')
check['date'] = system('bash', '-c', 'which date &> /dev/null')
check.each do |key, value|
error_messages << "This script requires #{key}. Please ensure it's installed and in your PATH." if value == false
end
unless error_messages.empty?
puts error_messages
exit 1
end
end
### ARGUMENT PARSING ###
options = {}
optparse = OptionParser.new do |opts|
opts.banner = "Drupal Database Dump (drupal_db_dump)\nUsage: #{__FILE__} --site-location /path/to/sites --path /path"
opts.separator ""
opts.separator "Specific options:"
opts.on("-s", "--site-location /path/to/sites", "The path to your Drupal sites directory.") do |db|
options[:sites_location] = db
end
opts.on("-p", "--path /path", "Path to store the dump files. Must not contain spaces.") do |path|
options[:dump_path] = path
end
options[:archive] = false
opts.on("-a", "--archive", "Purge all but the last dump of the previous month when a new months starts") do |bool|
options[:archive] = bool
end
opts.on_tail( '-h', '--help', 'Display this screen' ) do
puts opts
exit
end
opts.on_tail( '--version', 'Show version' ) do
puts "Version: #{VERS}"
exit
end
end
other_opts = []
begin
optparse.parse!
mandatory = [:sites_location, :dump_path]
missing = mandatory.select{ |param| options[param].nil? }
unless missing.empty?
puts "Missing options: #{missing.join(', ')}"
puts optparse
exit
end
sanity_check(options)
rescue OptionParser::MissingArgument
puts $!.to_s
puts optparse
exit 1
rescue OptionParser::InvalidOption => e
puts e.to_s
exit 1
end
### MAIN ###
drupal_sites_location = File.expand_path(options[:sites_location])
dump_path = File.expand_path(options[:dump_path])
Dir.glob("#{drupal_sites_location}/*").each do |site|
drupal_site = Drupal.new(site, dump_path, options[:archive])
if drupal_site.active
puts "\n\n=== #{drupal_site.site_name} ==="
drupal_site.backup
end
end
puts "\n\n"
exit 0