ec2onrails-xtreme-head 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +44 -0
- data/Version +1 -0
- data/lib/ec2onrails.rb +20 -0
- data/lib/ec2onrails/capistrano_utils.rb +50 -0
- data/lib/ec2onrails/recipes.rb +135 -0
- data/lib/ec2onrails/recipes/db.rb +369 -0
- data/lib/ec2onrails/recipes/deploy.rb +58 -0
- data/lib/ec2onrails/recipes/server.rb +505 -0
- data/lib/ec2onrails/version_helper.rb +33 -0
- metadata +12 -4
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# This rakefile is for building the EC2 on Rails gem.
|
2
|
+
# To build a server AMI, see server/rakefile.rb
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
require 'rake'
|
7
|
+
|
8
|
+
Jeweler::Tasks.new do |gemspec|
|
9
|
+
gemspec.name = "ec2onrails-xtreme-head"
|
10
|
+
gemspec.summary = "Client-side libraries (Capistrano tasks) for managing and deploying to EC2 on Rails servers."
|
11
|
+
gemspec.description = <<-DESC
|
12
|
+
Deploy a Ruby on Rails app on EC2 in five minutes.
|
13
|
+
EC2 on Rails is an Ubuntu Linux server image for Amazon EC2 that's ready to run a standard
|
14
|
+
Ruby on Rails application with little or no customization.
|
15
|
+
It's a Ruby on Rails virtual appliance.
|
16
|
+
This gem contains Capistrano tasks to manage and deploy to an EC2 on Rails server instance.
|
17
|
+
DESC
|
18
|
+
if gemspec.name == "ec2onrails-xtreme-head"
|
19
|
+
experimental_warning = " *** This is the experimental, pre-release version.*** For the regular version install the gem 'ec2onrails-xtreme'"
|
20
|
+
gemspec.summary += experimental_warning
|
21
|
+
gemspec.description += experimental_warning
|
22
|
+
end
|
23
|
+
gemspec.homepage = "http://ec2onrails.rubyforge.org"
|
24
|
+
|
25
|
+
gemspec.authors = ['Xtreme Labs', 'Dwayne Forde']
|
26
|
+
gemspec.email = "aws@xtremelabs.com"
|
27
|
+
|
28
|
+
gemspec.files = FileList['lib/**/**/*', 'Rakefile', 'Version'].to_a
|
29
|
+
|
30
|
+
gemspec.add_dependency('capistrano', '>= 2.5.19')
|
31
|
+
gemspec.add_dependency('archive-tar-minitar', '>= 0.5.2')
|
32
|
+
gemspec.add_dependency('optiflag', '>= 0.7')
|
33
|
+
|
34
|
+
gemspec.rubyforge_project = gemspec.name
|
35
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
36
|
+
rubyforge.doc_task = "rdoc"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Jeweler::GemcutterTasks.new
|
41
|
+
|
42
|
+
rescue LoadError
|
43
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
44
|
+
end
|
data/Version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.7
|
data/lib/ec2onrails.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# This file is part of EC2 on Rails.
|
2
|
+
# http://rubyforge.org/projects/ec2onrails/
|
3
|
+
#
|
4
|
+
# Copyright 2007 Paul Dowman, http://pauldowman.com/
|
5
|
+
#
|
6
|
+
# EC2 on Rails is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# EC2 on Rails is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
|
20
|
+
$:.unshift File.dirname(__FILE__)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Ec2onrails
|
2
|
+
module CapistranoUtils
|
3
|
+
def run_local(command)
|
4
|
+
result = system command
|
5
|
+
raise("error: #{$?}") unless result
|
6
|
+
end
|
7
|
+
|
8
|
+
def run_init_script(script, arg)
|
9
|
+
# TODO only restart a service if it's already started.
|
10
|
+
# Aside from being smarter and more efficient, This will make sure we
|
11
|
+
# aren't starting a service that shouldn't be started for the current
|
12
|
+
# roles (e.g. don't start nginx when we're not in the web role)
|
13
|
+
# How? Maybe need another param with the process name?
|
14
|
+
sudo "/etc/init.d/#{script} #{arg}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# return hostnames for the role named role_sym that has the specified options
|
18
|
+
def hostnames_for_role(role_sym, options = {})
|
19
|
+
role = roles[role_sym]
|
20
|
+
unless role
|
21
|
+
return []
|
22
|
+
end
|
23
|
+
# make sure we match the server with all the passed in options, BUT the server can
|
24
|
+
# have additional options defined. e.g.: :primary => true and :ebs_vol_id => 'vol-1234abcd'
|
25
|
+
# but we want to select the server where :primary => true
|
26
|
+
role.select{|s|
|
27
|
+
match = true
|
28
|
+
options.each_pair{|k,v| match = false if s.options[k] != v}
|
29
|
+
}.collect{|s| s.host}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Like the capture method, but does not print out error stream and swallows
|
33
|
+
# an exception if the process's exit code != 0
|
34
|
+
# NOTE: this only executes on the first server in the list. Don't use this
|
35
|
+
# to execute a task that has a side-effect (i.e. something that needs to be
|
36
|
+
# run on all servers).
|
37
|
+
def quiet_capture(command, options={})
|
38
|
+
output = ""
|
39
|
+
invoke_command(command, options.merge(:once => true)) do |ch, stream, data|
|
40
|
+
case stream
|
41
|
+
when :out then output << data
|
42
|
+
# when :err then warn "[err :: #{ch[:server]}] #{data}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
return (output || '').strip
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# This file is part of EC2 on Rails.
|
2
|
+
# http://rubyforge.org/projects/ec2onrails/
|
3
|
+
#
|
4
|
+
# Copyright 2007 Paul Dowman, http://pauldowman.com/
|
5
|
+
#
|
6
|
+
# EC2 on Rails is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# EC2 on Rails is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'fileutils'
|
20
|
+
include FileUtils
|
21
|
+
require 'tmpdir'
|
22
|
+
require 'pp'
|
23
|
+
require 'zlib'
|
24
|
+
require 'archive/tar/minitar'
|
25
|
+
include Archive::Tar
|
26
|
+
|
27
|
+
require 'ec2onrails/version_helper'
|
28
|
+
require 'ec2onrails/capistrano_utils'
|
29
|
+
include Ec2onrails::CapistranoUtils
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
Dir[File.join(File.dirname(__FILE__), "recipes/*")].find_all{|x| File.file? x}.each do |recipe|
|
34
|
+
require recipe
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
Capistrano::Configuration.instance.load do
|
39
|
+
|
40
|
+
unless ec2onrails_config
|
41
|
+
raise "ec2onrails_config variable not set. (It should be a hash.)"
|
42
|
+
end
|
43
|
+
|
44
|
+
cfg = ec2onrails_config
|
45
|
+
|
46
|
+
set :ec2onrails_version, Ec2onrails::VersionHelper.string
|
47
|
+
set :deploy_to, "/mnt/app"
|
48
|
+
set :use_sudo, true
|
49
|
+
set :user, "app"
|
50
|
+
|
51
|
+
# in case anyone is still using deploy:cold
|
52
|
+
before "deploy:cold", "ec2onrails:setup"
|
53
|
+
|
54
|
+
# after "deploy:symlink", "ec2onrails:server:set_roles", "ec2onrails:server:init_services"
|
55
|
+
# after "deploy:symlink", "ec2onrails:server:purge_proxy_cache"
|
56
|
+
|
57
|
+
on :load do
|
58
|
+
before "deploy:symlink", "ec2onrails:server:run_rails_rake_gems_install"
|
59
|
+
before "deploy:symlink", "ec2onrails:server:install_system_files"
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
namespace :ec2onrails do
|
64
|
+
desc <<-DESC
|
65
|
+
Show the AMI id's of the current images for this version of \
|
66
|
+
EC2 on Rails.
|
67
|
+
DESC
|
68
|
+
task :ami_ids do
|
69
|
+
puts "32-bit server image (US location) for EC2 on Rails #{ec2onrails_version}: #{Ec2onrails::VersionHelper.ami_ids["us"]["32bit"]}"
|
70
|
+
puts "64-bit server image (US location) for EC2 on Rails #{ec2onrails_version}: #{Ec2onrails::VersionHelper.ami_ids["us"]["64bit"]}"
|
71
|
+
puts "32-bit server image (EU location) for EC2 on Rails #{ec2onrails_version}: #{Ec2onrails::VersionHelper.ami_ids["eu"]["32bit"]}"
|
72
|
+
puts "64-bit server image (EU location) for EC2 on Rails #{ec2onrails_version}: #{Ec2onrails::VersionHelper.ami_ids["eu"]["64bit"]}"
|
73
|
+
end
|
74
|
+
|
75
|
+
desc <<-DESC
|
76
|
+
Copies the public key from the server using the external "ssh"
|
77
|
+
command because Net::SSH, which is used by Capistrano, needs it.
|
78
|
+
This will only work if you have an ssh command in the path.
|
79
|
+
If Capistrano can successfully connect to your EC2 instance you
|
80
|
+
don't need to do this. It will copy from one of the servers
|
81
|
+
at random, this can be overridden by specifying the HOST
|
82
|
+
environment variable
|
83
|
+
DESC
|
84
|
+
task :get_public_key_from_server do
|
85
|
+
host = find_servers_for_task(current_task).first.host
|
86
|
+
privkey = ssh_options[:keys][0]
|
87
|
+
pubkey = "#{privkey}.pub"
|
88
|
+
msg = <<-MSG
|
89
|
+
Your first key in ssh_options[:keys] is #{privkey}, presumably that's
|
90
|
+
your EC2 private key. The public key will be copied from the server
|
91
|
+
named '#{host}' and saved locally as #{pubkey}. Continue? [y/n]
|
92
|
+
MSG
|
93
|
+
choice = nil
|
94
|
+
while choice != "y" && choice != "n"
|
95
|
+
choice = Capistrano::CLI.ui.ask(msg).downcase
|
96
|
+
msg = "Please enter 'y' or 'n'."
|
97
|
+
end
|
98
|
+
if choice == "y"
|
99
|
+
run_local "scp -i '#{privkey}' app@#{host}:.ssh/authorized_keys #{pubkey}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc <<-DESC
|
104
|
+
Prepare a newly-started instance for a cold deploy.
|
105
|
+
DESC
|
106
|
+
task :setup do
|
107
|
+
# we now have some things being included inside the app so we deploy
|
108
|
+
# the app's code to the server before we do any other setup
|
109
|
+
server.upload_deploy_keys
|
110
|
+
deploy.setup
|
111
|
+
deploy.update_code
|
112
|
+
|
113
|
+
ec2onrails.server.allow_sudo do
|
114
|
+
server.set_timezone
|
115
|
+
server.set_mail_forward_address
|
116
|
+
server.install_packages
|
117
|
+
server.install_gems
|
118
|
+
server.run_rails_rake_gems_install
|
119
|
+
server.deploy_files # DEPRECATED, see install_system_files
|
120
|
+
server.install_system_files
|
121
|
+
server.set_roles
|
122
|
+
server.enable_ssl if cfg[:enable_ssl]
|
123
|
+
server.set_rails_env
|
124
|
+
server.restart_services
|
125
|
+
db.create
|
126
|
+
server.harden_server
|
127
|
+
db.enable_ebs
|
128
|
+
db.set_root_password
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
@@ -0,0 +1,369 @@
|
|
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
|
+
puts " * Pausing to give MySQL some time to start up..."
|
56
|
+
sleep 30
|
57
|
+
|
58
|
+
# TODO run init_backup after creating the db. This might be slow, so we need to
|
59
|
+
# check if the db exists, and exit without doing any of this if it exists.
|
60
|
+
|
61
|
+
run %{mysql -u root -e "drop database if exists test; flush privileges;"}
|
62
|
+
# removing anonymous mysql accounts
|
63
|
+
run %{mysql -u root -D mysql -e "delete from db where User = ''; flush privileges;"}
|
64
|
+
run %{mysql -u root -D mysql -e "delete from user where User = ''; flush privileges;"}
|
65
|
+
|
66
|
+
# qoting of database names allows special characters eg (the-database-name)
|
67
|
+
# the quotes need to be double escaped. Once for capistrano and once for the host shell
|
68
|
+
run %{mysql -u root -e "create database if not exists \\`#{cfg[:db_name]}\\`;"}
|
69
|
+
run %{mysql -u root -e "grant all on \\`#{cfg[:db_name]}\\`.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
|
70
|
+
run %{mysql -u root -e "grant reload on *.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
|
71
|
+
run %{mysql -u root -e "grant super on *.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
|
72
|
+
end
|
73
|
+
|
74
|
+
desc <<-DESC
|
75
|
+
Move the MySQL database to Amazon's Elastic Block Store (EBS), \
|
76
|
+
which is a persistant data store for the cloud.
|
77
|
+
OPTIONAL PARAMETERS:
|
78
|
+
* SIZE: Pass in a number representing the GB's to hold, like 10. \
|
79
|
+
It will default to 10 gigs.
|
80
|
+
* VOLUME_ID: The volume_id to use for the mysql database
|
81
|
+
NOTE: keep track of the volume ID, as you'll want to keep this for your \
|
82
|
+
records and probably add it to the :db role in your deploy.rb file \
|
83
|
+
(see the ec2onrails sample deploy.rb file for additional information)
|
84
|
+
DESC
|
85
|
+
task :enable_ebs, :roles => :db, :only => { :primary => true } do
|
86
|
+
# based off of Eric's work:
|
87
|
+
# http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1663&categoryID=100
|
88
|
+
#
|
89
|
+
# EXPLAINATION:
|
90
|
+
# There is a lot going on here! At the end, the setup should be:
|
91
|
+
# * create EBS volume if run outside of the ec2onrails:setup and
|
92
|
+
# VOLUME_ID is not passed in when the cap task is called
|
93
|
+
# * EBS volume attached to /dev/sdh
|
94
|
+
# * format to xfs if new or do a xfs_check if previously existed
|
95
|
+
# * mounted on /var/local and update /etc/fstab
|
96
|
+
# * move /mnt/mysql_data -> /var/local/mysql_data
|
97
|
+
# * move /mnt/log/mysql -> /var/local/log/mysql
|
98
|
+
# * change mysql configs by writing /etc/mysql/conf.d/mysql-ec2-ebs.cnf
|
99
|
+
# * keep a copy of the mysql configs with the EBS volume, and if that volume is hooked into
|
100
|
+
# another instance, make sure the mysql configs that go with that volume are symlinked to /etc/mysql
|
101
|
+
# * update the file locations of the mysql binary logs in /mnt/log/mysql/mysql-bin.index
|
102
|
+
# * symlink the moved folders to their old position... makes the move to EBS transparent
|
103
|
+
# * Amazon doesn't contain EBS information in the meta-data API (yet). So write
|
104
|
+
# /etc/ec2onrails/ebs_info.yml
|
105
|
+
# to contain the meta-data information that we need
|
106
|
+
#
|
107
|
+
# DESIGN CONSIDERATIONS
|
108
|
+
# * only moving mysql data to EBS. seems the most obvious, and if we move over other components
|
109
|
+
# we will have to share that bandwidth (1 Gbps pipe to SAN). So limiting to what we really need
|
110
|
+
# * not moving all mysql logic over (tmp scratch space stays local). Again, this is to limit
|
111
|
+
# unnecessary bandwidth usage, PLUS, we are charged per million IO to EBS
|
112
|
+
#
|
113
|
+
# TODO:
|
114
|
+
# * make sure if we have a predefined ebs_vol_id, that we error out with a nice msg IF the zones do not match
|
115
|
+
# * can we move more of the mysql cache files back to the local disk and off of EBS, like the innodb table caches?
|
116
|
+
# * right now we force this task to only be run on one server; that works for db :primary => true
|
117
|
+
# But what is the best way to make this work if it needs to setup multiple servers (like db slaves)?
|
118
|
+
# I need to figure out how to do a direct mapping from a server definition to a ebs_vol_id
|
119
|
+
# * when we enable slaves and we setup ebs volumes on them, make it transparent to the user.
|
120
|
+
# have the slave create a snapshot of the db.master volume, and then use that to mount from
|
121
|
+
# * need to do a rollback that if the volume is created but something fails, lets uncreate it?
|
122
|
+
# carefull though! If it fails towards the end when information is copied over, it could cause information
|
123
|
+
# to be lost!
|
124
|
+
#
|
125
|
+
|
126
|
+
mysql_dir_root = '/var/local'
|
127
|
+
block_mnt = '/dev/sdh'
|
128
|
+
servers = find_servers_for_task(current_task)
|
129
|
+
|
130
|
+
if servers.empty?
|
131
|
+
raise Capistrano::NoMatchingServersError, "`#{task.fully_qualified_name}' is only run for servers matching #{task.options.inspect}, but no servers matched"
|
132
|
+
elsif servers.size > 1
|
133
|
+
raise Capistrano::Error, "`#{task.fully_qualified_name}' is can only be run on one server, not #{server.size}"
|
134
|
+
end
|
135
|
+
|
136
|
+
vol_id = ENV['VOLUME_ID'] || servers.first.options[:ebs_vol_id]
|
137
|
+
|
138
|
+
#HACK! capistrano doesn't allow arguments to be passed in if we call this task as a method, like 'db.enable_ebs'
|
139
|
+
# the places where we do call it like that, we don't want to force a move to ebs, so....
|
140
|
+
# if the call frame is > 1 (ie, another task called it), do NOT force the ebs move
|
141
|
+
no_force = task_call_frames.size > 1
|
142
|
+
prev_created = !(vol_id.nil? || vol_id.empty?)
|
143
|
+
#no vol_id was passed in, but perhaps it is already mounted...?
|
144
|
+
prev_created = true if !quiet_capture("mount | grep -inr '#{mysql_dir_root}' || echo ''").empty?
|
145
|
+
|
146
|
+
unless no_force && (vol_id.nil? || vol_id.empty?)
|
147
|
+
zone = quiet_capture("/usr/local/ec2onrails/bin/ec2_meta_data -key 'placement/availability-zone'")
|
148
|
+
instance_id = quiet_capture("/usr/local/ec2onrails/bin/ec2_meta_data -key 'instance-id'")
|
149
|
+
|
150
|
+
unless prev_created
|
151
|
+
puts "creating new ebs volume...."
|
152
|
+
size = ENV["SIZE"] || "10"
|
153
|
+
cmd = "ec2-create-volume -s #{size} -z #{zone} 2>&1"
|
154
|
+
puts "running: #{cmd}"
|
155
|
+
output = `#{cmd}`
|
156
|
+
puts output
|
157
|
+
vol_id = (output =~ /^VOLUME\t(.+?)\t/ && $1)
|
158
|
+
puts "NOTE: remember that vol_id"
|
159
|
+
sleep(2)
|
160
|
+
end
|
161
|
+
vol_id.strip! if vol_id
|
162
|
+
if quiet_capture("mount | grep -inr '#{block_mnt}' || echo ''").empty?
|
163
|
+
cmd = "ec2-attach-volume -d #{block_mnt} -i #{instance_id} #{vol_id} 2>&1"
|
164
|
+
puts "running: #{cmd}"
|
165
|
+
output = `#{cmd}`
|
166
|
+
puts output
|
167
|
+
if output =~ /Client.InvalidVolume.ZoneMismatch/i
|
168
|
+
raise Exception, "The volume you are trying to attach does not reside in the zone of your instance. Stopping!"
|
169
|
+
end
|
170
|
+
while !system( "ec2-describe-volumes | grep #{vol_id} | grep attached" )
|
171
|
+
puts "Waiting for #{vol_id} to be attached..."
|
172
|
+
sleep 1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
ec2onrails.server.allow_sudo do
|
177
|
+
# try to format the volume... if it is already formatted, lets run a check on
|
178
|
+
# it to make sure it is ok, and then continue on
|
179
|
+
# if errors, the device is busy...something else is going on here and it is already mounted... skip!
|
180
|
+
if prev_created
|
181
|
+
# Stop the db (mysql server) for cases where this is being run after the original run
|
182
|
+
# If EBS partiion is already mounted and being used by mysql, it will fail when umount is run
|
183
|
+
god_status = quiet_capture("sudo god status")
|
184
|
+
god_status = god_status.empty? ? {} : YAML::load(god_status)
|
185
|
+
start_stop_db = false
|
186
|
+
start_stop_db = god_status['db_primary']['mysql'] == 'up'
|
187
|
+
if start_stop_db
|
188
|
+
stop
|
189
|
+
puts "Waiting for mysql to stop"
|
190
|
+
sleep(10)
|
191
|
+
end
|
192
|
+
quiet_capture("sudo umount #{mysql_dir_root}") #unmount if need to
|
193
|
+
puts "Checking if the filesystem needs to be created (if you created #{vol_id} yourself)"
|
194
|
+
existing = quiet_capture( "mkfs.xfs /dev/sdh", :via => 'sudo' ).match( /existing filesystem/ )
|
195
|
+
sudo "xfs_check #{block_mnt}"
|
196
|
+
# Restart the db if it
|
197
|
+
start if start_stop_db && existing
|
198
|
+
else
|
199
|
+
sudo "mkfs.xfs #{block_mnt}"
|
200
|
+
end
|
201
|
+
|
202
|
+
# if not added to /etc/fstab, lets do so
|
203
|
+
sudo "sh -c \"grep -iqn '#{mysql_dir_root}' /etc/fstab || echo '#{block_mnt} #{mysql_dir_root} xfs noatime 0 0' >> /etc/fstab\""
|
204
|
+
sudo "mkdir -p #{mysql_dir_root}"
|
205
|
+
#if not already mounted, lets mount it
|
206
|
+
sudo "sh -c \"mount | grep -iqn '#{mysql_dir_root}' || mount '#{mysql_dir_root}'\""
|
207
|
+
|
208
|
+
#ok, now lets move the mysql stuff off of /mnt -> mysql_dir_root
|
209
|
+
stop rescue nil #already stopped
|
210
|
+
sudo "mkdir -p #{mysql_dir_root}/log"
|
211
|
+
#move the data over, but keep a symlink to the new location for backwards compatibility
|
212
|
+
#and do not do it if /mnt/mysql_data has already been moved
|
213
|
+
quiet_capture("sudo sh -c 'test ! -d #{mysql_dir_root}/mysql_data && mv /mnt/mysql_data #{mysql_dir_root}/'")
|
214
|
+
sudo "mv /mnt/mysql_data /mnt/mysql_data_old 2>/dev/null || echo"
|
215
|
+
sudo "ln -fs #{mysql_dir_root}/mysql_data /mnt/mysql_data"
|
216
|
+
|
217
|
+
#but keep the tmpdir on mnt
|
218
|
+
sudo "sh -c 'mkdir -p /mnt/tmp/mysql && chown mysql:mysql /mnt/tmp/mysql'"
|
219
|
+
#move the logs over, but keep a symlink to the new location for backwards compatibility
|
220
|
+
#and do not do it if the logs have already been moved
|
221
|
+
quiet_capture("sudo sh -c 'test ! -d #{mysql_dir_root}/log/mysql_data && mv /mnt/log/mysql #{mysql_dir_root}/log/'")
|
222
|
+
sudo "ln -fs #{mysql_dir_root}/log/mysql /mnt/log/mysql"
|
223
|
+
quiet_capture("sudo sh -c \"test -f #{mysql_dir_root}/log/mysql/mysql-bin.index && \
|
224
|
+
perl -pi -e 's%/mnt/log/%#{mysql_dir_root}/log/%' #{mysql_dir_root}/log/mysql/mysql-bin.index\"") rescue false
|
225
|
+
|
226
|
+
if quiet_capture("test -d /var/local/etc/mysql && echo 'yes'").empty?
|
227
|
+
txt = <<-FILE
|
228
|
+
[mysqld]
|
229
|
+
datadir = #{mysql_dir_root}/mysql_data
|
230
|
+
tmpdir = /mnt/tmp/mysql
|
231
|
+
log_bin = #{mysql_dir_root}/log/mysql/mysql-bin.log
|
232
|
+
log_slow_queries = #{mysql_dir_root}/log/mysql/mysql-slow.log
|
233
|
+
FILE
|
234
|
+
put txt, '/tmp/mysql-ec2-ebs.cnf'
|
235
|
+
sudo 'mv /tmp/mysql-ec2-ebs.cnf /etc/mysql/conf.d/mysql-ec2-ebs.cnf'
|
236
|
+
|
237
|
+
#keep a copy
|
238
|
+
sudo "rsync -aR /etc/mysql #{mysql_dir_root}/"
|
239
|
+
end
|
240
|
+
# lets use the mysql configs on the EBS volume
|
241
|
+
sudo "mv /etc/mysql /etc/mysql.orig 2>/dev/null"
|
242
|
+
sudo "ln -sf #{mysql_dir_root}/etc/mysql /etc/mysql"
|
243
|
+
|
244
|
+
#just put a README on the drive so we know what this volume is for!
|
245
|
+
txt = <<-FILE
|
246
|
+
This volume is setup to be used by Ec2onRails in conjunction with Amazon's EBS, for primary MySql database persistence.
|
247
|
+
RAILS_ENV: #{fetch(:rails_env, 'undefined')}
|
248
|
+
DOMAIN: #{fetch(:domain, 'undefined')}
|
249
|
+
|
250
|
+
Modify this volume at your own risk
|
251
|
+
FILE
|
252
|
+
|
253
|
+
put txt, "/tmp/VOLUME-README"
|
254
|
+
sudo "mv /tmp/VOLUME-README #{mysql_dir_root}/VOLUME-README"
|
255
|
+
sudo "touch /etc/ec2onrails/ebs_info.yml"
|
256
|
+
ebs_info = quiet_capture("cat /etc/ec2onrails/ebs_info.yml")
|
257
|
+
|
258
|
+
ebs_info = ebs_info.empty? ? {} : YAML::load(ebs_info)
|
259
|
+
ebs_info[mysql_dir_root] = {'block_loc' => block_mnt, 'volume_id' => vol_id}
|
260
|
+
put(ebs_info.to_yaml, "/tmp/ebs_info.yml")
|
261
|
+
sudo "mv /tmp/ebs_info.yml /etc/ec2onrails/ebs_info.yml"
|
262
|
+
#lets start it back up
|
263
|
+
start
|
264
|
+
end #end of sudo
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
desc <<-DESC
|
270
|
+
[internal] Make sure the MySQL server has been started, just in case the db role
|
271
|
+
hasn't been set, e.g. when called from ec2onrails:setup.
|
272
|
+
(But don't enable monitoring on it.)
|
273
|
+
DESC
|
274
|
+
task :start, :roles => :db do
|
275
|
+
sudo "god start db_primary"
|
276
|
+
end
|
277
|
+
|
278
|
+
task :stop, :roles => :db do
|
279
|
+
sudo "god stop db_primary"
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
desc <<-DESC
|
284
|
+
Drop the MySQL database. Assumes there is no MySQL root \
|
285
|
+
password. If there is a MySQL root password, create a task that removes \
|
286
|
+
it and run that task before this one using a before hook.
|
287
|
+
DESC
|
288
|
+
task :drop, :roles => :db do
|
289
|
+
load_config
|
290
|
+
run %{mysql -u root -e "drop database if exists \\`#{cfg[:db_name]}\\`;"}
|
291
|
+
end
|
292
|
+
|
293
|
+
desc <<-DESC
|
294
|
+
db:drop and db:create.
|
295
|
+
DESC
|
296
|
+
task :recreate, :roles => :db do
|
297
|
+
drop
|
298
|
+
create
|
299
|
+
end
|
300
|
+
|
301
|
+
desc <<-DESC
|
302
|
+
Set a root password for MySQL, using the variable mysql_root_password \
|
303
|
+
if it is set. If this is done db:drop won't work.
|
304
|
+
DESC
|
305
|
+
task :set_root_password, :roles => :db do
|
306
|
+
if cfg[:mysql_root_password]
|
307
|
+
begin
|
308
|
+
run %{mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('#{cfg[:mysql_root_password]}') WHERE User='root'; FLUSH PRIVILEGES;"}
|
309
|
+
rescue Exception => e
|
310
|
+
#most likely because the password was already set
|
311
|
+
#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
|
312
|
+
#what the old root password was
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
desc <<-DESC
|
318
|
+
Dump the MySQL database to ebs (if enabled) or the S3 bucket specified by \
|
319
|
+
ec2onrails_config[:archive_to_bucket]. The filename will be \
|
320
|
+
"database-archive/<timestamp>/dump.sql.gz".
|
321
|
+
DESC
|
322
|
+
task :archive, :roles => :db do
|
323
|
+
run "/usr/local/ec2onrails/bin/backup_app_db --bucket #{cfg[:archive_to_bucket]} --dir #{cfg[:archive_to_bucket_subdir]}"
|
324
|
+
end
|
325
|
+
|
326
|
+
desc <<-DESC
|
327
|
+
Restore the MySQL database from the S3 bucket specified by \
|
328
|
+
ec2onrails_config[:restore_from_bucket]. The archive filename is \
|
329
|
+
expected to be the default, "mysqldump.sql.gz".
|
330
|
+
DESC
|
331
|
+
task :restore, :roles => :db do
|
332
|
+
run "/usr/local/ec2onrails/bin/restore_app_db --bucket #{cfg[:restore_from_bucket]} --dir #{cfg[:restore_from_bucket_subdir]}"
|
333
|
+
end
|
334
|
+
|
335
|
+
desc <<-DESC
|
336
|
+
[internal] Initialize the default backup folder on S3 (i.e. do a full
|
337
|
+
backup of the newly-created db so the automatic incremental backups
|
338
|
+
make sense). NOTE: Only of use if you do not have ebs enabled
|
339
|
+
DESC
|
340
|
+
task :init_backup, :roles => :db do
|
341
|
+
server.allow_sudo do
|
342
|
+
sudo "/usr/local/ec2onrails/bin/backup_app_db --reset"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# do NOT run if the flag does not exist. This is placed by a startup script
|
347
|
+
# and it is only run on the first-startup. This means after the db has been
|
348
|
+
# optimized, this task will not work again.
|
349
|
+
#
|
350
|
+
# Of course you can overload it or call the file directly
|
351
|
+
task :optimize, :roles => :db do
|
352
|
+
if !quiet_capture("test -e /tmp/optimize_db_flag && echo 'file exists'").empty?
|
353
|
+
ec2onrails.server.allow_sudo do
|
354
|
+
begin
|
355
|
+
sudo "/usr/local/ec2onrails/bin/optimize_mysql"
|
356
|
+
ensure
|
357
|
+
sudo "rm -rf /tmp/optimize_db_flag" #remove so we cannot run again
|
358
|
+
end
|
359
|
+
end
|
360
|
+
else
|
361
|
+
puts "skipping as it looks like this task has already been run"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
|
3
|
+
# Override default start/stop/restart tasks for Passenger
|
4
|
+
namespace :deploy do
|
5
|
+
desc <<-DESC
|
6
|
+
Overrides the default Capistrano deploy:start.
|
7
|
+
DESC
|
8
|
+
task :start, :roles => :web do
|
9
|
+
run "touch #{current_release}/tmp/restart.txt"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc <<-DESC
|
13
|
+
Overrides the default Capistrano deploy:stop.
|
14
|
+
DESC
|
15
|
+
task :stop, :roles => :web do
|
16
|
+
# Do nothing,
|
17
|
+
end
|
18
|
+
|
19
|
+
desc <<-DESC
|
20
|
+
Overrides the default Capistrano deploy:restart.
|
21
|
+
DESC
|
22
|
+
task :restart, :roles => :web do
|
23
|
+
run "touch #{current_release}/tmp/restart.txt"
|
24
|
+
end
|
25
|
+
|
26
|
+
desc <<-DESC
|
27
|
+
Run the migrate rake task. By default, it runs this in most recently \
|
28
|
+
deployed version of the app. However, you can specify a different release \
|
29
|
+
via the migrate_target variable, which must be one of :latest (for the \
|
30
|
+
default behavior), or :current (for the release indicated by the `current' \
|
31
|
+
symlink). latest release to be deployed with the update_code task). Strings will work \
|
32
|
+
for those values instead of symbols, too. You can also specify additional \
|
33
|
+
environment variables to pass to rake via the migrate_env variable. Finally, \
|
34
|
+
you can specify the full path to the rake executable by setting the rake \
|
35
|
+
variable. The defaults are:
|
36
|
+
|
37
|
+
set :rake, \"rake\"
|
38
|
+
set :rails_env, \"production\"
|
39
|
+
set :migrate_env, \"\"
|
40
|
+
set :migrate_target, :latest"
|
41
|
+
DESC
|
42
|
+
task :migrate, :roles => :db, :only => { :primary => true } do
|
43
|
+
rake = fetch(:rake, "rake")
|
44
|
+
rails_env = fetch(:rails_env, "production")
|
45
|
+
migrate_env = fetch(:migrate_env, "")
|
46
|
+
migrate_target = fetch(:migrate_target, :latest)
|
47
|
+
|
48
|
+
directory = case migrate_target.to_sym
|
49
|
+
when :current then current_path
|
50
|
+
when :latest then current_release
|
51
|
+
else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
run "cd #{directory}; rvmsudo bundle exec #{rake} RAILS_ENV=#{rails_env} #{migrate_env} db:migrate"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,505 @@
|
|
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
|
+
# Create a list of server roles based on the capistrano roles.
|
13
|
+
# We treat :db specially because it maps to a different rolename on the server
|
14
|
+
# if :primary => true
|
15
|
+
server_roles = {}
|
16
|
+
roles.keys.delete_if{|r|r == :db}.each do |rolename|
|
17
|
+
server_roles[rolename] = hostnames_for_role(rolename)
|
18
|
+
end
|
19
|
+
server_roles[:db_primary] = hostnames_for_role(:db, :primary => true)
|
20
|
+
|
21
|
+
roles_yml = YAML::dump(server_roles)
|
22
|
+
put roles_yml, "/tmp/roles.yml"
|
23
|
+
allow_sudo do
|
24
|
+
sudo "cp /tmp/roles.yml /etc/ec2onrails"
|
25
|
+
#we want everyone to be able to read to it
|
26
|
+
sudo "chmod a+r /etc/ec2onrails/roles.yml"
|
27
|
+
sudo "/usr/local/ec2onrails/bin/set_roles"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
task :init_services do
|
32
|
+
allow_sudo do
|
33
|
+
#lets pick up the new configuration files
|
34
|
+
sudo "/usr/local/ec2onrails/bin/init_services"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc <<-DESC
|
39
|
+
Change the default value of RAILS_ENV on the server.
|
40
|
+
The value is specified in :rails_env.
|
41
|
+
Be sure to do deploy:restart after this.
|
42
|
+
DESC
|
43
|
+
task :set_rails_env do
|
44
|
+
allow_sudo do
|
45
|
+
rails_env = fetch(:rails_env, "production")
|
46
|
+
sudo "/usr/local/ec2onrails/bin/set_rails_env #{rails_env}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc <<-DESC
|
51
|
+
Upgrade to the newest versions of all Ubuntu packages.
|
52
|
+
DESC
|
53
|
+
task :upgrade_packages do
|
54
|
+
allow_sudo do
|
55
|
+
sudo "aptitude -q update"
|
56
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y safe-upgrade'"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
desc <<-DESC
|
61
|
+
Upgrade to the newest versions of all rubygems.
|
62
|
+
DESC
|
63
|
+
task :upgrade_gems do
|
64
|
+
allow_sudo do
|
65
|
+
sudo "gem update --system --no-rdoc --no-ri"
|
66
|
+
sudo "gem update --no-rdoc --no-ri"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
desc <<-DESC
|
71
|
+
Install extra Ubuntu packages. Set ec2onrails_config[:packages], it \
|
72
|
+
should be an array of strings.
|
73
|
+
NOTE: the package installation will be non-interactive, if the packages \
|
74
|
+
require configuration either set ec2onrails_config[:interactive_packages] \
|
75
|
+
like you would for ec2onrails_config[:packages] (we'll flood the server \
|
76
|
+
with 'Y' inputs), or log in as 'root' and run \
|
77
|
+
'dpkg-reconfigure packagename' or replace the package's config files \
|
78
|
+
using the 'ec2onrails:server:deploy_files' task.
|
79
|
+
DESC
|
80
|
+
task :install_packages do
|
81
|
+
allow_sudo do
|
82
|
+
sudo "aptitude -q update"
|
83
|
+
if cfg[:packages] && cfg[:packages].any?
|
84
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install #{cfg[:packages].join(' ')}'"
|
85
|
+
end
|
86
|
+
if cfg[:interactive_packages] && cfg[:interactive_packages].any?
|
87
|
+
# sudo "aptitude install #{cfg[:interactive_packages].join(' ')}", {:env => {'DEBIAN_FRONTEND' => 'readline'} }
|
88
|
+
#trying to pick WHEN to send a Y is a bit tricky...it totally depends on the
|
89
|
+
#interactive package you want to install. FLOODING it with 'Y'... but not sure how
|
90
|
+
#'correct' or robust this is
|
91
|
+
cmd = "sudo sh -c 'export DEBIAN_FRONTEND=readline; aptitude -y -q install #{cfg[:interactive_packages].join(' ')}'"
|
92
|
+
run(cmd) do |channel, stream, data|
|
93
|
+
channel.send_data "Y\n"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
task :configure_firewall do
|
100
|
+
# TODO
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
desc <<-DESC
|
105
|
+
Provide extra security measures. Set ec2onrails_config[:harden_server] = true \
|
106
|
+
to allow the hardening of the server.
|
107
|
+
These security measures are those which can make initial setup and playing around
|
108
|
+
with Ec2onRails tricky. For example, you can be logged out of your server forever
|
109
|
+
DESC
|
110
|
+
task :harden_server do
|
111
|
+
#NOTES: for those security features that will get in the way of ease-of-use
|
112
|
+
# hook them in here
|
113
|
+
# Like encrypting the mnt directory
|
114
|
+
# http://groups.google.com/group/ec2ubuntu/web/encrypting-mnt-using-cryptsetup-on-ubuntu-7-10-gutsy-on-amazon-ec2
|
115
|
+
if cfg[:harden_server]
|
116
|
+
#lets install some extra packages:
|
117
|
+
# denyhosts: sshd security tool. config file is already installed...
|
118
|
+
#
|
119
|
+
security_pkgs = %w{denyhosts}
|
120
|
+
allow_sudo do
|
121
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install #{security_pkgs.join(' ')}'"
|
122
|
+
#lets setup dkim
|
123
|
+
setup_email_signing
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#based on the recipe here (but which is missing a few key steps!)
|
129
|
+
#http://www.howtoforge.com/quick-and-easy-setup-for-domainkeys-using-ubuntu-postfix-and-dkim-filter
|
130
|
+
desc <<-DESC
|
131
|
+
enables dkim signing of outgoing msgs. This helps with fightint spam.
|
132
|
+
You'll have to update your dns records to take advantage of this, but we'll
|
133
|
+
help you out with that
|
134
|
+
NOTE: set ec2onrails_config[:service_domain] = 'yourdomain.com' before running this task
|
135
|
+
DESC
|
136
|
+
task :setup_email_signing do
|
137
|
+
if cfg[:service_domain].nil? || cfg[:service_domain].empty?
|
138
|
+
raise "ERROR: missing the :service_domain key. Please set that in your deploy script if you would like to use this task."
|
139
|
+
end
|
140
|
+
|
141
|
+
domain = cfg[:service_domain]
|
142
|
+
postmaster_email = "postmaster@#{domain}"
|
143
|
+
|
144
|
+
#make the selector something that will help us roll over and expire the old key next year
|
145
|
+
selector = "mail#{Time.now.year.to_s[-2..-1]}" #ie, mail09
|
146
|
+
|
147
|
+
allow_sudo do
|
148
|
+
sudo "sh -c 'export DEBIAN_FRONTEND=noninteractive; aptitude -q -y install postfix dkim-filter'"
|
149
|
+
#do NOT change the size of the key; making it longer can cause problems with some of the dkim implementations
|
150
|
+
|
151
|
+
keys_exist = File.exist?("config/mail/dkim/dkim_#{selector}.private.key") && File.exist?("config/mail/dkim/dkim_#{selector}.public.key")
|
152
|
+
|
153
|
+
unless keys_exist
|
154
|
+
#lets make them!
|
155
|
+
cmds = <<-CMDS
|
156
|
+
mkdir -p config/mail/dkim;
|
157
|
+
cd config/mail/dkim;
|
158
|
+
openssl genrsa -out dkim_#{selector}.private.key 1024;
|
159
|
+
openssl rsa -in dkim_#{selector}.private.key -out dkim_#{selector}.public.key -pubout -outform PEM
|
160
|
+
CMDS
|
161
|
+
system cmds
|
162
|
+
end
|
163
|
+
|
164
|
+
pub_key = File.read("config/mail/dkim/dkim_#{selector}.public.key")
|
165
|
+
pub_key = pub_key.split("\n")[1..-2].join('')
|
166
|
+
|
167
|
+
#lets get the private and public keys up to the server
|
168
|
+
put File.read("config/mail/dkim/dkim_#{selector}.private.key"), "/tmp/dkim_#{selector}.private.key"
|
169
|
+
put File.read("config/mail/dkim/dkim_#{selector}.public.key"), "/tmp/dkim_#{selector}.public.key"
|
170
|
+
sudo "mkdir -p /var/dkim-filter"
|
171
|
+
sudo "mv /tmp/dkim_#{selector}.p*.key /var/dkim-filter/."
|
172
|
+
|
173
|
+
#saw a note that Canonicalization relaxed was helpful for rails applications...
|
174
|
+
#haven't tested that yet
|
175
|
+
dkim_filter_conf = <<-SCRIPT
|
176
|
+
# Log to syslog
|
177
|
+
Syslog yes
|
178
|
+
|
179
|
+
# Sign for example.com with key in /etc/mail/dkim.key using
|
180
|
+
Domain #{domain}
|
181
|
+
KeyFile /var/dkim-filter/dkim_#{selector}.private.key
|
182
|
+
Selector #{selector}
|
183
|
+
|
184
|
+
# Common settings. See dkim-filter.conf(5) for more information.
|
185
|
+
AutoRestart no
|
186
|
+
Background yes
|
187
|
+
SubDomains no
|
188
|
+
Canonicalization relaxed
|
189
|
+
SCRIPT
|
190
|
+
|
191
|
+
put dkim_filter_conf, "/tmp/dkim-filter.conf.tmp"
|
192
|
+
sudo "mv /etc/dkim-filter.conf /etc/dkim-filter.conf.orig"
|
193
|
+
sudo "mv /tmp/dkim-filter.conf.tmp /etc/dkim-filter.conf"
|
194
|
+
cmds = <<-CMDS
|
195
|
+
sudo postconf -e 'myhostname = #{domain}';
|
196
|
+
sudo postconf -e 'mydomain = #{domain}';
|
197
|
+
sudo postconf -e 'myorigin = $mydomain';
|
198
|
+
sudo postconf -e 'mynetworks_style=subnet';
|
199
|
+
sudo postconf -e 'biff = no';
|
200
|
+
sudo postconf -e 'alias_maps = hash:/etc/aliases';
|
201
|
+
sudo postconf -e 'alias_database = hash:/etc/aliases';
|
202
|
+
sudo postconf -e 'mydestination = localdomain, localhost, localhost.localdomain, localhost';
|
203
|
+
sudo postconf -e 'relay_domains=$mydestination';
|
204
|
+
sudo postconf -e 'mynetworks = 127.0.0.0/8';
|
205
|
+
sudo postconf -e 'smtpd_milters = inet:localhost:8891';
|
206
|
+
sudo postconf -e 'non_smtpd_milters = inet:localhost:8891';
|
207
|
+
sudo postconf -e 'milter_protocol = 2';
|
208
|
+
sudo postconf -e 'milter_default_action = accept'
|
209
|
+
CMDS
|
210
|
+
sudo cmds
|
211
|
+
|
212
|
+
#lets lock it down
|
213
|
+
sudo "chown -R dkim-filter:dkim-filter /var/dkim-filter"
|
214
|
+
sudo "chmod 600 /var/dkim-filter/*"
|
215
|
+
|
216
|
+
puts "*" * 80
|
217
|
+
puts "NOTE: you need to do a few things"
|
218
|
+
puts " * created public and private DKIM keys to config/mail/dkim_#{selector}.*.key" unless keys_exist
|
219
|
+
puts "\n"
|
220
|
+
msg = <<-MSG
|
221
|
+
* Enter these *TWO* records into your DNS record:
|
222
|
+
#{selector}._domainkey.#{domain} IN TXT 'k=rsa; t=y; p=#{pub_key}'
|
223
|
+
_domainkey.#{domain} IN TXT 't=y; o=~; r=#{postmaster_email}'
|
224
|
+
|
225
|
+
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:
|
226
|
+
|
227
|
+
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
|
228
|
+
|
229
|
+
|
230
|
+
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.
|
231
|
+
|
232
|
+
|
233
|
+
MSG
|
234
|
+
puts msg
|
235
|
+
|
236
|
+
#sometimes the dkim-filter restart fails; it seems to be a race condition with some of the postfix changes going in...
|
237
|
+
#but a sleep here seems to do the trick.
|
238
|
+
sleep(10)
|
239
|
+
output = quiet_capture "sudo /etc/init.d/dkim-filter restart"
|
240
|
+
if output =~ /smfi_opensocket\(\) failed/
|
241
|
+
#ah, if we didn't sleep enough above, lets try it one more time; but this time it will fail if we still get
|
242
|
+
#the smfi_opensocket error
|
243
|
+
sleep(5)
|
244
|
+
sudo "/etc/init.d/dkim-filter restart 2>&1"
|
245
|
+
end
|
246
|
+
sleep(2)
|
247
|
+
sudo "/etc/init.d/postfix restart 2>&1"
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
desc <<-DESC
|
254
|
+
Install extra rubygems. Set ec2onrails_config[:rubygems], it should \
|
255
|
+
be with an array of strings.
|
256
|
+
DESC
|
257
|
+
task :install_gems do
|
258
|
+
if cfg[:rubygems]
|
259
|
+
allow_sudo do
|
260
|
+
cfg[:rubygems].each do |g|
|
261
|
+
sudo "gem install #{g} --no-rdoc --no-ri"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
task :run_rails_rake_gems_install do
|
268
|
+
#if running under Rails 2.1, lets trigger 'rake gems:install', but in such a way
|
269
|
+
#so it fails gracefully if running rails < 2.1
|
270
|
+
# ALSO, this might be the first time rake is run, and running it as sudo means that
|
271
|
+
# if any plugins are loaded and create directories... like what image_science does for
|
272
|
+
# ruby_inline, then the dirs will be created as root. so trigger the rails loading
|
273
|
+
# very quickly before the sudo is called
|
274
|
+
# run "cd #{release_path} && rake RAILS_ENV=#{rails_env} -T 1>/dev/null && sudo rake RAILS_ENV=#{rails_env} gems:install"
|
275
|
+
allow_sudo do
|
276
|
+
output = run "cd #{release_path} && rake RAILS_ENV=#{rails_env} db:version > /dev/null 2>&1 || rvmsudo bundle install --path vendor/bundle"
|
277
|
+
puts output
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
desc <<-DESC
|
282
|
+
A convenience task to upgrade existing packages and gems and install \
|
283
|
+
specified new ones.
|
284
|
+
DESC
|
285
|
+
task :upgrade_and_install_all do
|
286
|
+
upgrade_packages
|
287
|
+
upgrade_gems
|
288
|
+
install_packages
|
289
|
+
install_gems
|
290
|
+
end
|
291
|
+
|
292
|
+
desc <<-DESC
|
293
|
+
Set the timezone using the value of the variable named timezone. \
|
294
|
+
Valid options for timezone can be determined by the contents of \
|
295
|
+
/usr/share/zoneinfo, which can be seen here: \
|
296
|
+
http://packages.ubuntu.com/cgi-bin/search_contents.pl?searchmode=filelist&word=tzdata&version=gutsy&arch=all&page=1&number=all \
|
297
|
+
Remove 'usr/share/zoneinfo/' from the filename, and use the last \
|
298
|
+
directory and file as the value. For example 'Africa/Abidjan' or \
|
299
|
+
'posix/GMT' or 'Canada/Eastern'.
|
300
|
+
DESC
|
301
|
+
task :set_timezone do
|
302
|
+
if cfg[:timezone]
|
303
|
+
allow_sudo do
|
304
|
+
sudo "bash -c 'echo #{cfg[:timezone]} > /etc/timezone'"
|
305
|
+
sudo "cp /usr/share/zoneinfo/#{cfg[:timezone]} /etc/localtime"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
desc <<-DESC
|
311
|
+
DEPRECATED. See install_system_files.
|
312
|
+
DESC
|
313
|
+
task :deploy_files do
|
314
|
+
if cfg[:server_config_files_root]
|
315
|
+
puts "***** DEPRECATION WARNING: you're using the deploy_files task which has been deprecated" # TODO pointer to documentation
|
316
|
+
begin
|
317
|
+
filename = "config_files.tar"
|
318
|
+
local_file = "#{Dir.tmpdir}/#{filename}"
|
319
|
+
remote_file = "/tmp/#{filename}"
|
320
|
+
FileUtils.cd(cfg[:server_config_files_root]) do
|
321
|
+
File.open(local_file, 'wb') { |tar| Minitar.pack(".", tar) }
|
322
|
+
end
|
323
|
+
put File.read(local_file), remote_file
|
324
|
+
allow_sudo do
|
325
|
+
sudo "tar xvf #{remote_file} -o -C /"
|
326
|
+
end
|
327
|
+
ensure
|
328
|
+
rm_rf local_file
|
329
|
+
run "rm -f #{remote_file}"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
desc <<-DESC
|
335
|
+
Installs files into the system anywhere outside of the Rails app.
|
336
|
+
The directory RAILS_ROOT/config/ec2onrails/system_files can contain
|
337
|
+
files that will be installed into the server relative to "/", and it
|
338
|
+
can contain a manifest file with metadata to change the file owner
|
339
|
+
and permissions, and it allows the files to be cleanly uninstalled
|
340
|
+
from the system.
|
341
|
+
TODO pointer to full documentation
|
342
|
+
DESC
|
343
|
+
task :install_system_files do
|
344
|
+
allow_sudo do
|
345
|
+
sudo "/usr/local/ec2onrails/bin/install_system_files #{release_path}"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
desc <<-DESC
|
350
|
+
Restart a set of services. Set ec2onrails_config[:services_to_restart]
|
351
|
+
to an array of strings. It's assumed that each service has a script
|
352
|
+
in /etc/init.d
|
353
|
+
DESC
|
354
|
+
task :restart_services do
|
355
|
+
if cfg[:services_to_restart] && cfg[:services_to_restart].any?
|
356
|
+
allow_sudo do
|
357
|
+
cfg[:services_to_restart].each do |service|
|
358
|
+
run_init_script(service, "restart")
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
desc <<-DESC
|
365
|
+
Set the email address that mail to the app user forwards to.
|
366
|
+
DESC
|
367
|
+
task :set_mail_forward_address do
|
368
|
+
if cfg[:mail_forward_address]
|
369
|
+
allow_sudo do
|
370
|
+
sudo "sh -c 'echo #{cfg[:mail_forward_address]} > /root/.forward'"
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
desc <<-DESC
|
376
|
+
Enable ssl for the web server. You'll want to replace the default SSL
|
377
|
+
certificate and key files, the certificate file is at
|
378
|
+
/etc/ec2onrails/ssl/cert/ec2onrails-default.crt
|
379
|
+
and a the key file is at
|
380
|
+
/etc/ec2onrails/ssl/private/ec2onrails-default.key
|
381
|
+
(use the deploy_files task).
|
382
|
+
The key file should NOT have a passphrase.
|
383
|
+
DESC
|
384
|
+
task :enable_ssl, :roles => :web do
|
385
|
+
# TODO: enable for nginx
|
386
|
+
# run_init_script("nginx", "restart")
|
387
|
+
end
|
388
|
+
|
389
|
+
desc <<-DESC
|
390
|
+
Upload the app user's SSH deploy keys from
|
391
|
+
config/ec2onrails/deploy_keys to /home/app/.ssh
|
392
|
+
The deploy_keys dir should contain the SSH config files that
|
393
|
+
are needed to deploy your app's source code from your SCM repository
|
394
|
+
(if you're deploying from an SCM repo). This means an SSH private key
|
395
|
+
(named id_dsa), the public key (named id_dsa.pub) and possibly a
|
396
|
+
known_hosts file.
|
397
|
+
They can't deployed via install_system_files because the
|
398
|
+
app user's SSH credentials might be needed to deploy the
|
399
|
+
app itself if it's coming directly from an SCM repository.
|
400
|
+
DESC
|
401
|
+
task :upload_deploy_keys do
|
402
|
+
deploy_keys_dir = "config/ec2onrails/deploy_keys"
|
403
|
+
remote_dir = "/home/app/.ssh"
|
404
|
+
|
405
|
+
if File.exist? deploy_keys_dir
|
406
|
+
run "mkdir -p #{remote_dir}"
|
407
|
+
Dir.chdir deploy_keys_dir do
|
408
|
+
Dir.glob("*").each do |f|
|
409
|
+
remote_file = "#{remote_dir}/#{f}"
|
410
|
+
put File.read(f), "#{remote_file}"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
run "chmod -R go-rwx /home/app/.ssh"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
desc <<-DESC
|
418
|
+
Clear the varnish proxy cache (if caching is enabled, which it isn't
|
419
|
+
by default). Purges all documents from the cache.
|
420
|
+
DESC
|
421
|
+
task :purge_proxy_cache, :roles => :proxy do
|
422
|
+
run "varnishadm -T localhost:6082 'url.purge .*'"
|
423
|
+
end
|
424
|
+
|
425
|
+
desc <<-DESC
|
426
|
+
Restrict the app user's sudo access.
|
427
|
+
Defaults the user to only be able to \
|
428
|
+
sudo to god
|
429
|
+
DESC
|
430
|
+
task :restrict_sudo_access do
|
431
|
+
old_user = fetch(:user)
|
432
|
+
is_rootequiv = capture("groups").split.include?("admin") # check groups before changing user
|
433
|
+
begin
|
434
|
+
set :user, 'ubuntu'
|
435
|
+
sessions.clear #clear out sessions cache..... this way the ssh connections are reinitialized
|
436
|
+
|
437
|
+
# Remove the app user from the "rootequiv" group, this removes full sudo ability
|
438
|
+
if is_rootequiv
|
439
|
+
sudo "deluser app admin"
|
440
|
+
else
|
441
|
+
puts "User 'app' is not a member of group 'rootequiv' (old_user = #{old_user})."
|
442
|
+
end
|
443
|
+
ensure
|
444
|
+
set :user, old_user
|
445
|
+
sessions.clear
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
desc <<-DESC
|
450
|
+
Grant *FULL* sudo access to the app user.
|
451
|
+
This is NOT RECOMMENDED, it will make the 'app' user the
|
452
|
+
equivalent of 'root' until the 'restrict_sudo_access' task is run.
|
453
|
+
Alternatively, a task that requires sudo ability can call the
|
454
|
+
allow_sudo method with a block, this will give the app user sudo
|
455
|
+
ability only while the block is being run.
|
456
|
+
DESC
|
457
|
+
task :grant_sudo_access do
|
458
|
+
allow_sudo
|
459
|
+
end
|
460
|
+
|
461
|
+
@within_sudo = 0
|
462
|
+
def allow_sudo
|
463
|
+
begin
|
464
|
+
@within_sudo += 1
|
465
|
+
old_user = fetch(:user)
|
466
|
+
if @within_sudo > 1
|
467
|
+
yield if block_given?
|
468
|
+
true
|
469
|
+
# The following can break if using multiple instances and a deploy was killed partway.
|
470
|
+
# Disabled for now.
|
471
|
+
# elsif capture("groups").split.include?("rootequiv")
|
472
|
+
# yield if block_given?
|
473
|
+
# false
|
474
|
+
else
|
475
|
+
begin
|
476
|
+
# need to cheat and temporarily set the user to ROOT so we
|
477
|
+
# can temporarily add the app user to the rootequiv group.
|
478
|
+
# we can do this because the root and app user have the same
|
479
|
+
# ssh login preferences....
|
480
|
+
set :user, 'ubuntu'
|
481
|
+
sessions.clear #clear out sessions cache..... this way the ssh connections are reinitialized
|
482
|
+
|
483
|
+
# Temporarily add the app user to the "rootequiv" group, this will give full sudo ability
|
484
|
+
sudo "adduser app admin"
|
485
|
+
|
486
|
+
set :user, old_user
|
487
|
+
sessions.clear
|
488
|
+
yield if block_given?
|
489
|
+
ensure
|
490
|
+
server.restrict_sudo_access if block_given?
|
491
|
+
set :user, old_user
|
492
|
+
sessions.clear
|
493
|
+
true
|
494
|
+
end
|
495
|
+
end
|
496
|
+
ensure
|
497
|
+
@within_sudo -= 1
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
end
|
502
|
+
|
503
|
+
end
|
504
|
+
|
505
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# This file is part of EC2 on Rails.
|
2
|
+
# http://rubyforge.org/projects/ec2onrails/
|
3
|
+
#
|
4
|
+
# Copyright 2007 Paul Dowman, http://pauldowman.com/
|
5
|
+
#
|
6
|
+
# EC2 on Rails is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# EC2 on Rails is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require "yaml"
|
20
|
+
|
21
|
+
module Ec2onrails
|
22
|
+
module VersionHelper
|
23
|
+
ROOT_DIR = File.dirname(__FILE__) + "/../.."
|
24
|
+
|
25
|
+
def self.string
|
26
|
+
File.read(ROOT_DIR + "/VERSION").strip
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.ami_ids
|
30
|
+
YAML::load_file(ROOT_DIR + "/ami_ids.yml")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 7
|
9
|
+
version: 0.0.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Xtreme Labs
|
@@ -70,8 +70,16 @@ extensions: []
|
|
70
70
|
|
71
71
|
extra_rdoc_files: []
|
72
72
|
|
73
|
-
files:
|
74
|
-
|
73
|
+
files:
|
74
|
+
- Rakefile
|
75
|
+
- Version
|
76
|
+
- lib/ec2onrails.rb
|
77
|
+
- lib/ec2onrails/capistrano_utils.rb
|
78
|
+
- lib/ec2onrails/recipes.rb
|
79
|
+
- lib/ec2onrails/recipes/db.rb
|
80
|
+
- lib/ec2onrails/recipes/deploy.rb
|
81
|
+
- lib/ec2onrails/recipes/server.rb
|
82
|
+
- lib/ec2onrails/version_helper.rb
|
75
83
|
has_rdoc: true
|
76
84
|
homepage: http://ec2onrails.rubyforge.org
|
77
85
|
licenses: []
|