capistrano-recipes 0.5.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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