skippy-ec2onrails 0.9.10 → 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/Manifest +7 -1
- data/README.textile +10 -13
- data/Rakefile +4 -3
- data/TODO +10 -8
- data/ec2onrails.gemspec +17 -15
- data/examples/deploy.rb +7 -1
- data/lib/ec2onrails/recipes.rb +11 -714
- data/lib/ec2onrails/recipes/db.rb +377 -0
- data/lib/ec2onrails/recipes/deploy.rb +30 -0
- data/lib/ec2onrails/recipes/server.rb +489 -0
- data/lib/ec2onrails/version.rb +1 -1
- data/server/files/etc/apache2/sites-available/app.common +6 -1
- data/server/files/etc/cron.d/{backup_app_db_to_s3 → ec2onrails} +8 -0
- data/server/files/etc/cron.daily/app +17 -2
- data/server/files/etc/cron.hourly/app +16 -2
- data/server/files/etc/cron.monthly/app +16 -2
- data/server/files/etc/cron.weekly/app +16 -2
- data/server/files/etc/ec2onrails/README +1 -1
- data/server/files/etc/god/app.god +7 -2
- data/server/files/etc/god/dkim_filter.god +20 -0
- data/server/files/etc/god/system.god +1 -1
- data/server/files/etc/god/web.god +6 -4
- data/server/files/etc/mysql/my.cnf +3 -0
- data/server/files/etc/nginx/nginx.conf +11 -2
- data/server/files/etc/rcS.d/S91ec2-first-startup +36 -1
- data/server/files/etc/rcS.d/S92ec2-every-startup +29 -1
- data/server/files/etc/rcS.d/S99set_roles +3 -1
- data/server/files/etc/sudoers +26 -1
- data/server/files/usr/bin/god +0 -0
- data/server/files/usr/local/ec2onrails/bin/backup_app_db.rb +3 -2
- data/server/files/usr/local/ec2onrails/bin/backup_dir.rb +89 -0
- data/server/files/usr/local/ec2onrails/bin/exec_runner +9 -6
- data/server/files/usr/local/ec2onrails/bin/init_services.rb +7 -0
- data/server/files/usr/local/ec2onrails/bin/rails_env +1 -2
- data/server/files/usr/local/ec2onrails/bin/setup_web_proxy.rb +32 -28
- data/server/files/usr/local/ec2onrails/bin/update_hostname +40 -0
- data/server/files/usr/local/ec2onrails/lib/mysql_helper.rb +1 -1
- data/server/files/usr/local/ec2onrails/lib/s3_helper.rb +22 -0
- data/server/files/usr/local/ec2onrails/startup-scripts/every-startup/get-hostname.sh +1 -3
- data/server/rakefile.rb +12 -5
- data/test/test_app/config/deploy.rb +1 -1
- metadata +16 -12
@@ -0,0 +1,377 @@
|
|
1
|
+
Capistrano::Configuration.instance.load do
|
2
|
+
cfg = ec2onrails_config
|
3
|
+
|
4
|
+
namespace :ec2onrails do
|
5
|
+
desc <<-DESC
|
6
|
+
Deploy and restore database from S3
|
7
|
+
DESC
|
8
|
+
task :restore_db_and_deploy do
|
9
|
+
db.recreate
|
10
|
+
deploy.update_code
|
11
|
+
deploy.symlink
|
12
|
+
db.restore
|
13
|
+
deploy.migrations
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :db do
|
17
|
+
desc <<-DESC
|
18
|
+
[internal] Load configuration info for the database from
|
19
|
+
config/database.yml, and start mysql (it must be running
|
20
|
+
in order to interact with it).
|
21
|
+
DESC
|
22
|
+
task :load_config do
|
23
|
+
unless hostnames_for_role(:db, :primary => true).empty?
|
24
|
+
db_config = YAML::load(ERB.new(File.read("config/database.yml")).result)[rails_env.to_s] || {}
|
25
|
+
cfg[:db_name] ||= db_config['database']
|
26
|
+
cfg[:db_user] ||= db_config['username'] || db_config['user']
|
27
|
+
cfg[:db_password] ||= db_config['password']
|
28
|
+
cfg[:db_host] ||= db_config['host']
|
29
|
+
cfg[:db_port] ||= db_config['port']
|
30
|
+
cfg[:db_socket] ||= db_config['socket']
|
31
|
+
|
32
|
+
if (cfg[:db_host].nil? || cfg[:db_host].empty?) && (cfg[:db_socket].nil? || cfg[:db_socket].empty?)
|
33
|
+
raise "ERROR: missing database config. Make sure database.yml contains a '#{rails_env}' section with either 'host: hostname' or 'socket: /var/run/mysqld/mysqld.sock'."
|
34
|
+
end
|
35
|
+
|
36
|
+
[cfg[:db_name], cfg[:db_user], cfg[:db_password]].each do |s|
|
37
|
+
if s.nil? || s.empty?
|
38
|
+
raise "ERROR: missing database config. Make sure database.yml contains a '#{rails_env}' section with a database name, user, and password."
|
39
|
+
elsif s.match(/['"]/)
|
40
|
+
raise "ERROR: database config string '#{s}' contains quotes."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
desc <<-DESC
|
47
|
+
Create the MySQL database. Assumes there is no MySQL root \
|
48
|
+
password. To create a MySQL root password create a task that's run \
|
49
|
+
after this task using an after hook.
|
50
|
+
DESC
|
51
|
+
task :create, :roles => :db do
|
52
|
+
on_rollback { drop }
|
53
|
+
load_config
|
54
|
+
start
|
55
|
+
sleep(5) #make sure the db has some time to start up!
|
56
|
+
|
57
|
+
#NOTE: if these commands fail, it is most likely because the command has already been run....
|
58
|
+
|
59
|
+
cmds = []
|
60
|
+
#sometimes there is a test database that comes with the default installation of MySQL...get rid of it!
|
61
|
+
|
62
|
+
cmds << %{mysql -u root -e "drop database if exists test; flush privileges;"}
|
63
|
+
# removing anonymous mysql accounts
|
64
|
+
cmds << %{mysql -u root -D mysql -e "delete from db where User = ''; flush privileges;"}
|
65
|
+
cmds << %{mysql -u root -D mysql -e "delete from user where User = ''; flush privileges;"}
|
66
|
+
|
67
|
+
# qoting of database names allows special characters eg (the-database-name)
|
68
|
+
# the quotes need to be double escaped. Once for capistrano and once for the host shell
|
69
|
+
cmds << %{mysql -u root -e "create database if not exists \\`#{cfg[:db_name]}\\`;"}
|
70
|
+
cmds << %{mysql -u root -e "grant all on \\`#{cfg[:db_name]}\\`.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
|
71
|
+
cmds << %{mysql -u root -e "grant reload on *.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
|
72
|
+
cmds << %{mysql -u root -e "grant super on *.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
|
73
|
+
|
74
|
+
cmds.each do |cmd|
|
75
|
+
begin
|
76
|
+
run cmd
|
77
|
+
rescue Exception => e
|
78
|
+
puts " CONTINUING: this command was previously run, so continuing"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
desc <<-DESC
|
85
|
+
Move the MySQL database to Amazon's Elastic Block Store (EBS), \
|
86
|
+
which is a persistant data store for the cloud.
|
87
|
+
OPTIONAL PARAMETERS:
|
88
|
+
* SIZE: Pass in a number representing the GB's to hold, like 10. \
|
89
|
+
It will default to 10 gigs.
|
90
|
+
* VOLUME_ID: The volume_id to use for the mysql database
|
91
|
+
NOTE: keep track of the volume ID, as you'll want to keep this for your \
|
92
|
+
records and probably add it to the :db role in your deploy.rb file \
|
93
|
+
(see the ec2onrails sample deploy.rb file for additional information)
|
94
|
+
DESC
|
95
|
+
task :enable_ebs, :roles => :db, :only => { :primary => true } do
|
96
|
+
# based off of Eric's work:
|
97
|
+
# http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1663&categoryID=100
|
98
|
+
#
|
99
|
+
# EXPLAINATION:
|
100
|
+
# There is a lot going on here! At the end, the setup should be:
|
101
|
+
# * create EBS volume if run outside of the ec2onrails:setup and
|
102
|
+
# VOLUME_ID is not passed in when the cap task is called
|
103
|
+
# * EBS volume attached to /dev/sdh
|
104
|
+
# * format to xfs if new or do a xfs_check if previously existed
|
105
|
+
# * mounted on /var/local and update /etc/fstab
|
106
|
+
# * move /mnt/mysql_data -> /var/local/mysql_data
|
107
|
+
# * move /mnt/log/mysql -> /var/local/log/mysql
|
108
|
+
# * change mysql configs by writing /etc/mysql/conf.d/mysql-ec2-ebs.cnf
|
109
|
+
# * keep a copy of the mysql configs with the EBS volume, and if that volume is hooked into
|
110
|
+
# another instance, make sure the mysql configs that go with that volume are symlinked to /etc/mysql
|
111
|
+
# * update the file locations of the mysql binary logs in /mnt/log/mysql/mysql-bin.index
|
112
|
+
# * symlink the moved folders to their old position... makes the move to EBS transparent
|
113
|
+
# * Amazon doesn't contain EBS information in the meta-data API (yet). So write
|
114
|
+
# /etc/ec2onrails/ebs_info.yml
|
115
|
+
# to contain the meta-data information that we need
|
116
|
+
#
|
117
|
+
# DESIGN CONSIDERATIONS
|
118
|
+
# * only moving mysql data to EBS. seems the most obvious, and if we move over other components
|
119
|
+
# we will have to share that bandwidth (1 Gbps pipe to SAN). So limiting to what we really need
|
120
|
+
# * not moving all mysql logic over (tmp scratch space stays local). Again, this is to limit
|
121
|
+
# unnecessary bandwidth usage, PLUS, we are charged per million IO to EBS
|
122
|
+
#
|
123
|
+
# TODO:
|
124
|
+
# * make sure if we have a predefined ebs_vol_id, that we error out with a nice msg IF the zones do not match
|
125
|
+
# * can we move more of the mysql cache files back to the local disk and off of EBS, like the innodb table caches?
|
126
|
+
# * right now we force this task to only be run on one server; that works for db :primary => true
|
127
|
+
# But what is the best way to make this work if it needs to setup multiple servers (like db slaves)?
|
128
|
+
# I need to figure out how to do a direct mapping from a server definition to a ebs_vol_id
|
129
|
+
# * when we enable slaves and we setup ebs volumes on them, make it transparent to the user.
|
130
|
+
# have the slave create a snapshot of the db.master volume, and then use that to mount from
|
131
|
+
# * need to do a rollback that if the volume is created but something fails, lets uncreate it?
|
132
|
+
# carefull though! If it fails towards the end when information is copied over, it could cause information
|
133
|
+
# to be lost!
|
134
|
+
#
|
135
|
+
|
136
|
+
mysql_dir_root = '/var/local'
|
137
|
+
block_mnt = '/dev/sdh'
|
138
|
+
servers = find_servers_for_task(current_task)
|
139
|
+
|
140
|
+
if servers.empty?
|
141
|
+
raise Capistrano::NoMatchingServersError, "`#{task.fully_qualified_name}' is only run for servers matching #{task.options.inspect}, but no servers matched"
|
142
|
+
elsif servers.size > 1
|
143
|
+
raise Capistrano::Error, "`#{task.fully_qualified_name}' is can only be run on one server, not #{server.size}"
|
144
|
+
end
|
145
|
+
|
146
|
+
vol_id = ENV['VOLUME_ID'] || servers.first.options[:ebs_vol_id]
|
147
|
+
|
148
|
+
#HACK! capistrano doesn't allow arguments to be passed in if we call this task as a method, like 'db.enable_ebs'
|
149
|
+
# the places where we do call it like that, we don't want to force a move to ebs, so....
|
150
|
+
# if the call frame is > 1 (ie, another task called it), do NOT force the ebs move
|
151
|
+
no_force = task_call_frames.size > 1
|
152
|
+
prev_created = !(vol_id.nil? || vol_id.empty?)
|
153
|
+
#no vol_id was passed in, but perhaps it is already mounted...?
|
154
|
+
prev_created = true if !quiet_capture("mount | grep -inr '#{mysql_dir_root}' || echo ''").empty?
|
155
|
+
|
156
|
+
unless no_force && (vol_id.nil? || vol_id.empty?)
|
157
|
+
zone = quiet_capture("/usr/local/ec2onrails/bin/ec2_meta_data.rb -key 'placement/availability-zone'")
|
158
|
+
instance_id = quiet_capture("/usr/local/ec2onrails/bin/ec2_meta_data.rb -key 'instance-id'")
|
159
|
+
|
160
|
+
unless prev_created
|
161
|
+
puts "creating new ebs volume...."
|
162
|
+
size = ENV["SIZE"] || "10"
|
163
|
+
cmd = "ec2-create-volume -s #{size} -z #{zone} 2>&1"
|
164
|
+
puts "running: #{cmd}"
|
165
|
+
output = `#{cmd}`
|
166
|
+
puts output
|
167
|
+
vol_id = (output =~ /^VOLUME\t(.+?)\t/ && $1)
|
168
|
+
puts "NOTE: remember that vol_id"
|
169
|
+
sleep(2)
|
170
|
+
end
|
171
|
+
vol_id.strip! if vol_id
|
172
|
+
if quiet_capture("mount | grep -inr '#{block_mnt}' || echo ''").empty?
|
173
|
+
cmd = "ec2-attach-volume -d #{block_mnt} -i #{instance_id} #{vol_id} 2>&1"
|
174
|
+
puts "running: #{cmd}"
|
175
|
+
output = `#{cmd}`
|
176
|
+
puts output
|
177
|
+
if output =~ /Client.InvalidVolume.ZoneMismatch/i
|
178
|
+
raise Exception, "The volume you are trying to attach does not reside in the zone of your instance. Stopping!"
|
179
|
+
end
|
180
|
+
while !system( "ec2-describe-volumes | grep #{vol_id} | grep attached" )
|
181
|
+
puts "Waiting for #{vol_id} to be attached..."
|
182
|
+
sleep 1
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
ec2onrails.server.allow_sudo do
|
187
|
+
# try to format the volume... if it is already formatted, lets run a check on
|
188
|
+
# it to make sure it is ok, and then continue on
|
189
|
+
# if errors, the device is busy...something else is going on here and it is already mounted... skip!
|
190
|
+
if prev_created
|
191
|
+
# Stop the db (mysql server) for cases where this is being run after the original run
|
192
|
+
# If EBS partiion is already mounted and being used by mysql, it will fail when umount is run
|
193
|
+
god_status = quiet_capture("sudo god status")
|
194
|
+
god_status = god_status.empty? ? {} : YAML::load(god_status)
|
195
|
+
start_stop_db = false
|
196
|
+
start_stop_db = god_status['db']['mysql'] == 'up'
|
197
|
+
if start_stop_db
|
198
|
+
stop
|
199
|
+
puts "Waiting for mysql to stop"
|
200
|
+
sleep(10)
|
201
|
+
end
|
202
|
+
quiet_capture("sudo umount #{mysql_dir_root}") #unmount if need to
|
203
|
+
puts "Checking if the filesystem needs to be created (if you created #{vol_id} yourself)"
|
204
|
+
existing = quiet_capture( "mkfs.xfs /dev/sdh", :via => 'sudo' ).match( /existing filesystem/ )
|
205
|
+
sudo "xfs_check #{block_mnt}"
|
206
|
+
# Restart the db if it
|
207
|
+
start if start_stop_db && existing
|
208
|
+
else
|
209
|
+
sudo "mkfs.xfs #{block_mnt}"
|
210
|
+
end
|
211
|
+
|
212
|
+
# if not added to /etc/fstab, lets do so
|
213
|
+
sudo "sh -c \"grep -iqn '#{mysql_dir_root}' /etc/fstab || echo '#{block_mnt} #{mysql_dir_root} xfs noatime 0 0' >> /etc/fstab\""
|
214
|
+
sudo "mkdir -p #{mysql_dir_root}"
|
215
|
+
#if not already mounted, lets mount it
|
216
|
+
sudo "sh -c \"mount | grep -iqn '#{mysql_dir_root}' || mount '#{mysql_dir_root}'\""
|
217
|
+
|
218
|
+
#ok, now lets move the mysql stuff off of /mnt -> mysql_dir_root
|
219
|
+
stop rescue nil #already stopped
|
220
|
+
sudo "mkdir -p #{mysql_dir_root}/log"
|
221
|
+
#move the data over, but keep a symlink to the new location for backwards compatibility
|
222
|
+
#and do not do it if /mnt/mysql_data has already been moved
|
223
|
+
quiet_capture("sudo sh -c 'test ! -d #{mysql_dir_root}/mysql_data && mv /mnt/mysql_data #{mysql_dir_root}/'")
|
224
|
+
sudo "mv /mnt/mysql_data /mnt/mysql_data_old 2>/dev/null || echo"
|
225
|
+
sudo "ln -fs #{mysql_dir_root}/mysql_data /mnt/mysql_data"
|
226
|
+
|
227
|
+
#but keep the tmpdir on mnt
|
228
|
+
sudo "sh -c 'mkdir -p /mnt/tmp/mysql && chown mysql:mysql /mnt/tmp/mysql'"
|
229
|
+
#move the logs over, but keep a symlink to the new location for backwards compatibility
|
230
|
+
#and do not do it if the logs have already been moved
|
231
|
+
quiet_capture("sudo sh -c 'test ! -d #{mysql_dir_root}/log/mysql_data && mv /mnt/log/mysql #{mysql_dir_root}/log/'")
|
232
|
+
sudo "ln -fs #{mysql_dir_root}/log/mysql /mnt/log/mysql"
|
233
|
+
quiet_capture("sudo sh -c \"test -f #{mysql_dir_root}/log/mysql/mysql-bin.index && \
|
234
|
+
perl -pi -e 's%/mnt/log/%#{mysql_dir_root}/log/%' #{mysql_dir_root}/log/mysql/mysql-bin.index\"") rescue false
|
235
|
+
|
236
|
+
if quiet_capture("test -d /var/local/etc/mysql && echo 'yes'").empty?
|
237
|
+
txt = <<-FILE
|
238
|
+
[mysqld]
|
239
|
+
datadir = #{mysql_dir_root}/mysql_data
|
240
|
+
tmpdir = /mnt/tmp/mysql
|
241
|
+
log_bin = #{mysql_dir_root}/log/mysql/mysql-bin.log
|
242
|
+
log_slow_queries = #{mysql_dir_root}/log/mysql/mysql-slow.log
|
243
|
+
FILE
|
244
|
+
put txt, '/tmp/mysql-ec2-ebs.cnf'
|
245
|
+
sudo 'mv /tmp/mysql-ec2-ebs.cnf /etc/mysql/conf.d/mysql-ec2-ebs.cnf'
|
246
|
+
|
247
|
+
#keep a copy
|
248
|
+
sudo "rsync -aR /etc/mysql #{mysql_dir_root}/"
|
249
|
+
end
|
250
|
+
# lets use the mysql configs on the EBS volume
|
251
|
+
sudo "mv /etc/mysql /etc/mysql.orig 2>/dev/null"
|
252
|
+
sudo "ln -sf #{mysql_dir_root}/etc/mysql /etc/mysql"
|
253
|
+
|
254
|
+
#just put a README on the drive so we know what this volume is for!
|
255
|
+
txt = <<-FILE
|
256
|
+
This volume is setup to be used by Ec2onRails in conjunction with Amazon's EBS, for primary MySql database persistence.
|
257
|
+
RAILS_ENV: #{fetch(:rails_env, 'undefined')}
|
258
|
+
DOMAIN: #{fetch(:domain, 'undefined')}
|
259
|
+
|
260
|
+
Modify this volume at your own risk
|
261
|
+
FILE
|
262
|
+
|
263
|
+
put txt, "/tmp/VOLUME-README"
|
264
|
+
sudo "mv /tmp/VOLUME-README #{mysql_dir_root}/VOLUME-README"
|
265
|
+
sudo "touch /etc/ec2onrails/ebs_info.yml"
|
266
|
+
ebs_info = quiet_capture("cat /etc/ec2onrails/ebs_info.yml")
|
267
|
+
|
268
|
+
ebs_info = ebs_info.empty? ? {} : YAML::load(ebs_info)
|
269
|
+
ebs_info[mysql_dir_root] = {'block_loc' => block_mnt, 'volume_id' => vol_id}
|
270
|
+
put(ebs_info.to_yaml, "/tmp/ebs_info.yml")
|
271
|
+
sudo "mv /tmp/ebs_info.yml /etc/ec2onrails/ebs_info.yml"
|
272
|
+
#lets start it back up
|
273
|
+
start
|
274
|
+
end #end of sudo
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
desc <<-DESC
|
280
|
+
[internal] Make sure the MySQL server has been started, just in case the db role
|
281
|
+
hasn't been set, e.g. when called from ec2onrails:setup.
|
282
|
+
(But don't enable monitoring on it.)
|
283
|
+
DESC
|
284
|
+
task :start, :roles => :db do
|
285
|
+
sudo "god start db"
|
286
|
+
end
|
287
|
+
|
288
|
+
task :stop, :roles => :db do
|
289
|
+
sudo "god stop db"
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
desc <<-DESC
|
294
|
+
Drop the MySQL database. Assumes there is no MySQL root \
|
295
|
+
password. If there is a MySQL root password, create a task that removes \
|
296
|
+
it and run that task before this one using a before hook.
|
297
|
+
DESC
|
298
|
+
task :drop, :roles => :db do
|
299
|
+
load_config
|
300
|
+
run %{mysql -u root -e "drop database if exists \\`#{cfg[:db_name]}\\`;"}
|
301
|
+
end
|
302
|
+
|
303
|
+
desc <<-DESC
|
304
|
+
db:drop and db:create.
|
305
|
+
DESC
|
306
|
+
task :recreate, :roles => :db do
|
307
|
+
drop
|
308
|
+
create
|
309
|
+
end
|
310
|
+
|
311
|
+
desc <<-DESC
|
312
|
+
Set a root password for MySQL, using the variable mysql_root_password \
|
313
|
+
if it is set. If this is done db:drop won't work.
|
314
|
+
DESC
|
315
|
+
task :set_root_password, :roles => :db do
|
316
|
+
if cfg[:mysql_root_password]
|
317
|
+
begin
|
318
|
+
run %{mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('#{cfg[:mysql_root_password]}') WHERE User='root'; FLUSH PRIVILEGES;"}
|
319
|
+
rescue Exception => e
|
320
|
+
#most likely because the password was already set
|
321
|
+
#in that case this is fine to swallow the error because the task is 'set' db password, not reset it.... we would have to know
|
322
|
+
#what the old root password was
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
desc <<-DESC
|
328
|
+
Dump the MySQL database to ebs (if enabled) or the S3 bucket specified by \
|
329
|
+
ec2onrails_config[:archive_to_bucket]. The filename will be \
|
330
|
+
"database-archive/<timestamp>/dump.sql.gz".
|
331
|
+
DESC
|
332
|
+
task :archive, :roles => :db do
|
333
|
+
run "/usr/local/ec2onrails/bin/backup_app_db.rb --bucket #{cfg[:archive_to_bucket]} --dir #{cfg[:archive_to_bucket_subdir]}"
|
334
|
+
end
|
335
|
+
|
336
|
+
desc <<-DESC
|
337
|
+
Restore the MySQL database from the S3 bucket specified by \
|
338
|
+
ec2onrails_config[:restore_from_bucket]. The archive filename is \
|
339
|
+
expected to be the default, "mysqldump.sql.gz".
|
340
|
+
DESC
|
341
|
+
task :restore, :roles => :db do
|
342
|
+
run "/usr/local/ec2onrails/bin/restore_app_db.rb --bucket #{cfg[:restore_from_bucket]} --dir #{cfg[:restore_from_bucket_subdir]}"
|
343
|
+
end
|
344
|
+
|
345
|
+
desc <<-DESC
|
346
|
+
[internal] Initialize the default backup folder on S3 (i.e. do a full
|
347
|
+
backup of the newly-created db so the automatic incremental backups
|
348
|
+
make sense). NOTE: Only of use if you do not have ebs enabled
|
349
|
+
DESC
|
350
|
+
task :init_backup, :roles => :db do
|
351
|
+
server.allow_sudo do
|
352
|
+
sudo "/usr/local/ec2onrails/bin/backup_app_db.rb --reset"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# do NOT run if the flag does not exist. This is placed by a startup script
|
357
|
+
# and it is only run on the first-startup. This means after the db has been
|
358
|
+
# optimized, this task will not work again.
|
359
|
+
#
|
360
|
+
# Of course you can overload it or call the file directly
|
361
|
+
task :optimize, :roles => :db do
|
362
|
+
if !quiet_capture("test -e /tmp/optimize_db_flag && echo 'file exists'").empty?
|
363
|
+
begin
|
364
|
+
sudo "/usr/local/ec2onrails/bin/optimize_mysql.rb"
|
365
|
+
ensure
|
366
|
+
sudo "rm -rf /tmp/optimize_db_flag" #remove so we cannot run again
|
367
|
+
end
|
368
|
+
else
|
369
|
+
puts "skipping as it looks like this task has already been run"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
cfg = ec2onrails_config
|
3
|
+
|
4
|
+
# override default start/stop/restart tasks to use god
|
5
|
+
namespace :deploy do
|
6
|
+
desc <<-DESC
|
7
|
+
Overrides the default Capistrano deploy:start, uses \
|
8
|
+
'god start app'
|
9
|
+
DESC
|
10
|
+
task :start, :roles => :app do
|
11
|
+
sudo "god start app"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc <<-DESC
|
15
|
+
Overrides the default Capistrano deploy:stop, uses \
|
16
|
+
'god stop app'
|
17
|
+
DESC
|
18
|
+
task :stop, :roles => :app do
|
19
|
+
sudo "god stop app"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc <<-DESC
|
23
|
+
Overrides the default Capistrano deploy:restart, uses \
|
24
|
+
'god restart app'
|
25
|
+
DESC
|
26
|
+
task :restart, :roles => :app do
|
27
|
+
sudo "god restart app"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,489 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
cfg = ec2onrails_config
|
3
|
+
|
4
|
+
namespace :ec2onrails do
|
5
|
+
namespace :server do
|
6
|
+
desc <<-DESC
|
7
|
+
Tell the servers what roles they are in. This configures them with \
|
8
|
+
the appropriate settings for each role, and starts and/or stops the \
|
9
|
+
relevant services.
|
10
|
+
DESC
|
11
|
+
task :set_roles do
|
12
|
+
# TODO generate this based on the roles that actually exist so arbitrary new ones can be added
|
13
|
+
# user_defined_roles = roles
|
14
|
+
# roles.each do |k,v|
|
15
|
+
# puts "#{k}, #{v.servers.first.options.inspect}"
|
16
|
+
# {:primary=>true}
|
17
|
+
#
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
roles = {
|
21
|
+
:web => hostnames_for_role(:web),
|
22
|
+
:app => hostnames_for_role(:app),
|
23
|
+
:db_primary => hostnames_for_role(:db, :primary => true),
|
24
|
+
# doing th ebelow can cause errors elsewhere unless :db is populated.
|
25
|
+
# :db => hostnames_for_role(:db),
|
26
|
+
:memcache => hostnames_for_role(:memcache)
|
27
|
+
}
|
28
|
+
roles_yml = YAML::dump(roles)
|
29
|
+
put roles_yml, "/tmp/roles.yml"
|
30
|
+
server.allow_sudo do
|
31
|
+
sudo "cp /tmp/roles.yml /etc/ec2onrails"
|
32
|
+
#we want everyone to be able to read to it
|
33
|
+
sudo "chmod a+r /etc/ec2onrails/roles.yml"
|
34
|
+
sudo "/usr/local/ec2onrails/bin/set_roles.rb"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
task :init_services do
|
39
|
+
server.allow_sudo do
|
40
|
+
#lets pick up the new configuration files
|
41
|
+
sudo "/usr/local/ec2onrails/bin/init_services.rb"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :setup_web_proxy, :roles => :web do
|
46
|
+
sudo "/usr/local/ec2onrails/bin/setup_web_proxy.rb --mode #{cfg[:web_proxy_server].to_s}"
|
47
|
+
end
|
48
|
+
|
49
|
+
task :setup_elastic_ip, :roles => :web do
|
50
|
+
#TODO: for elastic IP
|
51
|
+
# * make sure the hostname is reset on the web server
|
52
|
+
# * make sure the roles.yml file is updated for ALL servers....
|
53
|
+
vol_id = ENV['ELASTIC_IP'] || servers.first.options[:elastic_ip]
|
54
|
+
ec2onrails.server.allow_sudo do
|
55
|
+
server.set_roles
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc <<-DESC
|
60
|
+
Change the default value of RAILS_ENV on the server. Technically
|
61
|
+
this changes the server's mongrel config to use a different value
|
62
|
+
for "environment". The value is specified in :rails_env.
|
63
|
+
Be sure to do deploy:restart after this.
|
64
|
+
DESC
|
65
|
+
task :set_rails_env do
|
66
|
+
rails_env = fetch(:rails_env, "production")
|
67
|
+
sudo "/usr/local/ec2onrails/bin/set_rails_env #{rails_env}"
|
68
|
+
end
|
69
|
+
|
70
|
+
desc <<-DESC
|
71
|
+
Upgrade to the newest versions of all Ubuntu packages.
|
72
|
+
DESC
|
73
|
+
task :upgrade_packages do
|
74
|
+
sudo "aptitude -q update"
|
75
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y safe-upgrade'"
|
76
|
+
end
|
77
|
+
|
78
|
+
desc <<-DESC
|
79
|
+
Upgrade to the newest versions of all rubygems.
|
80
|
+
DESC
|
81
|
+
task :upgrade_gems do
|
82
|
+
sudo "gem update --system --no-rdoc --no-ri"
|
83
|
+
sudo "gem update --no-rdoc --no-ri" do |ch, str, data|
|
84
|
+
ch[:data] ||= ""
|
85
|
+
ch[:data] << data
|
86
|
+
if data =~ />\s*$/
|
87
|
+
puts data
|
88
|
+
choice = Capistrano::CLI.ui.ask("The gem command is asking for a number:")
|
89
|
+
ch.send_data("#{choice}\n")
|
90
|
+
else
|
91
|
+
puts data
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
desc <<-DESC
|
97
|
+
Install extra Ubuntu packages. Set ec2onrails_config[:packages], it \
|
98
|
+
should be an array of strings.
|
99
|
+
NOTE: the package installation will be non-interactive, if the packages \
|
100
|
+
require configuration either set ec2onrails_config[:interactive_packages] \
|
101
|
+
like you would for ec2onrails_config[:packages] (we'll flood the server \
|
102
|
+
with 'Y' inputs), or log in as 'root' and run \
|
103
|
+
'dpkg-reconfigure packagename' or replace the package's config files \
|
104
|
+
using the 'ec2onrails:server:deploy_files' task.
|
105
|
+
DESC
|
106
|
+
task :install_packages do
|
107
|
+
ec2onrails.server.allow_sudo do
|
108
|
+
sudo "aptitude -q update"
|
109
|
+
if cfg[:packages] && cfg[:packages].any?
|
110
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install #{cfg[:packages].join(' ')}'"
|
111
|
+
end
|
112
|
+
if cfg[:interactive_packages] && cfg[:interactive_packages].any?
|
113
|
+
# sudo "aptitude install #{cfg[:interactive_packages].join(' ')}", {:env => {'DEBIAN_FRONTEND' => 'readline'} }
|
114
|
+
#trying to pick WHEN to send a Y is a bit tricky...it totally depends on the
|
115
|
+
#interactive package you want to install. FLOODING it with 'Y'... but not sure how
|
116
|
+
#'correct' or robust this is
|
117
|
+
cmd = "sudo sh -c 'export DEBIAN_FRONTEND=readline; aptitude -y -q install #{cfg[:interactive_packages].join(' ')}'"
|
118
|
+
run(cmd) do |channel, stream, data|
|
119
|
+
channel.send_data "Y\n"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
task :configure_firewall do
|
126
|
+
# TODO
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
desc <<-DESC
|
131
|
+
Provide extra security measures. Set ec2onrails_config[:harden_server] = true \
|
132
|
+
to allow the hardening of the server.
|
133
|
+
These security measures are those which can make initial setup and playing around
|
134
|
+
with Ec2onRails tricky. For example, you can be logged out of your server forever
|
135
|
+
DESC
|
136
|
+
task :harden_server do
|
137
|
+
#NOTES: for those security features that will get in the way of ease-of-use
|
138
|
+
# hook them in here
|
139
|
+
# Like encrypting the mnt directory
|
140
|
+
# http://groups.google.com/group/ec2ubuntu/web/encrypting-mnt-using-cryptsetup-on-ubuntu-7-10-gutsy-on-amazon-ec2
|
141
|
+
if cfg[:harden_server]
|
142
|
+
#lets install some extra packages:
|
143
|
+
# denyhosts: sshd security tool. config file is already installed...
|
144
|
+
#
|
145
|
+
security_pkgs = %w{denyhosts}
|
146
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install #{security_pkgs.join(' ')}'"
|
147
|
+
|
148
|
+
#lets setup dkim
|
149
|
+
setup_email_signing
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#based on the recipe here (but which is missing a few key steps!)
|
154
|
+
#http://www.howtoforge.com/quick-and-easy-setup-for-domainkeys-using-ubuntu-postfix-and-dkim-filter
|
155
|
+
desc <<-DESC
|
156
|
+
enables dkim signing of outgoing msgs. This helps with fightint spam.
|
157
|
+
You'll have to update your dns records to take advantage of this, but we'll
|
158
|
+
help you out with that
|
159
|
+
NOTE: set ec2onrails_config[:service_domain] = 'yourdomain.com' before running this task
|
160
|
+
DESC
|
161
|
+
task :setup_email_signing, :roles => :app do
|
162
|
+
ec2onrails.server.allow_sudo do
|
163
|
+
if cfg[:service_domain].nil? || cfg[:service_domain].empty?
|
164
|
+
raise "ERROR: missing the :service_domain key. Please set that in your deploy script if you would like to use this task."
|
165
|
+
end
|
166
|
+
|
167
|
+
domain = cfg[:service_domain]
|
168
|
+
postmaster_email = "postmaster@#{domain}"
|
169
|
+
|
170
|
+
#make the selector something that will help us roll over and expire the old key next year
|
171
|
+
selector = "mail#{Time.now.year.to_s[-2..-1]}" #ie, mail09
|
172
|
+
|
173
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install postfix dkim-filter'"
|
174
|
+
#do NOT change the size of the key; making it longer can cause problems with some of the dkim implementations
|
175
|
+
|
176
|
+
keys_exist = File.exist?("config/mail/dkim/dkim_#{selector}.private.key") && File.exist?("config/mail/dkim/dkim_#{selector}.public.key")
|
177
|
+
|
178
|
+
unless keys_exist
|
179
|
+
#lets make them!
|
180
|
+
cmds = <<-CMDS
|
181
|
+
mkdir -p config/mail/dkim;
|
182
|
+
cd config/mail/dkim;
|
183
|
+
openssl genrsa -out dkim_#{selector}.private.key 1024;
|
184
|
+
openssl rsa -in dkim_#{selector}.private.key -out dkim_#{selector}.public.key -pubout -outform PEM
|
185
|
+
CMDS
|
186
|
+
system cmds
|
187
|
+
end
|
188
|
+
|
189
|
+
pub_key = File.read("config/mail/dkim/dkim_#{selector}.public.key")
|
190
|
+
pub_key = pub_key.split("\n")[1..-2].join('')
|
191
|
+
|
192
|
+
#lets get the private and public keys up to the server
|
193
|
+
put File.read("config/mail/dkim/dkim_#{selector}.private.key"), "/tmp/dkim_#{selector}.private.key"
|
194
|
+
put File.read("config/mail/dkim/dkim_#{selector}.public.key"), "/tmp/dkim_#{selector}.public.key"
|
195
|
+
sudo "mkdir -p /var/dkim-filter"
|
196
|
+
sudo "mv /tmp/dkim_#{selector}.p*.key /var/dkim-filter/."
|
197
|
+
|
198
|
+
#saw a note that Canonicalization relaxed was helpful for rails applications...
|
199
|
+
#haven't tested that yet
|
200
|
+
dkim_filter_conf = <<-SCRIPT
|
201
|
+
# Log to syslog
|
202
|
+
Syslog yes
|
203
|
+
|
204
|
+
# Sign for example.com with key in /etc/mail/dkim.key using
|
205
|
+
Domain #{domain}
|
206
|
+
KeyFile /var/dkim-filter/dkim_#{selector}.private.key
|
207
|
+
Selector #{selector}
|
208
|
+
|
209
|
+
# Common settings. See dkim-filter.conf(5) for more information.
|
210
|
+
AutoRestart no
|
211
|
+
Background yes
|
212
|
+
SubDomains no
|
213
|
+
Canonicalization relaxed
|
214
|
+
SCRIPT
|
215
|
+
|
216
|
+
put dkim_filter_conf, "/tmp/dkim-filter.conf.tmp"
|
217
|
+
sudo "mv /etc/dkim-filter.conf /etc/dkim-filter.conf.orig"
|
218
|
+
sudo "mv /tmp/dkim-filter.conf.tmp /etc/dkim-filter.conf"
|
219
|
+
cmds = <<-CMDS
|
220
|
+
sudo postconf -e 'myhostname = #{domain}';
|
221
|
+
sudo postconf -e 'mydomain = #{domain}';
|
222
|
+
sudo postconf -e 'myorigin = $mydomain';
|
223
|
+
sudo postconf -e 'mynetworks_style=subnet';
|
224
|
+
sudo postconf -e 'biff = no';
|
225
|
+
sudo postconf -e 'alias_maps = hash:/etc/aliases';
|
226
|
+
sudo postconf -e 'alias_database = hash:/etc/aliases';
|
227
|
+
sudo postconf -e 'mydestination = localdomain, localhost, localhost.localdomain, localhost';
|
228
|
+
sudo postconf -e 'relay_domains=$mydestination';
|
229
|
+
sudo postconf -e 'mynetworks = 127.0.0.0/8';
|
230
|
+
sudo postconf -e 'smtpd_milters = inet:localhost:8891';
|
231
|
+
sudo postconf -e 'non_smtpd_milters = inet:localhost:8891';
|
232
|
+
sudo postconf -e 'milter_protocol = 2';
|
233
|
+
sudo postconf -e 'milter_default_action = accept'
|
234
|
+
CMDS
|
235
|
+
sudo cmds
|
236
|
+
|
237
|
+
#lets lock it down
|
238
|
+
sudo "chown -R dkim-filter:dkim-filter /var/dkim-filter"
|
239
|
+
sudo "chmod 600 /var/dkim-filter/*"
|
240
|
+
|
241
|
+
puts "*" * 80
|
242
|
+
puts "NOTE: you need to do a few things"
|
243
|
+
puts " * created public and private DKIM keys to config/mail/dkim_#{selector}.*.key" unless keys_exist
|
244
|
+
puts "\n"
|
245
|
+
msg = <<-MSG
|
246
|
+
* Enter these *TWO* records into your DNS record:
|
247
|
+
#{selector}._domainkey.#{domain} IN TXT 'k=rsa; t=y; p=#{pub_key}'
|
248
|
+
_domainkey.#{domain} IN TXT 't=y; o=~; r=#{postmaster_email}'
|
249
|
+
|
250
|
+
I would recommend signing into your ec2 instance and running some test emails. Gmail is very fast in updating their records, but yahoo (as of this writing) is slow and inconsistent. But you can run a command like this to various email address to see how it works:
|
251
|
+
|
252
|
+
echo 'something searchable so you can find it in your spam filter! did dkim work?' | mail -s "my dkim email; lets see how it went" adam@someservice.com
|
253
|
+
|
254
|
+
|
255
|
+
NOTE: in the near future, when things are looking good, if you take away the 't=y; ' from the above two records, it tells the email services that you are no longer testing the service and to treat your signings with tough love.
|
256
|
+
|
257
|
+
|
258
|
+
MSG
|
259
|
+
puts msg
|
260
|
+
|
261
|
+
#sometimes the dkim-filter restart fails; it seems to be a race condition with some of the postfix changes going in...
|
262
|
+
#but a sleep here seems to do the trick.
|
263
|
+
sleep(10)
|
264
|
+
output = quiet_capture "sudo /etc/init.d/dkim-filter restart"
|
265
|
+
if output =~ /smfi_opensocket\(\) failed/
|
266
|
+
#ah, if we didn't sleep enough above, lets try it one more time; but this time it will fail if we still get
|
267
|
+
#the smfi_opensocket error
|
268
|
+
sleep(5)
|
269
|
+
sudo "/etc/init.d/dkim-filter restart 2>&1"
|
270
|
+
end
|
271
|
+
sleep(2)
|
272
|
+
sudo "/etc/init.d/postfix restart 2>&1"
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
desc <<-DESC
|
279
|
+
Install extra rubygems. Set ec2onrails_config[:rubygems], it should \
|
280
|
+
be with an array of strings.
|
281
|
+
DESC
|
282
|
+
task :install_gems do
|
283
|
+
if cfg[:rubygems]
|
284
|
+
cfg[:rubygems].each do |gem|
|
285
|
+
sudo "gem install #{gem} --no-rdoc --no-ri" do |ch, str, data|
|
286
|
+
ch[:data] ||= ""
|
287
|
+
ch[:data] << data
|
288
|
+
if data =~ />\s*$/
|
289
|
+
puts data
|
290
|
+
choice = Capistrano::CLI.ui.ask("The gem command is asking for a number:")
|
291
|
+
ch.send_data("#{choice}\n")
|
292
|
+
else
|
293
|
+
puts data
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
task :run_rails_rake_gems_install do
|
301
|
+
#if running under Rails 2.1, lets trigger 'rake gems:install', but in such a way
|
302
|
+
#so it fails gracefully if running rails < 2.1
|
303
|
+
# ALSO, this might be the first time rake is run, and running it as sudo means that
|
304
|
+
# if any plugins are loaded and create directories... like what image_science does for
|
305
|
+
# ruby_inline, then the dirs will be created as root. so trigger the rails loading
|
306
|
+
# very quickly before the sudo is called
|
307
|
+
# run "cd #{release_path} && rake RAILS_ENV=#{rails_env} -T 1>/dev/null && sudo rake RAILS_ENV=#{rails_env} gems:install"
|
308
|
+
ec2onrails.server.allow_sudo do
|
309
|
+
output = quiet_capture "cd #{release_path} && rake RAILS_ENV=#{rails_env} db:version > /dev/null 2>&1 || sudo rake RAILS_ENV=#{rails_env} gems:install"
|
310
|
+
puts output
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
desc <<-DESC
|
315
|
+
Add extra gem sources to rubygems (to able to fetch gems from for example gems.github.com).
|
316
|
+
Set ec2onrails_config[:rubygems_sources], it should be with an array of strings.
|
317
|
+
DESC
|
318
|
+
task :add_gem_sources do
|
319
|
+
if cfg[:rubygems_sources]
|
320
|
+
cfg[:rubygems_sources].each do |gem_source|
|
321
|
+
sudo "gem sources -a #{gem_source}"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
desc <<-DESC
|
327
|
+
A convenience task to upgrade existing packages and gems and install \
|
328
|
+
specified new ones.
|
329
|
+
DESC
|
330
|
+
task :upgrade_and_install_all do
|
331
|
+
upgrade_packages
|
332
|
+
upgrade_gems
|
333
|
+
install_packages
|
334
|
+
install_gems
|
335
|
+
end
|
336
|
+
|
337
|
+
desc <<-DESC
|
338
|
+
Set the timezone using the value of the variable named timezone. \
|
339
|
+
Valid options for timezone can be determined by the contents of \
|
340
|
+
/usr/share/zoneinfo, which can be seen here: \
|
341
|
+
http://packages.ubuntu.com/cgi-bin/search_contents.pl?searchmode=filelist&word=tzdata&version=gutsy&arch=all&page=1&number=all \
|
342
|
+
Remove 'usr/share/zoneinfo/' from the filename, and use the last \
|
343
|
+
directory and file as the value. For example 'Africa/Abidjan' or \
|
344
|
+
'posix/GMT' or 'Canada/Eastern'.
|
345
|
+
DESC
|
346
|
+
task :set_timezone do
|
347
|
+
if cfg[:timezone]
|
348
|
+
ec2onrails.server.allow_sudo do
|
349
|
+
sudo "bash -c 'echo #{cfg[:timezone]} > /etc/timezone'"
|
350
|
+
sudo "cp /usr/share/zoneinfo/#{cfg[:timezone]} /etc/localtime"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
desc <<-DESC
|
356
|
+
Deploy a set of config files to the server, the files will be owned by \
|
357
|
+
root. This doesn't delete any files from the server. This is intended
|
358
|
+
mainly for customized config files for new packages installed via the \
|
359
|
+
ec2onrails:server:install_packages task. Subdirectories and files \
|
360
|
+
inside here will be placed within the same directory structure \
|
361
|
+
relative to the root of the server's filesystem.
|
362
|
+
DESC
|
363
|
+
task :deploy_files do
|
364
|
+
if cfg[:server_config_files_root]
|
365
|
+
begin
|
366
|
+
filename = "config_files.tar"
|
367
|
+
local_file = "#{Dir.tmpdir}/#{filename}"
|
368
|
+
remote_file = "/tmp/#{filename}"
|
369
|
+
FileUtils.cd(cfg[:server_config_files_root]) do
|
370
|
+
File.open(local_file, 'wb') { |tar| Minitar.pack(".", tar) }
|
371
|
+
end
|
372
|
+
put File.read(local_file), remote_file
|
373
|
+
sudo "tar xvf #{remote_file} -o -C /"
|
374
|
+
ensure
|
375
|
+
rm_rf local_file
|
376
|
+
sudo "rm -f #{remote_file}"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
desc <<-DESC
|
382
|
+
Restart a set of services. Set ec2onrails_config[:services_to_restart] \
|
383
|
+
to an array of strings. It's assumed that each service has a script \
|
384
|
+
in /etc/init.d
|
385
|
+
DESC
|
386
|
+
task :restart_services do
|
387
|
+
if cfg[:services_to_restart] && cfg[:services_to_restart].any?
|
388
|
+
cfg[:services_to_restart].each do |service|
|
389
|
+
run_init_script(service, "restart")
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
desc <<-DESC
|
395
|
+
Set the email address that mail to the app user forwards to.
|
396
|
+
DESC
|
397
|
+
task :set_mail_forward_address do
|
398
|
+
run "echo '#{cfg[:mail_forward_address]}' >> /home/app/.forward" if cfg[:mail_forward_address]
|
399
|
+
# put cfg[:admin_mail_forward_address], "/home/admin/.forward" if cfg[:admin_mail_forward_address]
|
400
|
+
end
|
401
|
+
|
402
|
+
desc <<-DESC
|
403
|
+
Enable ssl for the web server. The SSL cert file should be in
|
404
|
+
/etc/ssl/certs/default.pem and the SSL key file should be in
|
405
|
+
/etc/ssl/private/default.key (use the deploy_files task).
|
406
|
+
DESC
|
407
|
+
task :enable_ssl, :roles => :web do
|
408
|
+
#TODO: enable for nginx
|
409
|
+
sudo "a2enmod ssl"
|
410
|
+
sudo "a2enmod headers" # the headers module is necessary to forward a header so that rails can detect it is handling an SSL connection. NPG 7/11/08
|
411
|
+
sudo "a2ensite default-ssl"
|
412
|
+
run_init_script("web_proxy", "restart")
|
413
|
+
end
|
414
|
+
|
415
|
+
desc <<-DESC
|
416
|
+
Restrict the main user's sudo access.
|
417
|
+
Defaults the user to only be able to \
|
418
|
+
sudo to god
|
419
|
+
DESC
|
420
|
+
task :restrict_sudo_access do
|
421
|
+
old_user = fetch(:user)
|
422
|
+
begin
|
423
|
+
set :user, 'root'
|
424
|
+
sessions.clear #clear out sessions cache..... this way the ssh connections are reinitialized
|
425
|
+
|
426
|
+
run "test ! -L /etc/sudoers || ( echo 'removing symlink /etc/sudoers' ; unlink /etc/sudoers )"
|
427
|
+
run "cp -f /etc/sudoers.restricted_access /etc/sudoers && chmod 440 /etc/sudoers"
|
428
|
+
# this doesn't work; sudo needs the file to not be a symlink
|
429
|
+
# run "ln -sf /etc/sudoers.restricted_access /etc/sudoers"
|
430
|
+
|
431
|
+
ensure
|
432
|
+
set :user, old_user
|
433
|
+
sessions.clear
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
desc <<-DESC
|
438
|
+
Grant *FULL* sudo access to the main user.
|
439
|
+
DESC
|
440
|
+
task :grant_sudo_access do
|
441
|
+
allow_sudo
|
442
|
+
end
|
443
|
+
|
444
|
+
@within_sudo = 0
|
445
|
+
def allow_sudo
|
446
|
+
begin
|
447
|
+
@within_sudo += 1
|
448
|
+
old_user = fetch(:user)
|
449
|
+
if @within_sudo > 1
|
450
|
+
yield if block_given?
|
451
|
+
true
|
452
|
+
elsif capture("ls -l /etc/sudoers /etc/sudoers.full_access | awk '{print $5}'").split.uniq.size == 1
|
453
|
+
yield if block_given?
|
454
|
+
false
|
455
|
+
else
|
456
|
+
begin
|
457
|
+
# need to cheet and temporarily set the user to ROOT so we
|
458
|
+
# can (re)grant full sudo access.
|
459
|
+
# we can do this because the root and app user have the same
|
460
|
+
# ssh login preferences....
|
461
|
+
#
|
462
|
+
# TODO:
|
463
|
+
# do not escalate priv. to root...use another user like 'admin' that has full sudo access
|
464
|
+
set :user, 'root'
|
465
|
+
sessions.clear #clear out sessions cache..... this way the ssh connections are reinitialized
|
466
|
+
|
467
|
+
|
468
|
+
# note, this approach prevents end users from effectively editing the sudoers file directly :(
|
469
|
+
sudo "test ! -L /etc/sudoers || ( echo 'removing symlink /etc/sudoers' ; unlink /etc/sudoers )"
|
470
|
+
run "cp /etc/sudoers.full_access /etc/sudoers && chmod 440 /etc/sudoers"
|
471
|
+
set :user, old_user
|
472
|
+
sessions.clear
|
473
|
+
yield if block_given?
|
474
|
+
ensure
|
475
|
+
server.restrict_sudo_access if block_given?
|
476
|
+
set :user, old_user
|
477
|
+
sessions.clear
|
478
|
+
true
|
479
|
+
end
|
480
|
+
end
|
481
|
+
ensure
|
482
|
+
@within_sudo -= 1
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|