1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
| require 'rvm/capistrano'
set :rvm_type, :user
# repo details
set :scm, :git
# need to clean shared/cached-copy if changed repository
set :repository, "your repository url"
# set :branch, "master"
set :git_enable_submodules, 1
# bundler bootstrap
require 'bundler/capistrano'
set :bundle_without, [:darwin, :development, :test]
# Multi stage
# https://github.com/capistrano/capistrano/wiki/2.x-Multistage-Extension
# https://github.com/VinceMD/Scem/wiki/Deploying-on-production
set :stages, %w(production)
set :default_stage, "production" # require config/deploy/staging.rb
# set :default_stage, "production" # require config/deploy/production.rb
require 'capistrano/ext/multistage'
# server details
default_run_options[:pty] = true # apparently helps with passphrase prompting
ssh_options[:forward_agent] = true # tells cap to use my local private key
set :deploy_via, :remote_cache
set :use_sudo, false
# integrate whenever
# when using bundler
# set :whenever_command, "bundle exec whenever"
# when using different environments
# set :whenever_environment, defer { stage }
# set :whenever_identifier, defer { "#{fetch(:application)}-#{fetch(:rails_env)}" }
# require "whenever/capistrano"
# https://github.com/javan/whenever/blob/master/lib/whenever/capistrano.rb
# tasks
namespace :deploy do
task :start, :roles => :app do
run "touch #{current_path}/tmp/restart.txt"
end
task :stop, :roles => :app do
# Do nothing.
end
desc "Restart Application"
task :restart, :roles => :app do
run "touch #{current_path}/tmp/restart.txt"
end
desc "Symlink shared resources on each release"
task :symlink_shared, :roles => :app do
%w{database settings.local}.each do |file|
run "ln -nfs #{shared_path}/config/#{file}.yml #{release_path}/config/#{file}.yml"
end
# link dirs in public/
%w{uploads}.each do |dir|
run "mkdir -p #{shared_path}/public/#{dir}"
run "ln -nfs #{shared_path}/public/#{dir} #{release_path}/public/#{dir}"
end
end
desc "Initialize configuration using example files provided in the distribution"
task :upload_config do
%w{config}.each do |dir|
run "mkdir -p #{shared_path}/#{dir}"
end
Dir["config/*.yml.example"].each do |file|
top.upload(File.expand_path(file), "#{shared_path}/config/#{File.basename(file, '.example')}")
end
end
desc 'Visit the app'
task :visit_web do
system "open #{app_url}"
end
end
after 'deploy:setup', 'deploy:upload_config'
after 'deploy:update_code', 'deploy:symlink_shared'
after 'deploy:restart', 'deploy:visit_web'
after 'deploy:migrations', 'deploy:cleanup'
set :keep_releases, 7 # number for keeping old releases
after 'deploy', 'deploy:cleanup'
namespace :db do
desc "Create db for current env"
task :create do
run "cd #{current_path}; bundle exec rake db:create RAILS_ENV=#{rails_env}"
puts 'could be able to run `cap deploy:migrate` now'
end
desc "Populates the Production Database"
task :seed do
puts "\n\n=== Populating the Production Database! ===\n\n"
run "cd #{current_path}; bundle exec rake db:seed RAILS_ENV=#{rails_env}"
end
end
# http://guides.rubyonrails.org/asset_pipeline.html#precompiling-assets
# https://github.com/capistrano/capistrano/blob/master/lib/capistrano/recipes/deploy/assets.rb
load 'deploy/assets' unless (ARGV.join == "deploy:update" || ARGV.last == 'deploy:update')
# then we got these tasks:
# cap deploy:assets:clean # Run the asset clean rake task.
# cap deploy:assets:precompile # Run the asset precompilation rake task.
namespace :remote do
desc "Open the rails console on one of the remote servers"
task :console, :roles => :app do
hostname = find_servers_for_task(current_task).first
command = "cd #{current_path} && bundle exec rails console #{fetch(:rails_env)}"
if fetch(:rvm_ruby_string)
# set rvm shell and get ride of "'"
# https://github.com/wayneeseguin/rvm/blob/master/lib/rvm/capistrano.rb
rvm_shell = %{rvm_path=$HOME/.rvm $HOME/.rvm/bin/rvm-shell "#{fetch(:rvm_ruby_string)}"}
command = %{#{rvm_shell} -c "#{command}"}
else
command = %{source ~/.profile && "#{command}"}
end
exec %{ssh -l #{user} #{hostname} -t '#{command}'}
end
desc "run rake task. e.g.: `cap remote:rake db:version`"
task :rake do
ARGV.values_at(Range.new(ARGV.index('remote:rake')+1, -1)).each do |rake_task|
top.run "cd #{current_path} && RAILS_ENV=#{rails_env} bundle exec rake #{rake_task}"
end
exit(0)
end
desc "run remote command. e.g.: `cap remote:run 'tail -n 10 log/production.log'`"
task :run do
command = ARGV.values_at(Range.new(ARGV.index('remote:run')+1, -1))
top.run "cd #{current_path}; RAILS_ENV=#{rails_env} #{command*' '}"
exit(0)
end
desc 'run specified rails code on server. e.g.: `cap remote:runner p User.all` or `cap remote:runner "User.all.each{ |u| p u }"`'
task :runner do
command=ARGV.values_at(Range.new(ARGV.index('remote:runner')+1,-1))
top.run "cd #{current_path}; RAILS_ENV=#{rails_env} bundle exec rails runner '#{command*' '}'"
exit(0)
end
desc "tail log on remote server"
task :tail_log do
top.run "tail -f #{current_path}/log/#{rails_env}.log" do |channel, stream, data|
puts "#{data}"
break if stream == :err
end
exit(0)
end
end
namespace :update do
desc "Dump remote database into tmp/, download file to local machine, import into local database"
task :database do
# config
remote_db_yml_path = "#{shared_path}/config/database.yml"
remote_db_yml_on_local_path = "tmp/database_#{rails_env}.yml"
# First lets get the remote database config file so that we can read in the database settings
get remote_db_yml_path, remote_db_yml_on_local_path
# load the remote settings within the database file
remote_settings = YAML::load_file(remote_db_yml_on_local_path)[rails_env]
remote_sql_file_path = "#{current_path}/tmp/#{rails_env}-#{remote_settings["database"]}-dump.sql"
remote_sql_gz_file_path = "#{remote_sql_file_path}.gz"
local_sql_file_path = "tmp/#{rails_env}-#{remote_settings["database"]}-#{Time.now.strftime("%Y-%m-%d_%H:%M:%S")}.sql"
local_sql_gz_file_path = "#{local_sql_file_path}.gz"
# we also need the local settings so that we can import the fresh database properly
local_settings = YAML::load_file("config/database.yml")[rails_env]
# dump the remote database and store it in the current path's tmp directory.
run "mysqldump -u'#{remote_settings["username"]}' -p'#{remote_settings["password"]}' #{"-h '#{remote_settings["host"]}'" if remote_settings["host"]} '#{remote_settings["database"]}' > #{remote_sql_file_path}"
# gzip db
run "gzip -f #{remote_sql_file_path}"
# download gz file to local
get remote_sql_gz_file_path, local_sql_gz_file_path
# unzip sql
run_locally "gunzip #{local_sql_gz_file_path}"
# import db to local db
# may need to run `RAILS_ENV=production rake db:create` on local first
run_locally("mysql -u#{local_settings["username"]} #{"-p#{local_settings["password"]}" if local_settings["password"]} #{local_settings["database"]} < #{local_sql_file_path}")
# now that we have the upated production dump file we should use the local settings to import this db.
end
desc "Mirrors the remote shared public directory with your local copy, doesn't download symlinks"
task :shared_assets do
run_locally "if [ -e public/uploads ]; then mv public/uploads public/uploads_back; fi"
# using rsync so that it only copies what it needs
run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress #{user}@#{app_server}:#{shared_path}/system/ public/system/")
run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress #{user}@#{app_server}:#{shared_path}/public/uploads/ public/uploads/")
end
namespace :remote do
desc "update the remote database with the local database"
task :database do
input = ''
# STDOUT.puts "Are you SURE to update the databse of remote?(YES)"
# confirmation = STDIN.gets.chomp
confirmation = Capistrano::CLI.ui.ask("Are you SURE to update the databse of remote?(YES)")
abort "Interrupt.." unless confirmation == "YES"
# config database.yml on both sides
remote_db_yml_path = "#{shared_path}/config/database.yml"
remote_db_yml_on_local_path = "tmp/database_#{rails_env}.yml"
# First get the local database config to remote
get remote_db_yml_path, remote_db_yml_on_local_path
# load the local settings within the database file
local_settings = YAML::load_file("config/database.yml")[rails_env]
# set the sql path on both sides
local_sql_file_path = "tmp/#{rails_env}-#{local_settings['database']}-dump.sql"
local_sql_gz_file_path = "#{local_sql_file_path}.gz"
remote_sql_file_path = "#{current_path}/tmp/#{rails_env}-#{local_settings['database']}-#{Time.now.strftime("%Y-%m-%d_%H:%M:%S")}.sql"
remote_sql_gz_file_path = "#{remote_sql_file_path}.gz"
# we also need the remote settings so that we can import the fresh dataabse properly
remote_settings = YAML::load_file(remote_db_yml_on_local_path)[rails_env]
# dump the local database and store it in the tmp dir
if local_settings['adapter'] == 'postgresql'
run_locally "PGPASSWORD='#{local_settings['password']}' pg_dump -U #{local_settings["username"]} #{"-h '#{local_settings["host"]}'" if local_settings["host"]} -c -O '#{local_settings["database"]}' > #{local_sql_file_path}"
elsif local_settings['adapter'] == 'mysql2'
run_locally "mysqldump -u'#{local_settings["username"]}' #{"-p#{local_settings["password"]}" if local_settings["password"]} #{"-h '#{local_settings["host"]}'" if local_settings["host"]} '#{local_settings["database"]}' > #{local_sql_file_path}"
else
raise "not supports #{local_settings['adapter']}"
end
# gzip db
run_locally "gzip -f #{local_sql_file_path}"
# send the gz file to remote
upload local_sql_gz_file_path, remote_sql_gz_file_path
# unzip sql
run "gunzip #{remote_sql_gz_file_path}"
# import db to remote db
# may need to run `RAILS_ENV=production rake db:create` on remote first
if local_settings['adapter'] == 'postgresql'
run "PGPASSWORD='#{remote_settings['password']}' psql -U #{remote_settings['username']} -d #{remote_settings["database"]} -f #{remote_sql_file_path}"
elsif local_settings['adapter'] == 'mysql2'
run "mysql -u#{remote_settings["username"]} #{"-p#{remote_settings["password"]}" if remote_settings["password"]} #{remote_settings["database"]} < #{remote_sql_file_path}"
else
raise "not supports #{local_settings['adapter']}"
end
# now that we have the updated production dump file we should use the remote settings to import this db
end
desc "Mirrors the local shared public directory with the remote copy, doesn't download symlinks"
task :shared_assets do
run "cp -R #{shared_path}/system #{shared_path}/system_back"
run "cp -R #{shared_path}/public/uploads/ #{shared_path}/public/uploads_back"
run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress public/system #{user}@#{app_server}:#{shared_path}/")
run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress public/uploads/ #{user}@#{app_server}:#{shared_path}/public/uploads")
end
end
end
|