· 7 years ago · Mar 02, 2018, 07:04 AM
1require 'yaml'
2require 'net/smtp'
3require 'logger'
4
5DIR = File.dirname(__FILE__)
6KEYS_DIR = DIR + "/keys"
7IMAGE_DIR = DIR + "/image"
8LOGS_DIR = DIR + "/logs"
9CONFIG_FILE = KEYS_DIR + "/config.yml"
10
11logger = Logger.new(ENV["LOG"] || STDOUT)
12
13def permission_check(file)
14 if ! File.owned?(file) || 0 != (File.stat(file).mode & 077)
15 raise "ERROR: permission: #{file}"
16 end
17end
18
19def load_config
20 if DIR !~ %r{^/mnt(/|$)}
21 raise "ERROR: EC2 Backup should be executed in the /mnt/ directory."
22 end
23
24 permission_check(KEYS_DIR)
25 permission_check(CONFIG_FILE)
26
27 conf = YAML.load_file(CONFIG_FILE)
28 conf["pk_file"] = Dir.glob(KEYS_DIR + "/pk-*.pem").first
29 conf["cert_file"] = Dir.glob(KEYS_DIR + "/cert-*.pem").first
30
31 permission_check(conf["pk_file"])
32 permission_check(conf["cert_file"])
33
34 if conf["ec2_amitool_home"]
35 ENV["EC2_AMITOOL_HOME"] = conf["ec2_amitool_home"]
36 end
37
38 if conf["cmd_path"]
39 conf["cmd_path"] += "/" unless conf["cmd_path"][-1] == ?/
40 elsif ENV["EC2_AMITOOL_HOME"] || ENV["EC2_HOME"]
41 conf["cmd_path"] = ENV["EC2_AMITOOL_HOME"] || ENV["EC2_HOME"]
42 conf["cmd_path"] += (conf["cmd_path"][-1] == ?/ ? "" : "/") + "bin/"
43 else
44 conf["cmd_path"] = ""
45 end
46
47 return conf
48end
49
50def exec_cmd(cmd, opts, dryrun = false)
51 cmd = [cmd]
52 opts.each do |k,v|
53 cmd << "--#{k}"
54 cmd << v unless v.nil?
55 end
56 if dryrun
57 puts cmd.join(" ")
58 return true
59 else
60 return system(*cmd)
61 end
62end
63
64def alert(subject, body)
65 return unless @conf["alert_to"]
66 Net::SMTP.start(@conf["smtp_host"], @conf["smtp_port"]) do |smtp|
67 smtp.send_mail <<EOT, @conf["alert_from"], @conf["alert_to"]
68From: #{@conf["alert_from"]}
69To: #{@conf["alert_to"]}
70Subject: #{subject}
71Date: #{Time::now.strftime("%a, %d %b %Y %X %z")}
72
73#{body}
74
75S3 bucket: #{@conf["bucket"]}
76Image dir: #{IMAGE_DIR}
77EOT
78 end
79end
80
81
82task :default => :info
83
84desc "Show config."
85task :info => :load_config do
86 puts "EC2 AMI Tools: " + %x{#{@conf["cmd_path"]}ec2-ami-tools-version}
87 puts "Bucket: #{@conf["bucket"]}"
88 puts "User number: #{@conf["user_number"]}"
89 puts "AWS Access: #{@conf["access_key"]}"
90 puts "AWS Secret: #{@conf["secret_key"]}"
91 puts "X.509 Cert: #{@conf["cert_file"]}"
92 puts "X.509 Private: #{@conf["pk_file"]}"
93 puts "Architecture: #{@conf["arch"]}"
94 puts "SMTP host: #{@conf["smtp_host"]}:#{@conf["smtp_port"]}"
95 puts "Alert mail from: #{@conf["alert_from"]}"
96 puts "Alert mail to: #{@conf["alert_to"]}"
97end
98
99desc "Execute backup."
100task :backup => [:mkdir, :bundle, :upload, :delete_old, :clean]
101
102task :load_config do
103 @conf = load_config
104end
105
106task :mkdir => :load_config do
107 if File.exist?(IMAGE_DIR)
108 alert("[ERROR] EC2 backup failed: Image dir exists.",
109 "Image dir exists. Seemes last backup ended unexpectedly.")
110 exit 1
111 end
112 Dir.mkdir(IMAGE_DIR, 0700)
113end
114
115task :bundle => :load_config do
116 logger.info "--- START bundle: #{Time.now.to_s}"
117 start = Time.now.to_i
118
119 cmd = "#{@conf["cmd_path"]}ec2-bundle-vol"
120 opts = {
121 :destination => IMAGE_DIR,
122 :privatekey => @conf["pk_file"],
123 :cert => @conf["cert_file"],
124 :user => @conf["user_number"],
125 :arch => @conf["arch"]
126 }
127 unless exec_cmd(cmd, opts, ENV["DRYRUN"])
128 alert("[ERROR] EC2 backup failed: Can't create image.", "See log file.")
129 exit 1
130 end
131
132 logger.info "--- END bundle: #{Time.now.to_s} (#{Time.now.to_i - start} sec.)"
133end
134
135task :upload => :load_config do
136 logger.info "--- START upload: #{Time.now.to_s}"
137 start = Time.now.to_i
138
139 cmd = "#{@conf["cmd_path"]}ec2-upload-bundle"
140 date = Time.now.strftime("%Y%m%d%H%M%S")
141 opts = {
142 :retry => nil,
143 :bucket => "#{@conf["bucket"]}/#{date}",
144 :manifest => "#{IMAGE_DIR}/image.manifest.xml",
145 :"access-key" => @conf["access_key"],
146 :"secret-key" => @conf["secret_key"]
147 }
148 unless exec_cmd(cmd, opts, ENV["DRYRUN"])
149 alert("[ERROR] EC2 backup failed: Can't upload image.", "See log file.")
150 exit 1
151 end
152
153 logger.info "--- END upload: #{Time.now.to_s} (#{Time.now.to_i - start} sec.)"
154end
155
156task :clean => :load_config do
157 begin
158 FileUtils.rm_r(IMAGE_DIR)
159 rescue => e
160 alert("[ERROR] EC2 backup failed: Can't remove image dir.", "")
161 exit 1
162 end
163end
164
165def load_s3_objects
166 AWS::S3::Base.establish_connection!(
167 :access_key_id => @conf["access_key"],
168 :secret_access_key => @conf["secret_key"]
169 )
170 (bucket, key) = @conf["bucket"].split("/", 2)
171 objects = AWS::S3::Bucket.objects(bucket, :prefix => key)
172 result = {}
173 objects.each do |obj|
174 result[obj.key] = obj
175 end
176 return result
177end
178
179
180task :require_aws_s3 do
181 begin
182 require 'aws/s3'
183 rescue Exception => e
184 puts "ERROR: AWS::S3 must be installed."
185 puts " $ sudo gem install aws-s3"
186 raise e
187 end
188end
189
190desc "Show contents of S3"
191task :list => [:load_config, :require_aws_s3] do
192 objects = load_s3_objects
193 objects.keys.sort.each do |k|
194 obj = objects[k]
195 about = obj.about
196 puts obj.key + "\t" +
197 about["last-modified"] + "\t" +
198 about["content-length"]
199 end
200end
201
202desc "Delete old backups"
203task :delete_old => [:load_config, :require_aws_s3] do
204 objects = load_s3_objects
205 (bucket,prefix) = @conf["bucket"].split("/", 2)
206 dates = []
207 objects.each do |k,v|
208 date = k[prefix.length+1, 14]
209 next unless date =~ /^\d{14}$/
210 dates << date unless dates.include? date
211 end
212 remove_list = dates.sort.reverse[@conf["hold"].to_i..-1].to_a
213 n = 0
214 objects.each do |k,v|
215 date = k[prefix.length+1, 14]
216 if remove_list.include? date
217 logger.info "Delete: #{k}"
218 v.delete
219 n+=1
220 end
221 end
222# logger.info "Delete #{n} files from #{@conf["bucket"]}/#{remove_list.join(",")}"
223end
224
225
226desc "Try to send alert mail."
227task :alert_test => :load_config do
228 alert("[TEST] EC2 backup alert test", "This is test.")
229end
230
231
232desc "Initialize environment."
233task :init do
234 unless File.exist?(KEYS_DIR)
235 Dir.mkdir(KEYS_DIR, 0700)
236 logger.info "Created: #{KEYS_DIR}"
237
238 open(KEYS_DIR + "/pk-backup.pem", "w") {}
239 open(KEYS_DIR + "/cert-backup.pem", "w") {}
240 end
241
242 unless File.exist?(CONFIG_FILE)
243 open(CONFIG_FILE, "w") do |io|
244 io << <<EOT
245bucket: site.example.com/ami/backup
246user_number: 0000-0000-0000
247access_key: 0123456789ABCDEFGHIJ
248secret_key: 0123456789ABCDEFGHIJ
249arch: i386
250hold: 3
251
252smtp_host: localhost
253smtp_port: 25
254alert_from: admin@example.com
255alert_to: admin@example.com
256
257# ec2_amitool_home: /usr/local/ec2-ami-tools
258# cmd_path: /usr/local/bin
259EOT
260 end
261 File.chmod(0700, CONFIG_FILE)
262 logger.info "Created: #{CONFIG_FILE}"
263 end
264
265 Dir.mkdir(LOGS_DIR, 0700) unless File.exist?(LOGS_DIR)
266 msg = <<EOT
267
268--------------------------------------------------------------------------------
269Initialized.
270--------------------------------------------------------------------------------
271Edit:
272#{CONFIG_FILE}
273
274Put keys:
275#{KEYS_DIR}/pk-*.pem
276#{KEYS_DIR}/cert-*.pem
277--------------------------------------------------------------------------------
278EOT
279 logger.info msg
280end