dark-capistrano-recipes 0.6.4

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
@@ -0,0 +1,5 @@
1
+ require 'capistrano'
2
+ require 'capistrano/cli'
3
+ require 'helpers'
4
+
5
+ Dir.glob(File.join(File.dirname(__FILE__), '/recipes/*.rb')).sort.each { |f| load f }
data/lib/helpers.rb ADDED
@@ -0,0 +1,70 @@
1
+ # =========================================================================
2
+ # These are helper methods that will be available to your recipes.
3
+ # =========================================================================
4
+
5
+ # automatically sets the environment based on presence of
6
+ # :stage (multistage gem), :rails_env, or RAILS_ENV variable; otherwise defaults to 'production'
7
+ def environment
8
+ if exists?(:stage)
9
+ stage
10
+ elsif exists?(:rails_env)
11
+ rails_env
12
+ elsif(ENV['RAILS_ENV'])
13
+ ENV['RAILS_ENV']
14
+ else
15
+ "production"
16
+ end
17
+ end
18
+
19
+ def is_using_nginx
20
+ is_using('nginx',:web_server)
21
+ end
22
+
23
+ def is_using_unicorn
24
+ is_using('unicorn',:server)
25
+ end
26
+
27
+ def is_using_god
28
+ is_using('god',:monitorer)
29
+ end
30
+
31
+ def is_using(something, with_some_var)
32
+ exists?(with_some_var.to_sym) && fetch(with_some_var.to_sym).to_s.downcase == something
33
+ end
34
+
35
+ # Path to where the generators live
36
+ def templates_path
37
+ expanded_path_for('../generators')
38
+ end
39
+
40
+ def docs_path
41
+ expanded_path_for('../doc')
42
+ end
43
+
44
+ def expanded_path_for(path)
45
+ e = File.join(File.dirname(__FILE__),path)
46
+ File.expand_path(e)
47
+ end
48
+
49
+ def parse_config(file)
50
+ require 'erb' #render not available in Capistrano 2
51
+ template=File.read(file) # read it
52
+ return ERB.new(template).result(binding) # parse it
53
+ end
54
+
55
+ # Generates a configuration file parsing through ERB
56
+ # Fetches local file and uploads it to remote_file
57
+ # Make sure your user has the right permissions.
58
+ def generate_config(local_file,remote_file)
59
+ temp_file = '/tmp/' + File.basename(local_file)
60
+ buffer = parse_config(local_file)
61
+ File.open(temp_file, 'w+') { |f| f << buffer }
62
+ upload temp_file, remote_file, :via => :scp
63
+ `rm #{temp_file}`
64
+ end
65
+
66
+ # Execute a rake task, example:
67
+ # run_rake log:clear
68
+ def run_rake(task)
69
+ run "cd #{current_path} && rake #{task} RAILS_ENV=#{environment}"
70
+ end
@@ -0,0 +1,68 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+
3
+ # User settings
4
+ set :user, 'deploy' unless exists?(:user)
5
+ set :group,'www-data' unless exists?(:group)
6
+
7
+
8
+ # Server settings
9
+ set :server, :unicorn unless exists?(:server)
10
+ set :web_server, :nginx unless exists?(:web_server)
11
+ set :runner, user unless exists?(:runner)
12
+
13
+ # The port to listen to, normally we default to 80
14
+ set :application_port, 80 unless exists?(:application_port)
15
+
16
+ # Are we using ssl as well? Used by generators to configure a ssl host
17
+ set :application_uses_ssl, true unless exists?(:application_uses_ssl)
18
+
19
+ # The port to listen to https requests, usually 443
20
+ set :application_port_ssl, 443 unless exists?(:application_port_ssl)
21
+
22
+ # Database settings
23
+ set :database, :mysql unless exists?(:database)
24
+
25
+ # SCM settings
26
+ set :scm, :git
27
+ set :branch, 'master' unless exists?(:branch)
28
+ set :deploy_to, "/var/www/apps/#{application}" unless exists?(:deploy_to)
29
+ set :deploy_via, :remote_cache
30
+ set :keep_releases, 3
31
+ set :git_enable_submodules, true
32
+ set :rails_env, 'production' unless exists?(:rails_env)
33
+ set :use_sudo, false
34
+
35
+ # Git settings for capistrano
36
+ default_run_options[:pty] = true
37
+ ssh_options[:forward_agent] = true
38
+
39
+ # RVM settings
40
+ set :using_rvm, true unless exists?(:using_rvm)
41
+ # Sets the rvm to a specific version (or whatever env you want it to run in)
42
+ set :rvm_ruby_string, 'ree' unless exists?(:rvm_ruby_string)
43
+
44
+ # Daemons settings
45
+ # The unix socket that unicorn will be attached to.
46
+ # Also, nginx will upstream to this guy.
47
+ # The *nix place for socks is /var/run, so we should probably put it there
48
+ # Make sure the runner can access this though.
49
+ set :sockets_path, "/var/run/#{application}" unless exists?(:sockets_path)
50
+
51
+ # Just to be safe, put the pid somewhere that survives deploys. shared/pids is
52
+ # a good choice as any.
53
+ set(:pids_path) { File.join(shared_path, "pids") } unless exists?(:pids_path)
54
+
55
+ set :monitorer, 'god' unless exists?(:monitorer)
56
+
57
+ # Application settings
58
+ set :shared_dirs, %w( config uploads backup bundle tmp ) unless exists?(:shared_dirs)
59
+
60
+ namespace :app do
61
+ task :setup, :roles => :app do
62
+ commands = shared_dirs.map do |path|
63
+ "if [ ! -d '#{path}' ]; then mkdir -p #{path}; fi;"
64
+ end
65
+ run "cd #{shared_path}; #{commands.join(' ')}"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,19 @@
1
+ Bundler
2
+ # Manages gems in a sane way.
3
+ # http://gembundler.com/
4
+ Capistrano::Configuration.instance(:must_exist).load do
5
+ namespace :bundler do
6
+ set :bundler_ver, '1.0.0'
7
+ desc "Installs bundler gem to your server"
8
+ task :setup, :roles => :app do
9
+ run "if ! gem list | grep --silent -e 'bundler.*#{bundler_ver}'; then #{try_sudo} gem uninstall bundler; #{try_sudo} gem install --no-rdoc --no-ri bundler; fi"
10
+ end
11
+
12
+ # [internal] runs bundle install on the app server
13
+ task :install, :roles => :app, :except => { :no_release => true } do
14
+ run "cd #{current_path} && bundle install --deployment --without=development test"
15
+ end
16
+ end
17
+
18
+ end
19
+
data/lib/recipes/db.rb ADDED
@@ -0,0 +1,105 @@
1
+ require 'erb'
2
+
3
+ Capistrano::Configuration.instance(:must_exist).load do
4
+ namespace :db do
5
+ namespace :mysql do
6
+
7
+ # Sets database variables from remote database.yaml
8
+ def prepare_from_yaml
9
+ set(:db_file) { "#{application}-dump.sql.bz2" }
10
+ set(:db_remote_file) { "#{shared_path}/backup/#{db_file}" }
11
+ set(:db_local_file) { "tmp/#{db_file}" }
12
+ set(:db_user) { db_config[rails_env]["username"] }
13
+ set(:db_pass) { db_config[rails_env]["password"] }
14
+ set(:db_host) { db_config[rails_env]["host"] }
15
+ set(:db_name) { db_config[rails_env]["database"] }
16
+ end
17
+
18
+ def db_config
19
+ @db_config ||= fetch_db_config
20
+ end
21
+
22
+ def fetch_db_config
23
+ require 'yaml'
24
+ file = capture "cat #{shared_path}/config/database.yml"
25
+ db_config = YAML.load(file)
26
+ end
27
+
28
+ desc <<-EOF
29
+ Performs a compressed database dump. \
30
+ WARNING: This locks your tables for the duration of the mysqldump.
31
+ Don't run it madly!
32
+ EOF
33
+ task :dump, :roles => :db, :only => { :primary => true } do
34
+ prepare_from_yaml
35
+ run "mysqldump --user=#{db_user} -p --host=#{db_host} #{db_name} | bzip2 -z9 > #{db_remote_file}" do |ch, stream, out|
36
+ ch.send_data "#{db_pass}\n" if out =~ /^Enter password:/
37
+ puts out
38
+ end
39
+ end
40
+
41
+ desc "Restores the database from the latest compressed dump"
42
+ task :restore, :roles => :db, :only => { :primary => true } do
43
+ prepare_from_yaml
44
+ run "bzcat #{db_remote_file} | mysql --user=#{db_user} -p --host=#{db_host} #{db_name}" do |ch, stream, out|
45
+ ch.send_data "#{db_pass}\n" if out =~ /^Enter password:/
46
+ puts out
47
+ end
48
+ end
49
+
50
+ desc "Downloads the compressed database dump to this machine"
51
+ task :fetch_dump, :roles => :db, :only => { :primary => true } do
52
+ prepare_from_yaml
53
+ download db_remote_file, db_local_file, :via => :scp
54
+ end
55
+
56
+
57
+ desc "Create MySQL database and user for this environment using prompted values"
58
+ task :setup, :roles => :db, :only => { :primary => true } do
59
+ prepare_for_db_command
60
+
61
+ sql = <<-SQL
62
+ CREATE DATABASE #{db_name};
63
+ GRANT ALL PRIVILEGES ON #{db_name}.* TO #{db_user}@localhost IDENTIFIED BY '#{db_pass}';
64
+ SQL
65
+
66
+ run "mysql --user=#{db_admin_user} -p --execute=\"#{sql}\"" do |channel, stream, data|
67
+ if data =~ /^Enter password:/
68
+ pass = Capistrano::CLI.password_prompt "Enter database password for '#{db_admin_user}':"
69
+ channel.send_data "#{pass}\n"
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ desc "Create database.yml in shared path with settings for current stage and test env"
76
+ task :create_yaml do
77
+ set(:db_user) { Capistrano::CLI.ui.ask "Enter #{environment} database username:" }
78
+ set(:db_pass) { Capistrano::CLI.password_prompt "Enter #{environment} database password:" }
79
+
80
+ db_config = ERB.new <<-EOF
81
+ base: &base
82
+ adapter: mysql
83
+ username: #{db_user}
84
+ password: #{db_pass}
85
+
86
+ #{environment}:
87
+ database: #{application}_#{environment}
88
+ <<: *base
89
+
90
+ test:
91
+ database: #{application}_test
92
+ <<: *base
93
+ EOF
94
+
95
+ put db_config.result, "#{shared_path}/config/database.yml"
96
+ end
97
+ end
98
+
99
+ def prepare_for_db_command
100
+ set :db_name, "#{application}_#{environment}"
101
+ set(:db_admin_user) { Capistrano::CLI.ui.ask "Username with priviledged database access (to create db):" }
102
+ set(:db_user) { Capistrano::CLI.ui.ask "Enter #{environment} database username:" }
103
+ set(:db_pass) { Capistrano::CLI.password_prompt "Enter #{environment} database password:" }
104
+ end
105
+ end
@@ -0,0 +1,105 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ set :shared_children, %w(system log pids config)
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
+
9
+ namespace :deploy do
10
+ desc "Deploy it, github-style."
11
+ task :default do
12
+ update
13
+ restart
14
+ end
15
+
16
+ desc "[Seppuku] Destroy everything"
17
+ task :seppuku do
18
+ run "rm -rf #{current_path}; rm -rf #{shared_path}"
19
+ rubycas.seppuku
20
+ end
21
+
22
+ task :setup_dirs, :except => { :no_release => true } do
23
+ commands = shared_dirs.map do |path|
24
+ "mkdir -p #{shared_path}/#{path}"
25
+ end
26
+ run commands.join(" && ")
27
+ end
28
+
29
+ desc "Uploads your local config.yml to the server"
30
+ task :configure, :except => { :no_release => true } do
31
+ generate_config('config/config.yml', "#{shared_path}/config/config.yml")
32
+ end
33
+
34
+ desc "Setup a GitHub-style deployment."
35
+ task :setup, :except => { :no_release => true } do
36
+ run "rm -rf #{current_path}"
37
+ setup_dirs
38
+ run "git clone #{repository} #{current_path}"
39
+ end
40
+
41
+ desc "Update the deployed code."
42
+ task :update_code, :except => { :no_release => true } do
43
+ run "cd #{current_path}; git fetch origin; git reset --hard #{branch}"
44
+ end
45
+
46
+ task :symlink, :except => { :no_release => true } do
47
+ symlinks.make
48
+ end
49
+
50
+ desc "[Obsolete] Nothing to cleanup when using reset --hard on git"
51
+ task :cleanup, :except => { :no_release => true } do
52
+ #nothing to cleanup, we're not working with 'releases'
53
+ puts "Nothing to cleanup, yay!"
54
+ end
55
+
56
+ namespace :rollback do
57
+ desc "Rollback a single commit."
58
+ task :default, :except => { :no_release => true } do
59
+ set :branch, "HEAD^"
60
+ deploy.default
61
+ end
62
+ end
63
+
64
+ desc <<-DESC
65
+ Restarts your application. This depends heavily on what server you're running.
66
+ If you are running Phusion Passenger, you can explicitly set the server type:
67
+
68
+ set :server, :passenger
69
+
70
+ ...which will touch tmp/restart.txt, a file monitored by Passenger.
71
+
72
+ If you are running Unicorn, you can set:
73
+
74
+ set :server, :unicorn
75
+
76
+ ...which will use unicorn signals for restarting its workers.
77
+
78
+ Otherwise, this command will call the script/process/reaper \
79
+ script under the current path.
80
+
81
+ If you are running with Unicorn, you can set the server type as well:
82
+
83
+ set :server, :unicorn
84
+
85
+ By default, this will be invoked via sudo as the `app' user. If \
86
+ you wish to run it as a different user, set the :runner variable to \
87
+ that user. If you are in an environment where you can't use sudo, set \
88
+ the :use_sudo variable to false:
89
+
90
+ set :use_sudo, false
91
+ DESC
92
+ task :restart, :roles => :app, :except => { :no_release => true } do
93
+ if exists?(:server)
94
+ case fetch(:server).to_s.downcase
95
+ when 'passenger'
96
+ passenger.bounce
97
+ when 'unicorn'
98
+ is_using_god ? god.restart_unicorn : unicorn.restart
99
+ end
100
+ else
101
+ puts "Dunno how to restart your internets! kthx!"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,114 @@
1
+ # God Monitoring Tool
2
+ # God is a Process Monitoring Framework written in Ruby
3
+ # More info at http://god.rubyforge.org/
4
+ #------------------------------------------------------------------------------
5
+ Capistrano::Configuration.instance(:must_exist).load do
6
+ _cset(:god_local_config) { "#{templates_path}/app.god.erb" }
7
+ _cset(:god_remote_config) { "#{shared_path}/config/app.god" }
8
+
9
+ namespace :god do
10
+
11
+ desc "Parses and uploads god configuration for this app"
12
+ task :setup do
13
+ generate_config(god_local_config, god_remote_config)
14
+ end
15
+
16
+ _cset(:bin_god) { defined?(:rvm_ruby_string) ? 'bootup_god' : 'god' }
17
+ _cset(:server_name) { "#{application}-unicorn" }
18
+ _cset(:god_init_local) { "#{docs_path}/god/god.init" }
19
+ _cset :god_init_temp, '/tmp/god.init'
20
+ _cset :god_init_remote, '/etc/init.d/god'
21
+ _cset(:god_defo_local) { "#{docs_path}/god/god"}
22
+ _cset :god_defo_temp, '/tmp/god'
23
+ _cset :god_defo_remote, '/etc/default/god'
24
+ _cset(:god_conf_local) { "#{docs_path}/god/god.conf" }
25
+ _cset :god_conf_temp, '/tmp/god.conf'
26
+ _cset :god_conf_remote, '/etc/god/god.conf'
27
+
28
+
29
+ task :setup_temp, :roles => :app do
30
+ sudo "rm -f #{god_conf_remote} #{god_init_remote} #{god_defo_remote}"
31
+ end
32
+
33
+ task :setup_conf, :roles => :app do
34
+ upload god_conf_local, god_conf_temp, :via => :scp
35
+ sudo "mkdir -p #{File.dirname(god_conf_remote)}"
36
+ sudo "mv #{god_conf_temp} #{god_conf_remote}"
37
+ end
38
+
39
+ task :setup_init, :roles => :app do
40
+ upload god_init_local, god_init_temp, :via => :scp
41
+ sudo "mv #{god_init_temp} #{god_init_remote}"
42
+ # Allow executing the init.d script
43
+ sudo "chmod +x #{god_init_remote}"
44
+ # Make it run at bootup
45
+ sudo "update-rc.d god defaults"
46
+ end
47
+
48
+ task :setup_defo, :roles => :app do
49
+ upload god_defo_local, god_defo_temp, :via => :scp
50
+ sudo "mv #{god_defo_temp} #{god_defo_remote}"
51
+ end
52
+
53
+ desc "Bootstraps god on your server. Be careful with this."
54
+ task :bootstrap, :roles => :app do
55
+ setup_temp
56
+ setup_defo
57
+ setup_init
58
+ setup_conf
59
+
60
+ puts "God is bootstrapped. To remove use 'cap god:implode'"
61
+ end
62
+
63
+ desc "(Seppuku) Completely remove god from the system init"
64
+ task :implode, :roles => :app do
65
+ # Removing any system startup links for /etc/init.d/god ...
66
+ sudo "update-rc.d -f god remove"
67
+
68
+ # Suicide follows.
69
+ sudo "rm -f #{god_conf_remote}"
70
+ sudo "rm -f #{god_defo_remote}"
71
+ sudo "rm -f #{god_init_remote}"
72
+ puts "God is no more."
73
+ end
74
+
75
+ task :restart_unicorn, :roles => :app do
76
+ sudo "#{bin_god} restart #{server_name}"
77
+ end
78
+
79
+ task :log, :roles => :app do
80
+ sudo "#{bin_god} log #{application}"
81
+ end
82
+
83
+ desc "Reload config"
84
+ task :reload, :roles => :app do
85
+ sudo "#{bin_god} load #{god_remote_config}"
86
+ end
87
+
88
+ desc "Start god service"
89
+ task :start, :roles => :app do
90
+ sudo "service god start"
91
+ end
92
+
93
+ desc "Stops god service"
94
+ task :stop, :roles => :app do
95
+ sudo "service god stop"
96
+ end
97
+
98
+ desc "Quit god, but not the processes it's monitoring"
99
+ task :quit, :roles => :app do
100
+ sudo "#{bin_god} quit"
101
+ end
102
+
103
+ desc "Terminate god and all monitored processes"
104
+ task :terminate, :roles => :app do
105
+ sudo "#{bin_god} terminate"
106
+ end
107
+
108
+ desc "Describe the status of the running tasks"
109
+ task :status, :roles => :app do
110
+ sudo "#{bin_god} status"
111
+ end
112
+ end
113
+ after 'deploy:setup', 'god:setup' if is_using_god
114
+ end