Skip to content

Instantly share code, notes, and snippets.

@u007
Last active February 17, 2017 06:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save u007/91bd898c5bdcc10098c995acf9ebeee8 to your computer and use it in GitHub Desktop.
Save u007/91bd898c5bdcc10098c995acf9ebeee8 to your computer and use it in GitHub Desktop.
full git deployment with auto boot of puma for ruby on rails via rvm, with nightly restart of rails

SETTING UP

  • 1st, add production origin to git.
  • Next, ssh to remote and do a git init
  • Then on local git:
    • git pull production master
    • git push production master
ln -s /home/username/rails/production /home/username/rails/current
mkdir -p ~/rails/shared/config
mkdir -p ~/rails/shared/log
mkdir -p ~/rails/shared/vendor/bundle
mkdir -p ~/rails/shared/log
mkdir -p ~/rails/shared/tmp/pids
mkdir -p ~/rails/shared/tmp/cache
mkdir -p ~/rails/shared/tmp/sockets
  • upload secrets.yml, elasticsearch.yml to ~/rails/shared/config/
  • make sure not to commit config/secrets.yml into git, remove it if you have done so, use this:
git rm --cached config/secrets.yml# back this file 1st to somewhere ele
# add this to gitignore
config/secrets.yml
  • Next, add user to /etc/group, find for line with "sudo" and add the user at the end of the line, separated by comma.
  • Next, setting up ruby on rails, install rvm first. Then:
rvm install 2.3.3
rvm use 2.3.3
gem install bundler
gem install puma
  • Next, goto rails root directory:
cd ~/rails/current
rvm 2.3.3 do bundle install --deployment --without development test

  • Then run load schema, seeds and then migration
export RAILS_ENV=production
rvm 2.3.3 do bin/rake db:schema:load
rvm 2.3.3 do bin/rake db:migrate
rvm 2.3.3 do bin/rake db:seed
  • then setup cronjob to restart instance every nightly:
crontab -e
0 1 * * * bash -lc 'cd /home/username/rails/current && rvm 2.3.3 do bundle exec pumactl -P tmp/pids/puma.pid phased-restart'
  • then setup rc.local to auto start instance when rebooted / booted (follow instruction in file)
  • then setup nginx to proxy application onto rails backend
mkdir -p ~/log
  • create the file nginx-[domainname].conf to /etc/nginx/sites-enabled
service nginx restart
  • and finally, setup /etc/logrotate.d/rails to auto clean up your log/production.log

  • that is all :), simply git commit, then git push production master, and the .git/hook/post-receive will do it's job

# located at /etc/nginx/sites-enabled/domain.com
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
upstream railsapp {
server 127.0.0.1:3000;
#server unix:///home/username/rails/shared/tmp/sockets/puma.sock;
}
server {
listen 80;
server_name domain.com;
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# ~2 seconds is often enough for most folks to parse HTML/CSS and
# retrieve needed images/icons/frames, connections are cheap in
# nginx so increasing this is generally safe...
keepalive_timeout 5;
# path for static files
access_log /home/username/log/nginx.access.log;
error_log /home/username/log/nginx.error.log info;
# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/maintenance.html) {
rewrite ^(.*)$ /maintenance.html last;
break;
}
location / {
root /home/username/rails/current/public;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header REMOTE_ADDR $http_x_real_ip;
proxy_set_header Host $http_host;
# If the file exists as a static file serve it directly without
# running all the other rewrite tests on it
if (-f $request_filename) {
break;
}
# check for index.html for directory index
# if it's there on the filesystem then rewrite
# the url to add /index.html to the end of it
# and then break to send it to the next config rules.
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
# this is the meat of the rack page caching config
# it adds .html to the end of the url and then checks
# the filesystem for that file. If it exists, then we
# rewrite the url to have explicit .html on the end
# and then send it on its way to the next config rule.
# if there is no file on the fs then it sets all the
# necessary headers and proxies to our upstream pumas
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://railsapp;
break;
}
}
# Now this supposedly should work as it gets the filenames with querystrings that Rails provides.
# BUT there's a chance it could break the ajax calls.
# location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
# expires max;
# break;
# }
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
# Error pages
# error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/username/rails/current/public;
}
}
#!/usr/bin/env bash
# make sure to chmod a+x post-receive
# located at: .git/hooks/post-receive
export RUBYVERSION=2.3.3
source /etc/profile
cd ..
export PWD=`pwd`
echo "cwd: $PWD"
GIT_DIR='.git'
export RAILS_ENV=production
refname=master
git reset --hard
git checkout master
unset GIT_DIR
unset GIT_WORK_TREE
#--quiet
echo "------> Bundling gems!!"
rvm $RUBYVERSION do bundle --deployment --without development test
if [ -d "../shared/" ] ; then
echo "------> copying configs"
cp -rf ../shared/config/* ./config/
ln -s "$PWD/../shared/log" ./
ln -s "$PWD/../shared/public/uploads" ./public/
ln -s "$PWD/../shared/public/system" ./public/
# ln -s "$PWD/../shared/tmp" ./
ln -s "$PWD/../shared/vendor/bundle" ./vendor/
ln -s "$PWD/../shared/tmp/pids" ./tmp/
ln -s "$PWD/../shared/tmp/cache" ./tmp/
ln -s "$PWD/../shared/tmp/sockets" ./tmp/
fi
echo "------> running bundle exec rake server:deploy"
rvm $RUBYVERSION do bin/rake server:deploy
# thanks to blazing gem, extracted script from them
# located at config/puma.rb
# if you have more ram, increase this thread
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 2 }.to_i
threads 1, threads_count
# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
#
port ENV.fetch("PORT") { 3000 }
daemonize true
pidfile 'tmp/pids/puma.pid'
state_path 'tmp/pids/puma.state'
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }
# if you have more core and also more ram, enable this and increase if needed.
# not recommended for system below 2GB of ram
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory. If you use this option
# you need to make sure to reconnect any threads in the `on_worker_boot`
# block.
#
# preload_app!
# do not use preload if you want to use phased-restart
prune_bundler
# located at: /etc/rc.local
su username_here /bin/bash -lc 'cd /home/userdir_here/rails/current && rvm 2.3.3 do bundle exec puma -C config/puma.rb config.ru' &
# located at lib/tasks/server.rake
namespace :server do
desc 'Deplying to server'
task deploy: :environment do
asset_precompilation_triggers = %w(app/assets vendor/assets Gemfile.lock config)
puts "ok"
if (deployed_file_exists?("db/schema.rb") || deployed_file_exists?("db/structure.sql")) && trigger_update?("db/")
puts 'post_deploy: migrating..'
rvm_run "bin/rake db:migrate 2>&1"
end
if asset_precompilation_triggers.detect {|path| trigger_update?(path)}
puts 'post_deploy: ignoring locally assets:precompile.'
# system "bundle exec rake assets:precompile 2>&1"
end
restart_server
# rvm_run "bundle exec rake rails:update:bin 2>&1"
# system "bundle exec rake server:restart 2>&1"
save_commit_count
end
desc 'Restarting server'
task restart: :environment do
puts 'Restarting...'
restart_server
end
def rvm_run(cmd)
cmd1 = "/usr/local/rvm/bin/rvm 2.3.3 do #{cmd}"
Rails.logger.info "running: #{cmd1}"
system cmd1
end
def shared_path
shared_path = "`pwd`/../shared"
end
def restart_server
system "pumactl -P tmp/pids/puma.pid phased-restart"# phased restart only useful if you have more than 1 worker
# system "puma -C #{shared_path}/puma.rb config.ru"
# system "touch tmp/restart.txt 2>&1"
# if you use sidekiq
rvm_run "bundle exec sidekiqctl stop tmp/sidekiq.pid 0"
rvm_run "bundle exec sidekiq -d -P tmp/sidekiq.pid -L log/sidekiq.log"
# if you use delayed job
# rvm_run "bin/delayed_job -n 2 restart"
end
def deploy_to
"."
end
# Capture the result of a git command run within the `deploy_to` directory
def capture_git(command)
`cd #{deploy_to} && git #{command}`
end
def trigger_update?(path)
# puts "updated? #{path} in #{changed_files.inspect}"
changed_files.detect {|p| p[0, path.length] == path}
end
def changed_files
# puts "latest tag: #{latest_tag}"
cnt = latest_merge_count
# puts "commit since last push: #{cnt}"
@changed_files = capture_git('diff --name-only HEAD~'+cnt.to_s+' | cat').split
# @changed_files ||= if latest_tag
# capture_git("diff --name-only #{latest_tag} origin/#{branch} | cat").split
# else
# capture_git("ls-files | cat").split
# end
end
def deployed_file_exists?(path)
exit_code("cd #{deploy_to} && [ -f #{path} ]") == "0"
end
# Does the given directory exist within the deployment directory?
def deployed_dir_exists?(path)
exit_code("cd #{deploy_to} && [ -d #{path} ]") == "0"
end
# Has the given path been created or changed since the previous deployment? During the first
# successful deployment this will always return true if the file exists.
def deployed_file_changed?(path)
return deployed_file_exists?(path) unless latest_tag
exit_code("cd #{deploy_to} && git diff --exit-code #{latest_tag} origin/#{branch} #{path}") == "1"
end
def exit_code(command)
# puts "cmd: #{command} > /dev/null 2>&1; echo $?".strip
# Ammeter.OutputCapturer.capture("#{command} > /dev/null 2>&1; echo $?").strip
`#{command} > /dev/null 2>&1; echo $?`.strip
end
def latest_merge_count
if File.exists?("../shared/git_count")
res = `cat ../shared/git_count`.strip.to_i
new_count = capture_git('rev-list --all --count').to_i
return new_count - res
else
return 1
end
end
def save_commit_count
new_count = capture_git('rev-list --all --count').to_i
File.open('../shared/git_count', 'w') { |file| file.write(new_count.to_s) }
end
def latest_tag
latest_tag_from_repository
end
def release_matcher
/\A[0-9]{14}\Z/
end
def latest_tag_from_repository
tags = capture_git("tag").strip.split
tags.grep(release_matcher).last
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment