├── .gitignore ├── CONTRIBUTORS.md ├── Capfile ├── Gemfile ├── LICENSE.md ├── README.md ├── config ├── deploy.rb └── deploy │ ├── production.rb │ ├── shared │ ├── application.yml.template.erb │ ├── database.yml.template.erb │ ├── log_rotation.erb │ ├── nginx.conf.erb │ ├── puma.rb.erb │ ├── puma_init.sh.erb │ └── secrets.yml.template.erb │ └── staging.rb └── lib └── capistrano ├── substitute_strings.rb ├── tasks ├── check_revision.cap ├── compile_assets_locally.cap ├── db.cap ├── logs.cap ├── monit.cap ├── nginx.cap ├── puma.cap ├── push_deploy_tag.cap ├── run_tests.cap └── setup_config.cap └── template.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | * **Tanvir Hasan** - *nginx configuration update* - [tanvir002700](https://github.com/tanvir002700) 2 | 3 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | require 'capistrano/setup' 2 | require 'capistrano/deploy' 3 | 4 | require 'capistrano/rbenv' 5 | require 'capistrano/bundler' 6 | require 'capistrano/rails/assets' 7 | require 'capistrano/rails/migrations' 8 | require 'capistrano/rails/console' 9 | 10 | require 'capistrano/scm/git' 11 | install_plugin Capistrano::SCM::Git 12 | 13 | Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r } 14 | Dir.glob('lib/capistrano/**/*.rb').each { |r| import r } 15 | 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | gem 'puma' 2 | group :development do 3 | gem 'capistrano' 4 | gem 'capistrano-bundler' 5 | gem 'capistrano-rails' 6 | gem 'capistrano-rails-console' 7 | gem 'capistrano-rbenv' 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2019 indrajit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puma Deploy 2 | [![Contributors](https://img.shields.io/github/contributors/eendroroy/puma-deploy.svg)](https://github.com/eendroroy/puma-deploy/graphs/contributors) 3 | [![GitHub last commit (branch)](https://img.shields.io/github/last-commit/eendroroy/puma-deploy/master.svg)](https://github.com/eendroroy/puma-deploy) 4 | [![license](https://img.shields.io/github/license/eendroroy/puma-deploy.svg)](https://github.com/eendroroy/puma-deploy/blob/master/LICENSE) 5 | [![GitHub issues](https://img.shields.io/github/issues/eendroroy/puma-deploy.svg)](https://github.com/eendroroy/puma-deploy/issues) 6 | [![GitHub closed issues](https://img.shields.io/github/issues-closed/eendroroy/puma-deploy.svg)](https://github.com/eendroroy/puma-deploy/issues?q=is%3Aissue+is%3Aclosed) 7 | [![GitHub pull requests](https://img.shields.io/github/issues-pr/eendroroy/puma-deploy.svg)](https://github.com/eendroroy/puma-deploy/pulls) 8 | [![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/eendroroy/puma-deploy.svg)](https://github.com/eendroroy/puma-deploy/pulls?q=is%3Apr+is%3Aclosed) 9 | 10 | Rails-5 application deploy configuration using puma and nginx. 11 | 12 | ## Description 13 | 14 | - Optional configuration of production and staging environments in same server. 15 | - Embedded Nginx configuration. 16 | - Embedded Puma init script and puma configuration. 17 | - Log rotation. 18 | 19 | ### Prerequisites 20 | 21 | Requires following gems. 22 | 23 | ``` 24 | gem 'puma' 25 | group :development do 26 | gem 'capistrano' 27 | gem 'capistrano-bundler' 28 | gem 'capistrano-rails' 29 | gem 'capistrano-rails-console' 30 | gem 'capistrano-rbenv' 31 | end 32 | ``` 33 | 34 | ### Installing 35 | 36 | - **Copy following files in your application root maintaining the structure:** 37 | 38 | ``` 39 | . 40 | ├── Capfile 41 | ├── config 42 | │ ├── deploy 43 | │ │ ├── shared 44 | │ │ │ ├── application.yml.template.erb 45 | │ │ │ ├── database.yml.template.erb 46 | │ │ │ ├── log_rotation.erb 47 | │ │ │ ├── nginx.conf.erb 48 | │ │ │ ├── puma.rb.erb 49 | │ │ │ ├── puma_init.sh.erb 50 | │ │ │ ├── secrets.yml.template.erb 51 | │ │ ├── production.rb 52 | │ │ └── staging.rb 53 | │ └── deploy.rb 54 | └── lib 55 | └── capistrano 56 | ├── substitute_strings.rb 57 | ├── tasks 58 | │ ├── check_revision.cap 59 | │ ├── compile_assets_locally.cap 60 | │ ├── db.cap 61 | │ ├── logs.cap 62 | │ ├── monit.cap 63 | │ ├── nginx.cap 64 | │ ├── puma.cap 65 | │ ├── push_deploy_tag.cap 66 | │ ├── run_tests.cap 67 | │ └── setup_config.cap 68 | └── template.rb 69 | ``` 70 | 71 | - **Put rails project's git url under :repo_url in 'config/deploy.rb' file.** 72 | 73 | Example: 74 | ```ruby 75 | #config/deploy.rb 76 | set :repo_url, 'git@github.com:user/repo.git' 77 | ``` 78 | 79 | - **Change application name under :application in 'config/deploy.rb' file.** 80 | 81 | Example: 82 | ```ruby 83 | #config/deploy.rb 84 | set :application, 'demo_application' 85 | ``` 86 | 87 | - **Define servers in 'config/deploy/production.rb' and 'config/deploy/staging.rb'** 88 | 89 | Example: 90 | ```ruby 91 | server '192.168.33.10', user: fetch(:deploy_user).to_s, roles: %w(app db), primary: true 92 | server '192.168.33.11', user: fetch(:deploy_user).to_s, roles: %w(app), primary: true 93 | server '192.168.33.12', user: fetch(:deploy_user).to_s, roles: %w(app), primary: true 94 | ``` 95 | 96 | - **Set server name in 'config/deploy/production.rb' and 'config/deploy/staging.rb'** 97 | 98 | Example: 99 | 100 | ```ruby 101 | # config/deploy/production.rb 102 | set :server_names, { 103 | '192.168.33.10': '192.168.33.10 node0.server', 104 | '192.168.33.11': '192.168.33.11 node1.server', 105 | '192.168.33.12': '192.168.33.12 node2.server', 106 | } 107 | ``` 108 | 109 | - **Set certificate and key path in 'config/deploy/production.rb' and 'config/deploy/staging.rb'g** 110 | 111 | ```ruby 112 | set :nginx_certificate_path, "#{shared_path}/certificates/#{fetch(:stage)}.crt" 113 | set :nginx_key_path, "#{shared_path}/certificates/#{fetch(:stage)}.key" 114 | ``` 115 | 116 | _For different certificate and key name in different server_ 117 | 118 | ```ruby 119 | set :nginx_certificate_paths, { 120 | '192.168.33.10': "/etc/certificates/192_168_33_10.crt", 121 | '192.168.33.11': "/etc/certificates/192_168_33_11.crt", 122 | '192.168.33.12': "/etc/certificates/192_168_33_12.crt", 123 | } 124 | set :nginx_key_paths, { 125 | '192.168.33.10': "/etc/certificates/192_168_33_10.key", 126 | '192.168.33.11': "/etc/certificates/192_168_33_11.key", 127 | '192.168.33.12': "/etc/certificates/192_168_33_12.key", 128 | } 129 | ``` 130 | 131 | _Note: configuration key name changes from `*_path` to `*_paths`_ 132 | 133 | ## Usage 134 | 135 | - **Upload configurations** 136 | 137 | ```bash 138 | $ bundle exec cap production deploy:setup_config 139 | ``` 140 | 141 | - **Deploy** 142 | 143 | ```bash 144 | $ bundle exec cap production deploy 145 | ``` 146 | 147 | ## Contributing 148 | 149 | Bug reports and pull requests are welcome on GitHub at [puma-deploy repository](https://github.com/eendroroy/puma-deploy). 150 | This project is intended to be a safe, welcoming space for collaboration, 151 | and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 152 | 153 | ## Authors 154 | 155 | * **Indrajit Roy** - *Owner* - [eendroroy](https://github.com/eendroroy) 156 | 157 | See also the list of [contributors](CONTRIBUTORS.md) who participated in this project. 158 | 159 | ## License 160 | 161 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 162 | 163 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # bundle exec cap staging deploy:setup_config 3 | # bundle exec cap staging deploy 4 | # 5 | 6 | lock '3.10.1' 7 | 8 | set :application, 'application' 9 | set :repo_url, '#' # Put Git url (Ex: https://github.com/user/repo.git) 10 | set :deploy_user, :deployer 11 | set :deploy_path, '/apps' 12 | set :pty, true 13 | set :tmp_dir, "/tmp" 14 | 15 | set :rbenv_type, :system 16 | set :rbenv_ruby, '2.5.0' 17 | set :rbenv_prefix, 18 | "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec" 19 | set :rbenv_map_bins, %w(rake gem bundle ruby rails) 20 | 21 | set :keep_releases, 5 22 | 23 | set :bundle_binstubs, nil 24 | 25 | set :linked_files, %w(config/application.yml config/database.yml config/secrets.yml) 26 | 27 | set( 28 | :linked_dirs, 29 | %w(log tmp/pids tmp/states tmp/sockets tmp/cache vendor/bundle public/system) 30 | ) 31 | 32 | set( 33 | :config_files, 34 | %w( 35 | nginx.conf 36 | application.yml.template 37 | database.yml.template 38 | secrets.yml.template 39 | log_rotation 40 | puma.rb 41 | puma_init.sh 42 | ) 43 | ) 44 | 45 | set(:executable_config_files, %w(puma_init.sh)) 46 | 47 | set( 48 | :symlinks, 49 | [ 50 | { 51 | source: 'nginx.conf', 52 | link: '/etc/nginx/sites-enabled/{{full_app_name}}.conf' 53 | }, 54 | { 55 | source: 'puma_init.sh', 56 | link: '/etc/init.d/puma_{{full_app_name}}' 57 | }, 58 | { 59 | source: 'log_rotation', 60 | link: '/etc/logrotate.d/{{full_app_name}}' 61 | } 62 | ] 63 | ) 64 | 65 | namespace :deploy do 66 | before :deploy, 'deploy:check_revision' 67 | before :deploy, "deploy:run_tests" 68 | after 'deploy:symlink:shared', 'deploy:compile_assets_locally' 69 | after :finishing, 'deploy:cleanup' 70 | before 'deploy:setup_config', 'nginx:remove_default_vhost' 71 | after 'deploy:setup_config', 'nginx:reload' 72 | after 'deploy:setup_config', 'monit:restart' 73 | after 'deploy:publishing', 'deploy:push_deploy_tag' 74 | after 'deploy:publishing', 'puma:restart' 75 | end 76 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | set :stage, :production 2 | set :branch, :master 3 | 4 | set :server_port, 80 5 | set :server_port_ssl, 443 6 | 7 | set :full_app_name, "#{fetch(:application)}_#{fetch(:stage)}" 8 | 9 | # Don't forget to put your server ip 10 | server '192.168.33.10', user: fetch(:deploy_user).to_s, roles: %w(app db), primary: true 11 | server '192.168.33.11', user: fetch(:deploy_user).to_s, roles: %w(app), primary: true 12 | server '192.168.33.12', user: fetch(:deploy_user).to_s, roles: %w(app), primary: true 13 | 14 | set :server_names, { 15 | '192.168.33.10': '192.168.33.10 node0.server', 16 | '192.168.33.11': '192.168.33.11 node1.server', 17 | '192.168.33.12': '192.168.33.12 node2.server', 18 | } 19 | 20 | set :deploy_to, "#{fetch(:deploy_path)}/#{fetch(:full_app_name)}" 21 | 22 | set :rails_env, :production 23 | 24 | set :puma_user, fetch(:deploy_user) 25 | set :puma_rackup, -> { File.join(current_path, 'config.ru') } 26 | set :puma_state, "#{shared_path}/tmp/states/puma.state" 27 | set :puma_pid, "#{shared_path}/tmp/pids/puma.pid" 28 | set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.#{fetch(:full_app_name)}.sock" 29 | set :puma_default_control_app, "unix://#{shared_path}/tmp/sockets/pumactl.#{fetch(:full_app_name)}.sock" 30 | set :puma_conf, "#{shared_path}/config/puma.rb" 31 | set :puma_access_log, "#{shared_path}/log/puma_access.log" 32 | set :puma_error_log, "#{shared_path}/log/puma_error.log" 33 | set :puma_role, :app 34 | set :puma_env, :production 35 | set :puma_threads, [1, 4] 36 | set :puma_workers, 8 37 | set :puma_worker_timeout, nil 38 | set :puma_init_active_record, false 39 | set :puma_preload_app, true 40 | set :puma_plugins, [:tmp_restart] 41 | set :nginx_disable_http, false 42 | set :nginx_http_limit_url, %w() 43 | set :allow_asset, true 44 | set :nginx_use_ssl, true 45 | set :nginx_https_limit_url, %w() 46 | set :nginx_certificate_path, "#{shared_path}/certificates/#{fetch(:stage)}.crt" 47 | set :nginx_key_path, "#{shared_path}/certificates/#{fetch(:stage)}.key" 48 | -------------------------------------------------------------------------------- /config/deploy/shared/application.yml.template.erb: -------------------------------------------------------------------------------- 1 | <%= fetch(:rails_env) %>: 2 | key: value 3 | -------------------------------------------------------------------------------- /config/deploy/shared/database.yml.template.erb: -------------------------------------------------------------------------------- 1 | <%= fetch(:rails_env) %>: 2 | adapter: mysql2 3 | encoding: utf8 4 | pool: 5 5 | timeout: 5000 6 | port: 3306 7 | host: localhost 8 | username: 9 | password: 10 | database: <%= "#{fetch(:application)}_#{fetch(:rails_env)}" %> 11 | -------------------------------------------------------------------------------- /config/deploy/shared/log_rotation.erb: -------------------------------------------------------------------------------- 1 | <%= fetch(:deploy_to) %>/shared/log/*.log { 2 | daily 3 | missingok 4 | rotate 30 5 | compress 6 | delaycompress 7 | notifempty 8 | sharedscripts 9 | copytruncate 10 | } 11 | -------------------------------------------------------------------------------- /config/deploy/shared/nginx.conf.erb: -------------------------------------------------------------------------------- 1 | upstream puma_<%= fetch(:stage) %> { 2 | server <%= fetch(:puma_bind) %> fail_timeout=15s; 3 | } 4 | 5 | <% unless fetch(:nginx_disable_http) %> 6 | server { 7 | server_name <%= fetch(:server_names).dig(host.hostname.to_sym) || host.hostname %>; 8 | listen <%= fetch(:server_port) %>; 9 | 10 | real_ip_header proxy_protocol; 11 | 12 | <% if fetch(:allow_asset) || true %> 13 | root <%= fetch(:deploy_to) %>/current/public; 14 | location ^~ /assets/ { 15 | gzip_static on; 16 | expires max; 17 | add_header Cache-Control public; 18 | proxy_set_header X-Real-IP $remote_addr; 19 | proxy_pass_request_headers on; 20 | } 21 | <% end %> 22 | 23 | <% if (fetch(:nginx_http_limit_url) || []).count > 0 %> 24 | <% fetch(:nginx_http_limit_url).each do |uri| %> 25 | location <%= uri %> { 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | proxy_set_header Host $http_host; 28 | proxy_redirect off; 29 | proxy_set_header X-Real-IP $remote_addr; 30 | proxy_pass_request_headers on; 31 | proxy_pass http://puma_<%= fetch(:stage) %>; 32 | } 33 | <% end %> 34 | <% else %> 35 | try_files $uri/index.html $uri @puma_<%= fetch(:stage) %>; 36 | location @puma_<%= fetch(:stage) %> { 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header Host $http_host; 39 | proxy_redirect off; 40 | proxy_set_header X-Real-IP $remote_addr; 41 | proxy_pass_request_headers on; 42 | proxy_pass http://puma_<%= fetch(:stage) %>; 43 | } 44 | <% end %> 45 | 46 | error_page 500 502 503 504 /500.html; 47 | client_max_body_size 4G; 48 | keepalive_timeout 60s; 49 | } 50 | <% end %> 51 | 52 | <% if fetch(:nginx_use_ssl) %> 53 | server { 54 | server_name <%= fetch(:server_names).dig(host.hostname.to_sym) || host.hostname %>; 55 | listen <%= fetch(:server_port_ssl) %>; 56 | root <%= fetch(:deploy_to) %>/current/public; 57 | 58 | real_ip_header proxy_protocol; 59 | 60 | <% if fetch(:allow_asset) || true %> 61 | root <%= fetch(:deploy_to) %>/current/public; 62 | location ^~ /assets/ { 63 | gzip_static on; 64 | expires max; 65 | add_header Cache-Control public; 66 | proxy_set_header X-Real-IP $remote_addr; 67 | proxy_pass_request_headers on; 68 | } 69 | <% end %> 70 | 71 | <% if (fetch(:nginx_https_limit_url) || []).count > 0 %> 72 | <% fetch(:nginx_https_limit_url).each do |uri| %> 73 | location <%= uri %> { 74 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 75 | proxy_set_header Host $http_host; 76 | proxy_set_header X-Real-IP $remote_addr; 77 | proxy_pass_request_headers on; 78 | proxy_redirect off; 79 | proxy_pass http://puma_<%= fetch(:stage) %>; 80 | } 81 | <% end %> 82 | <% else %> 83 | try_files $uri/index.html $uri @puma_<%= fetch(:stage) %>; 84 | location @puma_<%= fetch(:stage) %> { 85 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 86 | proxy_set_header Host $http_host; 87 | proxy_set_header X-Real-IP $remote_addr; 88 | proxy_pass_request_headers on; 89 | proxy_redirect off; 90 | proxy_pass http://puma_<%= fetch(:stage) %>; 91 | } 92 | 93 | <% end %> 94 | 95 | error_page 500 502 503 504 /500.html; 96 | client_max_body_size 4G; 97 | keepalive_timeout 60s; 98 | ssl on; 99 | ssl_certificate <%= fetch(:nginx_certificate_path) || fetch(:nginx_certificate_paths).dig(host.hostname.to_sym) %>; 100 | ssl_certificate_key <%= fetch(:nginx_key_path) || fetch(:nginx_key_paths).dig(host.hostname.to_sym) %>; 101 | } 102 | <% end %> 103 | -------------------------------------------------------------------------------- /config/deploy/shared/puma.rb.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env puma 2 | 3 | directory '<%= current_path %>' 4 | environment '<%= fetch(:rails_env) %>' 5 | 6 | pidfile '<%= fetch(:puma_pid) %>' 7 | state_path '<%= fetch(:puma_state) %>' 8 | stdout_redirect '<%= fetch(:puma_access_log) %>', '<%= fetch(:puma_error_log) %>', true 9 | 10 | daemonize 11 | 12 | threads <%= fetch(:puma_threads).join(', ') %> 13 | 14 | bind '<%= fetch(:puma_bind) %>' 15 | 16 | activate_control_app '<%= fetch(:puma_default_control_app) %>' 17 | 18 | workers '<%= fetch(:puma_workers) %>' 19 | 20 | <% if fetch(:puma_preload_app) %> 21 | preload_app! 22 | <% end %> 23 | 24 | <% fetch(:puma_plugins).each do |plug| %> 25 | plugin <%= ':' + plug.to_s %> 26 | <% end %> 27 | 28 | on_restart do 29 | puts 'Refreshing Gemfile' 30 | ENV['BUNDLE_GEMFILE'] = '<%= fetch(:bundle_gemfile, "#{current_path}/Gemfile") %>' 31 | end 32 | 33 | before_fork do 34 | ActiveRecord::Base.connection_pool.disconnect! 35 | end 36 | 37 | on_worker_boot do 38 | ActiveSupport.on_load(:active_record) do 39 | ActiveRecord::Base.establish_connection 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /config/deploy/shared/puma_init.sh.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PATH=/usr/local/bin:/usr/local/sbin/:/sbin:/usr/sbin:/bin:/usr/bin 4 | DESC="Puma rack web server" 5 | NAME=puma_<%=fetch(:full_app_name)%> 6 | SCRIPT_NAME=/etc/init.d/${NAME} 7 | APP_ROOT=<%=current_path%> 8 | PIDFILE=<%= fetch(:puma_pid) %> 9 | STATE_FILE=<%= fetch(:puma_state) %> 10 | 11 | log_daemon_msg() { echo "$@"; } 12 | log_end_msg() { [ $1 -eq 0 ] && RES=OK; logger ${RES:=FAIL}; } 13 | 14 | run_pumactl(){ 15 | [ $# -lt 1 ] && echo "$# params were given, Expected 1" && exit 1 16 | cd ${APP_ROOT} && <%= fetch(:rbenv_prefix) %> bundle exec pumactl -F <%=fetch(:puma_conf)%> $1 17 | } 18 | 19 | # 20 | # Function that starts the puma 21 | # 22 | start_task() { 23 | if [ -e ${PIDFILE} ]; then 24 | PID=`cat ${PIDFILE}` 25 | # If the puma isn't running, run it, otherwise restart it. 26 | if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then 27 | do_start_task 28 | else 29 | restart_task 30 | fi 31 | else 32 | do_start_task 33 | fi 34 | } 35 | do_start_task() { 36 | log_daemon_msg "--> Woke up puma ${APP_ROOT}" 37 | run_pumactl start 38 | } 39 | 40 | # 41 | # Function that stops the daemon/service 42 | # 43 | stop_task() { 44 | log_daemon_msg "--> Stopping puma in path: ${APP_ROOT} ..." 45 | if [ -e ${PIDFILE} ]; then 46 | PID=`cat ${PIDFILE}` 47 | if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then 48 | log_daemon_msg "--> Puma isn't running in path: ${APP_ROOT}." 49 | else 50 | log_daemon_msg "--> About to kill puma with PID: `cat $PIDFILE` ..." 51 | if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then 52 | log_daemon_msg "--> Puma isn't running in path: ${APP_ROOT}." 53 | return 0 54 | else 55 | run_pumactl stop 56 | log_daemon_msg "--> Waiting for status ..." 57 | sleep 5 58 | if [ "`ps -A -o pid= | grep -c ${PID}`" -eq 0 ]; then 59 | log_daemon_msg "--> Puma with pid ${PID} stopped successfully." 60 | rm -f ${PIDFILE} ${STATE_FILE} 61 | else 62 | log_daemon_msg "--> Unable to stop puma with pid ${PID}." 63 | fi 64 | fi 65 | fi 66 | else 67 | log_daemon_msg "--> Puma isn't running in path: ${APP_ROOT}." 68 | fi 69 | return 0 70 | } 71 | 72 | # 73 | # Function that sends a SIGUSR2 to the daemon/service 74 | # 75 | restart_task() { 76 | if [ -e ${PIDFILE} ]; then 77 | log_daemon_msg "--> About to restart puma in path: ${APP_ROOT} ..." 78 | run_pumactl restart 79 | else 80 | log_daemon_msg "--> Your puma was never playing... Let's get it out there first ..." 81 | start_task 82 | fi 83 | return 0 84 | } 85 | 86 | # 87 | # Function that sends a SIGUSR2 to the daemon/service 88 | # 89 | status_task() { 90 | if [ -e ${PIDFILE} ]; then 91 | log_daemon_msg "--> About to status puma ${APP_ROOT} ..." 92 | run_pumactl status 93 | else 94 | log_daemon_msg "---> Puma isn't running in path: ${APP_ROOT}." 95 | fi 96 | return 0 97 | } 98 | 99 | case "$1" in 100 | start) 101 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting ${DESC}" "${NAME} ..." 102 | start_task 103 | case "$?" in 104 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 105 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 106 | esac 107 | ;; 108 | stop) 109 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping ${DESC}" "${NAME} ..." 110 | stop_task 111 | case "$?" in 112 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 113 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 114 | esac 115 | ;; 116 | status) 117 | log_daemon_msg "Status ${DESC}" "${NAME} ..." 118 | status_task 119 | case "$?" in 120 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 121 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 122 | esac 123 | ;; 124 | restart) 125 | log_daemon_msg "Restarting ${DESC}" "${NAME} ..." 126 | restart_task 127 | case "$?" in 128 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 129 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 130 | esac 131 | ;; 132 | *) 133 | echo "Usage:" >&2 134 | echo " ${SCRIPT_NAME} {start|stop|status|restart}" >&2 135 | exit 3 136 | ;; 137 | esac 138 | : 139 | -------------------------------------------------------------------------------- /config/deploy/shared/secrets.yml.template.erb: -------------------------------------------------------------------------------- 1 | # Do not keep production secrets in the repository, 2 | # instead read values from the environment. 3 | <%= fetch(:rails_env) %>: 4 | secret_key_base: 5 | -------------------------------------------------------------------------------- /config/deploy/staging.rb: -------------------------------------------------------------------------------- 1 | set :stage, :staging 2 | set :branch, :development 3 | 4 | set :server_port, 3000 5 | set :server_port_ssl, 3443 6 | 7 | set :full_app_name, "#{fetch(:application)}_#{fetch(:stage)}" 8 | 9 | # Don't forget to put your server ip 10 | server '192.168.33.10', user: fetch(:deploy_user).to_s, roles: %w(app db), primary: true 11 | server '192.168.33.11', user: fetch(:deploy_user).to_s, roles: %w(app), primary: true 12 | server '192.168.33.12', user: fetch(:deploy_user).to_s, roles: %w(app), primary: true 13 | 14 | set :server_names, { 15 | '192.168.33.10': '192.168.33.10 node0.server', 16 | '192.168.33.11': '192.168.33.11 node1.server', 17 | '192.168.33.12': '192.168.33.12 node2.server', 18 | } 19 | 20 | set :deploy_to, "#{fetch(:deploy_path)}/#{fetch(:full_app_name)}" 21 | 22 | set :rails_env, :staging 23 | 24 | set :puma_user, fetch(:deploy_user) 25 | set :puma_rackup, -> { File.join(current_path, 'config.ru') } 26 | set :puma_state, "#{shared_path}/tmp/states/puma.state" 27 | set :puma_pid, "#{shared_path}/tmp/pids/puma.pid" 28 | set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.#{fetch(:full_app_name)}.sock" 29 | set :puma_default_control_app, "unix://#{shared_path}/tmp/sockets/pumactl.#{fetch(:full_app_name)}.sock" 30 | set :puma_conf, "#{shared_path}/config/puma.rb" 31 | set :puma_access_log, "#{shared_path}/log/puma_access.log" 32 | set :puma_error_log, "#{shared_path}/log/puma_error.log" 33 | set :puma_role, :app 34 | set :puma_env, :staging 35 | set :puma_threads, [1, 4] 36 | set :puma_workers, 4 37 | set :puma_worker_timeout, nil 38 | set :puma_init_active_record, false 39 | set :puma_preload_app, true 40 | set :puma_plugins, [:tmp_restart] 41 | set :nginx_disable_http, false 42 | set :nginx_http_limit_url, %w() 43 | set :allow_asset, true 44 | set :nginx_use_ssl, true 45 | set :nginx_https_limit_url, %w() 46 | set :nginx_certificate_path, "#{shared_path}/certificates/#{fetch(:stage)}.crt" 47 | set :nginx_key_path, "#{shared_path}/certificates/#{fetch(:stage)}.key" 48 | -------------------------------------------------------------------------------- /lib/capistrano/substitute_strings.rb: -------------------------------------------------------------------------------- 1 | # we often want to refer to variables which 2 | # are defined in subsequent stage files. This 3 | # let's us use the {{var}} to represent fetch(:var) 4 | # in strings which are only evaluated at runtime. 5 | 6 | def sub_strings(input_string) 7 | output_string = input_string 8 | input_string.scan(/{{(\w*)}}/).each do |var| 9 | output_string.gsub!("{{#{var[0]}}}", fetch(var[0].to_sym)) 10 | end 11 | output_string 12 | end 13 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/check_revision.cap: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | desc "checks whether the currently checkout out revision matches the 3 | remote one we're trying to deploy from" 4 | task :check_revision do 5 | branch = fetch(:branch) 6 | unless `git rev-parse HEAD` == `git rev-parse origin/#{branch}` 7 | puts "WARNING: HEAD is not the same as origin/#{branch}" 8 | puts "Run `git push` to sync changes or make sure you've" 9 | puts "checked out the branch: #{branch} as you can only deploy" 10 | puts "if you've got the target branch checked out" 11 | exit 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/compile_assets_locally.cap: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | desc 'compiles assets locally then rsyncs' 3 | task :compile_assets_locally do 4 | run_locally do 5 | execute "RAILS_ENV=#{fetch(:rails_env)} bundle exec rake assets:precompile" 6 | end 7 | on roles(:app) do |role| 8 | run_locally do 9 | execute "rsync -av ./public/assets/ #{role.user}@#{role.hostname}:#{release_path}/public/assets/;" 10 | end 11 | end 12 | run_locally do 13 | execute 'rm -rf ./public/assets' 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/db.cap: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc "Runs seed on server" 3 | task :seed do 4 | on roles(:db) do 5 | execute "cd #{current_path} && RAILS_ENV=#{fetch(:rails_env)} #{fetch(:rbenv_prefix)} bundle exec rails db:seed" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/logs.cap: -------------------------------------------------------------------------------- 1 | namespace :logs do 2 | task :tail, :file do |_, args| 3 | if args[:file] 4 | on roles(:app) do 5 | execute "tail -f #{shared_path}/log/#{args[:file]}.log" 6 | end 7 | else 8 | puts "please specify a logfile e.g: 'rake logs:tail[logfile]" 9 | puts "will tail 'shared_path/log/logfile.log'" 10 | puts "remember if you use zsh you'll need to format it as:" 11 | puts "rake 'logs:tail[logfile]' (single quotes)" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/monit.cap: -------------------------------------------------------------------------------- 1 | namespace :monit do 2 | %w(start stop restart).each do |task_name| 3 | desc "#{task_name} Monit" 4 | task task_name do 5 | on roles(:app), in: :sequence, wait: 5 do 6 | sudo "service monit #{task_name}" 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/nginx.cap: -------------------------------------------------------------------------------- 1 | namespace :nginx do 2 | %w(start stop restart reload).each do |task_name| 3 | desc "#{task} Nginx" 4 | task task_name do 5 | on roles(:app), in: :sequence, wait: 5 do 6 | sudo "/usr/sbin/service nginx #{task_name}" 7 | end 8 | end 9 | end 10 | 11 | desc 'Remove default Nginx Virtual Host' 12 | task 'remove_default_vhost' do 13 | on roles(:app) do 14 | if test('[ -f /etc/nginx/sites-enabled/default ]') 15 | sudo 'rm /etc/nginx/sites-enabled/default' 16 | puts 'removed default Nginx Virtualhost' 17 | else 18 | puts 'No default Nginx Virtualhost to remove' 19 | end 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/puma.cap: -------------------------------------------------------------------------------- 1 | namespace :puma do 2 | desc 'Commands for puma application' 3 | %w(start stop force-stop restart upgrade reopen-logs).each do |command| 4 | task command.to_sym do 5 | on roles(:app), in: :sequence, wait: 5 do 6 | execute "/etc/init.d/puma_#{fetch(:full_app_name)} #{command}" 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/push_deploy_tag.cap: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | desc "push deploy tag before deploy" 3 | task :push_deploy_tag do 4 | on roles(:db) do 5 | tag = "#{fetch(:stage)}_#{fetch(:release_timestamp)}" 6 | puts `git tag #{tag} #{fetch(:current_revision)} -m "Deployed from #{fetch(:branch)}@#{fetch(:current_revision)[0..6]} by #{fetch(:local_user)} at #{Time.now.strftime("%D %I:%M:%S%p")}"` 7 | puts `git push origin #{tag}` 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/run_tests.cap: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | desc "Runs test before deploying, can't deploy unless they pass" 3 | task :run_tests do 4 | test_log = 'log/capistrano.test.log' 5 | tests = fetch(:tests) 6 | tests.each do |test| 7 | puts "--> Running tests: '#{test}', please wait ..." 8 | unless system "bundle exec rspec #{test} > #{test_log} 2>&1" 9 | puts "--> Tests: '#{test}' failed. Results in: #{test_log} and below:" 10 | system "cat #{test_log}" 11 | exit 12 | end 13 | puts "--> '#{test}' passed" 14 | end 15 | puts '--> All tests passed' 16 | system "rm #{test_log}" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/setup_config.cap: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | task :setup_config do 3 | on roles(:app) do 4 | if test("[ -f #{shared_path}/config/log_rotation ]") 5 | sudo "rm #{shared_path}/config/log_rotation" 6 | end 7 | 8 | config_files = fetch(:config_files) 9 | unless config_files.nil? 10 | config_files.each do |file| 11 | smart_template file 12 | end 13 | end 14 | 15 | executable_files = fetch(:executable_config_files) 16 | unless executable_files.nil? 17 | executable_files.each do |file| 18 | execute :chmod, "+x #{shared_path}/config/#{file}" 19 | end 20 | end 21 | 22 | symlinks = fetch(:symlinks) 23 | unless symlinks.nil? 24 | symlinks.each do |symlink| 25 | sudo "ln -nfs #{shared_path}/config/#{symlink[:source]} #{sub_strings(symlink[:link])}" 26 | end 27 | end 28 | 29 | mvs = fetch(:mvs) 30 | unless mvs.nil? 31 | mvs.each do |mv| 32 | sudo "mv #{shared_path}/config/#{mv[:source]} #{sub_strings(mv[:destination])}" 33 | end 34 | end 35 | 36 | if test("[ -f /etc/logrotate.d/#{fetch(:application)}_#{fetch(:stage)} ]") 37 | sudo "chown root:root /etc/logrotate.d/#{fetch(:application)}_#{fetch(:stage)}" 38 | end 39 | end 40 | end 41 | end 42 | 43 | -------------------------------------------------------------------------------- /lib/capistrano/template.rb: -------------------------------------------------------------------------------- 1 | def smart_template(from, to = nil) 2 | to ||= from 3 | full_to_path = "#{shared_path}/config/#{to}" 4 | if (from_erb_path = template_file(from)) 5 | from_erb = StringIO.new(ERB.new(File.read(from_erb_path)).result(binding)) 6 | upload! from_erb, full_to_path 7 | info "copying: #{from_erb} to: #{full_to_path}" 8 | else 9 | error "error #{from} not found" 10 | end 11 | end 12 | 13 | def template_file(name) 14 | file = "config/deploy/#{fetch(:full_app_name)}/#{name}.erb" 15 | return file if File.exist?(file) 16 | file = "config/deploy/shared/#{name}.erb" 17 | return file if File.exist?(file) 18 | nil 19 | end 20 | --------------------------------------------------------------------------------