capistrano-recipes 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ rails_root = '<%= "#{deploy_to}/current" %>'
2
+ rails_env = '<%= environment %>'
3
+ pid_file = '<%= unicorn_pid %>'
4
+ socket_file= '<%= unicorn_socket %>'
5
+ log_file = "#{rails_root}/log/unicorn.log"
6
+ username = '<%= unicorn_user %>'
7
+ group = '<%= unicorn_group %>'
8
+ old_pid = pid_file + '.oldbin'
9
+
10
+
11
+ timeout <%= unicorn_workers_timeout %>
12
+
13
+ worker_processes <%= unicorn_workers %>
14
+
15
+ # Listen on a Unix data socket
16
+ listen socket_file, :backlog => 1024
17
+ pid pid_file
18
+
19
+ stderr_path log_file
20
+ stdout_path log_file
21
+
22
+ preload_app true
23
+ ##
24
+ # REE
25
+
26
+ GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
27
+
28
+ before_fork do |server, worker|
29
+ # the following is highly recomended for Rails + "preload_app true"
30
+ # as there's no need for the master process to hold a connection
31
+ defined?(ActiveRecord::Base) and
32
+ ActiveRecord::Base.connection.disconnect!
33
+
34
+
35
+ ##
36
+ # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
37
+ # immediately start loading up a new version of itself (loaded with a new
38
+ # version of our app). When this new Unicorn is completely loaded
39
+ # it will begin spawning workers. The first worker spawned will check to
40
+ # see if an .oldbin pidfile exists. If so, this means we've just booted up
41
+ # a new Unicorn and need to tell the old one that it can now die. To do so
42
+ # we send it a QUIT.
43
+ #
44
+ # Using this method we get 0 downtime deploys.
45
+
46
+ if File.exists?(old_pid) && server.pid != old_pid
47
+ begin
48
+ Process.kill("QUIT", File.read(old_pid).to_i)
49
+ rescue Errno::ENOENT, Errno::ESRCH
50
+ # someone else did our job for us
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ after_fork do |server, worker|
57
+ defined?(ActiveRecord::Base) and
58
+ ActiveRecord::Base.establish_connection
59
+
60
+
61
+ worker.user(username, group) if Process.euid == 0 && rails_env == 'production'
62
+ end
@@ -2,4 +2,4 @@ require 'capistrano'
2
2
  require 'capistrano/cli'
3
3
  require 'helpers'
4
4
 
5
- Dir.glob(File.join(File.dirname(__FILE__), '/recipes/*.rb')).each { |f| load f }
5
+ Dir.glob(File.join(File.dirname(__FILE__), '/recipes/*.rb')).sort.each { |f| load f }
@@ -16,8 +16,68 @@ def environment
16
16
  end
17
17
  end
18
18
 
19
- # Execute a rake task, example:
20
- # run_rake log:clear
19
+ def is_using_nginx
20
+ is_using('nginx',:web_server)
21
+ end
22
+
23
+ def is_using_passenger
24
+ is_using('passenger',:app_server)
25
+ end
26
+
27
+ def is_using_unicorn
28
+ is_using('unicorn',:app_server)
29
+ end
30
+
31
+ def is_app_monitored?
32
+ is_using('bluepill', :monitorer) || is_using('god', :monitorer)
33
+ end
34
+
35
+ def is_using(something, with_some_var)
36
+ exists?(with_some_var.to_sym) && fetch(with_some_var.to_sym).to_s.downcase == something
37
+ end
38
+
39
+ # Path to where the generators live
40
+ def templates_path
41
+ expanded_path_for('../generators')
42
+ end
43
+
44
+ def docs_path
45
+ expanded_path_for('../doc')
46
+ end
47
+
48
+ def expanded_path_for(path)
49
+ e = File.join(File.dirname(__FILE__),path)
50
+ File.expand_path(e)
51
+ end
52
+
53
+ def parse_config(file)
54
+ require 'erb' #render not available in Capistrano 2
55
+ template=File.read(file) # read it
56
+ return ERB.new(template).result(binding) # parse it
57
+ end
58
+
59
+ # =========================================================================
60
+ # Prompts the user for a message to agree/decline
61
+ # =========================================================================
62
+ def ask(message, default=true)
63
+ Capistrano::CLI.ui.agree(message)
64
+ end
65
+
66
+ # Generates a configuration file parsing through ERB
67
+ # Fetches local file and uploads it to remote_file
68
+ # Make sure your user has the right permissions.
69
+ def generate_config(local_file,remote_file)
70
+ temp_file = '/tmp/' + File.basename(local_file)
71
+ buffer = parse_config(local_file)
72
+ File.open(temp_file, 'w+') { |f| f << buffer }
73
+ upload temp_file, remote_file, :via => :scp
74
+ `rm #{temp_file}`
75
+ end
76
+
77
+ # =========================================================================
78
+ # Executes a basic rake task.
79
+ # Example: run_rake log:clear
80
+ # =========================================================================
21
81
  def run_rake(task)
22
82
  run "cd #{current_path} && rake #{task} RAILS_ENV=#{environment}"
23
83
  end
@@ -0,0 +1,67 @@
1
+ Capistrano::Configuration.instance.load do
2
+ # User settings
3
+ set :user, 'deploy' unless exists?(:user)
4
+ set :group,'www-data' unless exists?(:group)
5
+
6
+ # Server settings
7
+ set :app_server, :unicorn unless exists?(:app_server)
8
+ set :web_server, :nginx unless exists?(:web_server)
9
+ set :runner, user unless exists?(:runner)
10
+ set :application_port, 80 unless exists?(:application_port)
11
+
12
+ set :application_uses_ssl, true unless exists?(:application_uses_ssl)
13
+ set :application_port_ssl, 443 unless exists?(:application_port_ssl)
14
+
15
+ # Database settings
16
+ set :database, :mysql unless exists?(:database)
17
+
18
+ # SCM settings
19
+ set :scm, :git
20
+ set :branch, 'master' unless exists?(:branch)
21
+ set :deploy_to, "/var/www/apps/#{application}" unless exists?(:deploy_to)
22
+ set :deploy_via, :remote_cache
23
+ set :keep_releases, 3
24
+ set :git_enable_submodules, true
25
+ set :rails_env, 'production' unless exists?(:rails_env)
26
+ set :use_sudo, false
27
+
28
+ # Git settings for capistrano
29
+ default_run_options[:pty] = true
30
+ ssh_options[:forward_agent] = true
31
+
32
+ # RVM settings
33
+ set :using_rvm, true unless exists?(:using_rvm)
34
+
35
+ if using_rvm
36
+ $:.unshift(File.expand_path('./lib', ENV['rvm_path'])) # Add RVM's lib directory to the load path.
37
+ require "rvm/capistrano" # Load RVM's capistrano plugin.
38
+
39
+ # Sets the rvm to a specific version (or whatever env you want it to run in)
40
+ set :rvm_ruby_string, 'ree' unless exists?(:rvm_ruby_string)
41
+ end
42
+
43
+ # Daemons settings
44
+ # The unix socket that unicorn will be attached to.
45
+ # Also, nginx will upstream to this guy.
46
+ # The *nix place for socks is /var/run, so we should probably put it there
47
+ # Make sure the runner can access this though.
48
+ set :sockets_path, "/var/run/#{application}" unless exists?(:sockets_path)
49
+
50
+ # Just to be safe, put the pid somewhere that survives deploys. shared/pids is
51
+ # a good choice as any.
52
+ set(:pids_path) { File.join(shared_path, "pids") } unless exists?(:pids_path)
53
+
54
+ set :monitorer, 'bluepill' unless exists?(:monitorer)
55
+
56
+ # Application settings
57
+ set :shared_dirs, %w(config uploads backup bundle tmp) unless exists?(:shared_dirs)
58
+
59
+ namespace :app do
60
+ task :setup, :roles => :app do
61
+ commands = shared_dirs.map do |path|
62
+ "if [ ! -d '#{path}' ]; then mkdir -p #{path}; fi;"
63
+ end
64
+ run "cd #{shared_path}; #{commands.join(' ')}"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ Capistrano::Configuration.instance.load do
2
+ namespace :bluepill do
3
+ desc "|DarkRecipes| Install the bluepill monitoring tool"
4
+ task :install, :roles => [:app] do
5
+ sudo "gem install bluepill"
6
+ end
7
+
8
+ desc "|DarkRecipes| Stop processes that bluepill is monitoring and quit bluepill"
9
+ task :quit, :roles => [:app] do
10
+ args = options || ""
11
+ begin
12
+ sudo "bluepill stop #{args}"
13
+ rescue
14
+ puts "Bluepill was unable to finish gracefully all the process"
15
+ ensure
16
+ sudo "bluepill quit"
17
+ end
18
+ end
19
+
20
+ desc "|DarkRecipes| Load the pill from {your-app}/config/pills/{app-name}.pill"
21
+ task :init, :roles =>[:app] do
22
+ sudo "bluepill load #{current_path}/config/pills/#{application}.pill"
23
+ end
24
+
25
+ desc "|DarkRecipes| Starts your previous stopped pill"
26
+ task :start, :roles =>[:app] do
27
+ args = options || ""
28
+ sudo "bluepill start #{args}"
29
+ end
30
+
31
+ desc "|DarkRecipes| Stops some bluepill monitored process"
32
+ task :stop, :roles =>[:app] do
33
+ args = options || ""
34
+ sudo "bluepill stop #{args}"
35
+ end
36
+
37
+ desc "|DarkRecipes| Restarts the pill from {your-app}/config/pills/{app-name}.pill"
38
+ task :restart, :roles =>[:app] do
39
+ args = options || ""
40
+ sudo "bluepill restart #{args}"
41
+ end
42
+
43
+ desc "|DarkRecipes| Prints bluepills monitored processes statuses"
44
+ task :status, :roles => [:app] do
45
+ args = options || ""
46
+ sudo "bluepill status #{args}"
47
+ end
48
+ end
49
+
50
+ after 'deploy:setup' do
51
+ bluepill.install if Capistrano::CLI.ui.agree("Do you want to install the bluepill monitor? [Yn]")
52
+ end if is_using('bluepill', :monitorer)
53
+ end
@@ -0,0 +1,14 @@
1
+ Capistrano::Configuration.instance.load do
2
+ namespace :bundler do
3
+ desc "|DarkRecipes| Installs bundler gem to your server"
4
+ task :setup, :roles => :app do
5
+ run "if ! gem list | grep --silent -e 'bundler'; then #{try_sudo} gem uninstall bundler; #{try_sudo} gem install --no-rdoc --no-ri bundler; fi"
6
+ end
7
+
8
+ desc "|DarkRecipes| Runs bundle install on the app server (internal task)"
9
+ task :install, :roles => :app, :except => { :no_release => true } do
10
+ run "cd #{current_path} && bundle install --deployment --without=development test"
11
+ end
12
+ end
13
+ end
14
+
@@ -1,9 +1,37 @@
1
1
  require 'erb'
2
2
 
3
- Capistrano::Configuration.instance(:must_exist).load do
3
+ Capistrano::Configuration.instance.load do
4
4
  namespace :db do
5
5
  namespace :mysql do
6
- desc "Create MySQL database and user for this environment using prompted values"
6
+ desc <<-EOF
7
+ |DarkRecipes| Performs a compressed database dump. \
8
+ WARNING: This locks your tables for the duration of the mysqldump.
9
+ Don't run it madly!
10
+ EOF
11
+ task :dump, :roles => :db, :only => { :primary => true } do
12
+ prepare_from_yaml
13
+ run "mysqldump --user=#{db_user} -p --host=#{db_host} #{db_name} | bzip2 -z9 > #{db_remote_file}" do |ch, stream, out|
14
+ ch.send_data "#{db_pass}\n" if out =~ /^Enter password:/
15
+ puts out
16
+ end
17
+ end
18
+
19
+ desc "|DarkRecipes| Restores the database from the latest compressed dump"
20
+ task :restore, :roles => :db, :only => { :primary => true } do
21
+ prepare_from_yaml
22
+ run "bzcat #{db_remote_file} | mysql --user=#{db_user} -p --host=#{db_host} #{db_name}" do |ch, stream, out|
23
+ ch.send_data "#{db_pass}\n" if out =~ /^Enter password:/
24
+ puts out
25
+ end
26
+ end
27
+
28
+ desc "|DarkRecipes| Downloads the compressed database dump to this machine"
29
+ task :fetch_dump, :roles => :db, :only => { :primary => true } do
30
+ prepare_from_yaml
31
+ download db_remote_file, db_local_file, :via => :scp
32
+ end
33
+
34
+ desc "|DarkRecipes| Create MySQL database and user for this environment using prompted values"
7
35
  task :setup, :roles => :db, :only => { :primary => true } do
8
36
  prepare_for_db_command
9
37
 
@@ -18,10 +46,31 @@ Capistrano::Configuration.instance(:must_exist).load do
18
46
  channel.send_data "#{pass}\n"
19
47
  end
20
48
  end
21
- end
49
+ end
50
+
51
+ # Sets database variables from remote database.yaml
52
+ def prepare_from_yaml
53
+ set(:db_file) { "#{application}-dump.sql.bz2" }
54
+ set(:db_remote_file) { "#{shared_path}/backup/#{db_file}" }
55
+ set(:db_local_file) { "tmp/#{db_file}" }
56
+ set(:db_user) { db_config[rails_env]["username"] }
57
+ set(:db_pass) { db_config[rails_env]["password"] }
58
+ set(:db_host) { db_config[rails_env]["host"] }
59
+ set(:db_name) { db_config[rails_env]["database"] }
60
+ end
61
+
62
+ def db_config
63
+ @db_config ||= fetch_db_config
64
+ end
65
+
66
+ def fetch_db_config
67
+ require 'yaml'
68
+ file = capture "cat #{shared_path}/config/database.yml"
69
+ db_config = YAML.load(file)
70
+ end
22
71
  end
23
72
 
24
- desc "Create database.yml in shared path with settings for current stage and test env"
73
+ desc "|DarkRecipes| Create database.yml in shared path with settings for current stage and test env"
25
74
  task :create_yaml do
26
75
  set(:db_user) { Capistrano::CLI.ui.ask "Enter #{environment} database username:" }
27
76
  set(:db_pass) { Capistrano::CLI.password_prompt "Enter #{environment} database password:" }
@@ -29,6 +78,7 @@ Capistrano::Configuration.instance(:must_exist).load do
29
78
  db_config = ERB.new <<-EOF
30
79
  base: &base
31
80
  adapter: mysql
81
+ encoding: utf8
32
82
  username: #{db_user}
33
83
  password: #{db_pass}
34
84
 
@@ -51,4 +101,14 @@ Capistrano::Configuration.instance(:must_exist).load do
51
101
  set(:db_user) { Capistrano::CLI.ui.ask "Enter #{environment} database username:" }
52
102
  set(:db_pass) { Capistrano::CLI.password_prompt "Enter #{environment} database password:" }
53
103
  end
54
- end
104
+
105
+ desc "Populates the database with seed data"
106
+ task :seed do
107
+ Capistrano::CLI.ui.say "Populating the database..."
108
+ run "cd #{current_path}; rake RAILS_ENV=#{variables[:rails_env]} db:seed"
109
+ end
110
+
111
+ after "deploy:setup" do
112
+ db.create_yaml if Capistrano::CLI.ui.agree("Create database.yml in app's shared path? [Yn]")
113
+ end
114
+ end
@@ -1,36 +1,106 @@
1
- Capistrano::Configuration.instance(:must_exist).load do
1
+ Capistrano::Configuration.instance.load do
2
2
  set :shared_children, %w(system log pids config)
3
3
 
4
- after "deploy:setup" do
5
- db.create_yaml if Capistrano::CLI.ui.agree("Create database.yml in app's shared path?")
6
- end
7
-
8
- after "deploy:update_code", "symlink:shared_config_files"
9
-
10
4
  namespace :deploy do
5
+ desc "|DarkRecipes| Deploy it, github-style."
6
+ task :default, :roles => :app, :except => { :no_release => true } do
7
+ update
8
+ restart
9
+ end
10
+
11
+ desc "|DarkRecipes| Destroys everything"
12
+ task :seppuku, :roles => :app, :except => { :no_release => true } do
13
+ run "rm -rf #{current_path}; rm -rf #{shared_path}"
14
+ end
15
+
16
+ desc "|DarkRecipes| Create shared dirs"
17
+ task :setup_dirs, :roles => :app, :except => { :no_release => true } do
18
+ commands = shared_dirs.map do |path|
19
+ "mkdir -p #{shared_path}/#{path}"
20
+ end
21
+ run commands.join(" && ")
22
+ end
23
+
24
+ desc "|DarkRecipes| Uploads your local config.yml to the server"
25
+ task :configure, :roles => :app, :except => { :no_release => true } do
26
+ generate_config('config/config.yml', "#{shared_path}/config/config.yml")
27
+ end
28
+
29
+ desc "|DarkRecipes| Setup a GitHub-style deployment."
30
+ task :setup, :roles => :app, :except => { :no_release => true } do
31
+ run "rm -rf #{current_path}"
32
+ setup_dirs
33
+ run "git clone #{repository} #{current_path}"
34
+ end
35
+
36
+ desc "|DarkRecipes| Update the deployed code."
37
+ task :update_code, :roles => :app, :except => { :no_release => true } do
38
+ run "cd #{current_path}; git fetch origin; git reset --hard #{branch}"
39
+ end
40
+
41
+ desc "|DarkRecipes| Alias for symlinks:make"
42
+ task :symlink, :roles => :app, :except => { :no_release => true } do
43
+ symlinks.make
44
+ end
45
+
46
+ desc "|DarkRecipes| Remote run for rake db:migrate"
47
+ task :migrate, :roles => :app, :except => { :no_release => true } do
48
+ run "cd #{current_path}; bundle exec rake RAILS_ENV=#{rails_env} db:migrate"
49
+ end
50
+
51
+ desc "|DarkRecipes| [Obsolete] Nothing to cleanup when using reset --hard on git"
52
+ task :cleanup, :roles => :app, :except => { :no_release => true } do
53
+ #nothing to cleanup, we're not working with 'releases'
54
+ puts "Nothing to cleanup, yay!"
55
+ end
56
+
57
+ namespace :rollback do
58
+ desc "|DarkRecipes| Rollback , :except => { :no_release => true }a single commit."
59
+ task :default, :roles => :app, :except => { :no_release => true } do
60
+ set :branch, "HEAD^"
61
+ deploy.default
62
+ end
63
+ end
64
+
11
65
  desc <<-DESC
12
- Restarts your application. If you are running Phusion Passenger, you can \
13
- explicitly set the server type:
66
+ |DarkRecipes| Restarts your application. This depends heavily on what server you're running.
67
+ If you are running Phusion Passenger, you can explicitly set the server type:
14
68
 
15
69
  set :server, :passenger
16
-
70
+
17
71
  ...which will touch tmp/restart.txt, a file monitored by Passenger.
72
+
73
+ If you are running Unicorn, you can set:
74
+
75
+ set :server, :unicorn
76
+
77
+ ...which will use unicorn signals for restarting its workers.
78
+
18
79
  Otherwise, this command will call the script/process/reaper \
19
80
  script under the current path.
81
+
82
+ If you are running with Unicorn, you can set the server type as well:
83
+
84
+ set :server, :unicorn
20
85
 
21
- By default, this will be invoked via sudo as the `app' user. If \
86
+ By default, this will be |DarkRecipes| d via sudo as the `app' user. If \
22
87
  you wish to run it as a different user, set the :runner variable to \
23
88
  that user. If you are in an environment where you can't use sudo, set \
24
89
  the :use_sudo variable to false:
25
90
 
26
- set :use_sudo, false
91
+ set :use_sudo, false
27
92
  DESC
28
93
  task :restart, :roles => :app, :except => { :no_release => true } do
29
- if exists?(:server) && fetch(:server).to_s.downcase == 'passenger'
30
- passenger.bounce
94
+ if exists?(:app_server)
95
+ case fetch(:app_server).to_s.downcase
96
+ when 'passenger'
97
+ passenger.bounce
98
+ when 'unicorn'
99
+ is_using('god', :monitorer) ? god.restart.app : unicorn.restart
100
+ end
31
101
  else
32
- try_runner "#{current_path}/script/process/reaper"
102
+ puts "Dunno how to restart your internets! kthx!"
33
103
  end
34
104
  end
35
105
  end
36
- end
106
+ end