Last active
February 16, 2019 13:14
-
-
Save vaibhav-kaushal/bb5976b15d486345fe52a38ce5a5f5f4 to your computer and use it in GitHub Desktop.
The Ruby on Rails Deployment Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# ============================================================================ | |
# This is a Ruby on Rails (RoR) Deployment script to deploy an existing RoR | |
# application. The entire guide (explaining all details and setup) is located | |
# at: | |
# | |
# https://vaibhavkaushal.com/rails-deployment/ | |
# | |
# If you are interested in only understanding what this script does in | |
# particular, please read about the steps at: | |
# | |
# https://vaibhavkaushal.com/rails-deployment/deployment-process/ | |
# | |
# ============================================================================ | |
# Step 1 - Declare the variables | |
colorcode='' | |
base_dir='<your_base_dir>' # e.g. /home/ubuntu/mysite | |
git_repo_url='<your git repo url>' # e.g. [email protected]:vaibhav-kaushal/my-rails-app.git -- remember that this should be accessible without password prompts | |
git_deployment_branch='<your deployment branch>' # e.g. master | |
api_only_app=true | |
restart_sidekiq=true | |
# Function to print messages in different colors | |
function pr () { | |
colorcode='39' | |
case $1 in | |
black) | |
colorcode='30' | |
;; | |
red) | |
colorcode='31' | |
;; | |
green) | |
colorcode='32' | |
;; | |
yellow) | |
colorcode='33' | |
;; | |
blue) | |
colorcode='34' | |
;; | |
magenta) | |
colorcode='35' | |
;; | |
cyan) | |
colorcode='36' | |
;; | |
lightgray) | |
colorcode='37' | |
;; | |
darkgray) | |
colorcode='90' | |
;; | |
lightred) | |
colorcode='91' | |
;; | |
lightgreen) | |
colorcode='92' | |
;; | |
lightyellow) | |
colorcode='93' | |
;; | |
lightblue) | |
colorcode='94' | |
;; | |
lightmagenta) | |
colorcode='95' | |
;; | |
lightcyan) | |
colorcode='96' | |
;; | |
white) | |
colorcode='97' | |
;; | |
*) | |
colorcode='39' | |
;; | |
esac | |
echo -e "\e[${colorcode}m$2\e[39m" | |
} | |
# Confirm the deployment from the user | |
pr blue "You are logged in to $(hostname)"; | |
pr blue "======================== IMPORTANT =========================="; | |
pr blue "Please check the hostname and see that the target is correct." | |
pr blue "If you want to stop the deployment, press 'N' at the prompt." | |
pr blue "============================================================="; | |
pr red "Continue with deployment? (Y/n)" | |
read yesorno | |
if [ "$yesorno" != 'y' ] && [ "$yesorno" != 'Y' ] && [ "$yesorno" != "" ]; then | |
pr red "You responded '$yesorno'" | |
pr red "Aborting Deployment" | |
exit 255 | |
fi | |
pr green "You responded '$yesorno'" | |
pr green "Continuing with deployment" | |
# Step 2 - Check the directories | |
pr blue "Trying to cd to $base_dir ..." | |
cd "$base_dir" || ( | |
pr red "! ERROR: not set up." | |
pr red "The path '$base_dir' is not accessible on the server." | |
pr blue "You need to create the directory first." | |
false | |
) || exit 15 | |
pr green "We are in $base_dir" | |
# Step 2.1 - Check releases path | |
pr blue "Checking if $base_dir/releases is a directory ..." | |
if [ ! -d "$base_dir/releases" ]; then | |
pr red "! ERROR: not set up." | |
pr red "The directory '$base_dir/releases' does not exist on the server." | |
pr blue "You need to create the directory first." | |
exit 16 | |
fi | |
pr green "$base_dir/releases is a directory" | |
# Step 2.2 - Determine $previous_path and other variables | |
pr blue "Ensuring that current directory links to a release ..." | |
if [ -h "$base_dir/current" ] && [ -d "$base_dir/current" ]; then | |
previous_path=$(cd "$base_dir/current" >/dev/null && pwd -LP) | |
else | |
pr red "$base_dir/current has to be a symbolic link to one of the releases directory" | |
fi | |
pr green "previous_path is set to $previous_path" | |
# Step 3 - Capturing the paths | |
build_path="./tmp/build-$(date +%s)" | |
pr blue "Build path is $build_path" | |
version="$((`ls -1 $base_dir/releases | sort -n | tail -n 1`+1))" | |
release_path="$base_dir/releases/$version" | |
pr blue "Latest version detected in $base_dir/releases is $version" | |
# Step 3.1 - Sanity check | |
if [ -e "$build_path" ]; then | |
pr red "! ERROR: Path already exists." | |
pr red 'Aborting deployment' | |
exit 18 | |
fi | |
# Step 4 - Fending against multiple deployments | |
pr blue "pwd returns: $(pwd)" | |
pr blue "Checking for the presence of a deploy.lock file ..." | |
if [ -e "deploy.lock" ]; then | |
pr red "! ERROR: another deployment is ongoing." | |
pr red "The file 'deploy.lock' was found. File was last modified at $(stat -c %y deploy.lock)" | |
pr blue "If no other deployment is ongoing, delete the file on server and try again" | |
exit 17 | |
fi | |
pr green "deply.lock does not exist" | |
pr magenta "Basic tests passed. Setting and testing environment" | |
pr blue "We are in $(pwd)" | |
pr blue "Creating deploy.lock file" | |
touch deploy.lock | |
pr green "done" | |
pr blue "Creating a temporary build path" | |
mkdir -p "$build_path" | |
cd "$build_path" | |
pr green "We are in $(pwd -LP)" | |
# Step 5 - Enable rbenv | |
pr blue "Testing rbenv ..." | |
pr blue "Loading rbenv" | |
export RBENV_ROOT="$HOME/.rbenv" | |
export PATH="$HOME/.rbenv/bin:$PATH" | |
if ! which rbenv >/dev/null; then | |
pr red "! rbenv not found" | |
exit 1 | |
fi | |
pr green "rbenv found in $(which rbenv)" | |
pr blue "Enabling rbenv ..." | |
eval "$(rbenv init -)" | |
pr green "done" | |
# Step 6 - Getting code from remote git repo | |
pr blue "Checking we have a local copy of the repo ..." | |
if [ ! -d "$base_dir/scm/objects" ]; then | |
# STEP 6 - Getting code from remote git repo (when we have to clone the repo the first time) | |
pr blue "$base_dir/scm/objects directory was not found (we don't seem to have a local copy)" | |
pr blue "Cloning the Git repository" | |
git clone "$git_repo_url" "$base_dir/scm" --bare | |
else | |
# STEP 6 - Getting code from remote git repo (when we have the repo and we need the latest code) | |
pr green "Git repo found" | |
pr blue "Fetching new git commits" | |
(cd "$base_dir/scm" && git fetch "$git_repo_url" "${git_deployment_branch}:${git_deployment_branch}" --force) | |
fi | |
# Step 7 - Checking out the latest code to our build directory | |
pr magenta "Checking out git repo" | |
pr blue "Using git branch '$git_deployment_branch'" | |
git clone "$base_dir/scm" . --recursive --branch "$git_deployment_branch" | |
pr blue "Using this git commit" | |
git rev-parse HEAD > .deploy_git_revision | |
git --no-pager log --format="%aN (%h):%n> %s" -n 1 | |
rm -rf .git | |
# Step 8 - Linking shared directories inside the build path | |
pr magenta "Linking common paths" | |
# Vendor/Bundle Check and link | |
pr blue "Symlinking shared paths" | |
if [ ! -d "$base_dir/shared/vendor/bundle" ]; then | |
pr red "! ERROR: not set up." | |
pr red "The directory '$base_dir/shared/vendor/bundle' does not exist on the server" | |
exit 18 | |
fi | |
pr blue "We are in directory: $(pwd)" | |
pr blue "Making vendor directory" | |
mkdir -p ./vendor | |
pr blue "Removing bundle inside vendor directory" | |
rm -rf "./vendor/bundle" | |
# Step 8.1 - linking the gem bundle directory | |
pr blue "Linking $base_dir/shared/vendor/bundle to $(pwd)/vendor/bundle" | |
ln -s "$base_dir/shared/vendor/bundle" "./vendor/bundle" | |
# Step 8.2 - linking the log directory | |
if [ ! -d "$base_dir/shared/log" ]; then | |
pr red "! ERROR: not set up." | |
pr red "The directory '$base_dir/shared/log' does not exist on the server" | |
exit 18 | |
fi | |
mkdir -p . | |
rm -rf "./log" | |
pr blue "Linking $base_dir/shared/log/ to $(pwd)/log" | |
ln -s "$base_dir/shared/log" "./log" | |
# Step 8.3 - linking the cache directory | |
if [ ! -d "$base_dir/shared/tmp/cache" ]; then | |
echo "! ERROR: not set up." | |
echo "The directory '$base_dir/shared/tmp/cache' does not exist on the server" | |
exit 18 | |
fi | |
mkdir -p ./tmp | |
rm -rf "./tmp/cache" | |
pr blue "Linking $base_dir/shared/tmp/cache to $(pwd)/tmp/cache" | |
ln -s "$base_dir/shared/tmp/cache" "./tmp/cache" | |
# Step 8.4 - linking the public assets directory | |
if [ ! -d "$base_dir/shared/public/assets" ]; then | |
echo "! ERROR: not set up." | |
echo "The directory '$base_dir/shared/public/assets' does not exist on the server" | |
exit 18 | |
fi | |
mkdir -p ./public | |
rm -rf "./public/assets" | |
pr blue "Linking $base_dir/shared/public/assets to $(pwd)/public/assets" | |
ln -s "$base_dir/shared/public/assets" "./public/assets" | |
# Step 9 - Installing Gems | |
pr magenta "Installing gem dependencies using Bundler" | |
bundle install --without development test --path "vendor/bundle" --deployment | |
# Step 10 - Copying custom files | |
pr magenta "Copying the main local_env.yml file to the deployment" | |
cp $base_dir/shared/config/local_env.yml ./config/local_env.yml | |
# Step 11 - Migrating your database to the latest version | |
if diff -qrN "$base_dir/current/db/migrate" "./db/migrate" 2>/dev/null | |
then | |
pr blue "DB migrations unchanged; skipping DB migration" | |
else | |
pr magenta "Migrating database" | |
RAILS_ENV="production" bundle exec rails db:migrate | |
fi | |
# Step 12 - Precompilation of assets | |
if [ ! "$api_only_app" = true ] ; then | |
if diff -qrN "$base_dir/current/vendor/assets/" "./vendor/assets/" 2>/dev/null && diff -qrN "$base_dir/current/app/assets/" "./app/assets/" 2>/dev/null | |
then | |
pr blue "Skipping asset precompilation" | |
else | |
pr blue "Precompiling asset files" | |
RAILS_ENV="production" bundle exec rake assets:precompile | |
fi | |
fi | |
# Step 13 - Cleanup of old releases | |
pr blue "Cleaning up old releases (keeping 5)" | |
(cd $base_dir/releases && count=$(ls -A1 | sort -rn | wc -l) && remove=$((count > 5 ? count - 5 : 0)) && ls -A1 | sort -rn | tail -n $remove | xargs rm -rf {} && cd -) | |
pr blue "Deploy finished" | |
# Step 14 - Moving the build to releases directory | |
pr magenta "Moving build" | |
cd "$base_dir" | |
pr default "We are in $(pwd)" | |
pr default "Moving $build_path to $release_path" | |
mv "$build_path" "$release_path" | |
pr blue "Changing directory to the new release path ( $release_path )" | |
cd "$release_path" | |
pr default "Currently in directory (result of pwd): $(pwd -LP)" | |
# Step 15 - Marking this release as the current release | |
pr magenta "Running Launch Scripts" | |
pr blue "Updating the $base_dir/current symlink ..." | |
ln -nfs "$release_path" "$base_dir/current" | |
pr green "done" | |
pr default "Changing to the current release directory ..." | |
cd "$base_dir/current" | |
pr green "done. We are in $(pwd)" | |
# Step 16 - Restarting puma | |
pr magenta "Restarting puma" | |
cd "$base_dir/current" | |
if [ ! -f $base_dir/pids/puma.pid ]; then | |
pr red "puma.pid not found in $base_dir/pids/puma.pid" | |
pr blue "Attempting to START Puma ..." | |
bundle exec pumactl -F "$base_dir/shared/config/puma_production_config.rb" start | |
pr green "done" | |
else | |
pumapid=$(cat $base_dir/pids/puma.pid) | |
pr blue "puma pid found: '$pumapid'" | |
pr blue "Attempting to restart puma ..." | |
bundle exec pumactl -F "$base_dir/shared/config/puma_production_config.rb" restart | |
pr green "done." | |
fi | |
pr green "done. Puma restarted!" | |
pumapid=$(cat $base_dir/pids/puma.pid) | |
pr blue "New Puma PID: $pumapid" | |
# Step 17 - Restarting Sidekiq | |
if [ ! "$restart_sidekiq" = true ] ; then | |
pr magenta "Attempting to restart sidekiq" | |
if [ ! -f $base_dir/pids/sidekiq.pid ]; then | |
pr red "sidekiq.pid not found in $base_dir/pids/sidekiq.pid" | |
else | |
sidekiqpid=$(cat $base_dir/pids/sidekiq.pid) | |
pr blue "sidekiq pid found: '$sidekiqpid'" | |
pr blue "Attempting to restart sidekiq ..." | |
bundle exec sidekiqctl stop $base_dir/pids/sidekiq.pid 11 | |
pr blue "Kill command sent to sidekiqctl" | |
pr blue "Waiting for 15 seconds to ensure sidekiq stops" | |
sleep 15 | |
fi | |
pr blue "Attempting to START Sidekiq ..." | |
bundle exec sidekiq -d -e production -C "$base_dir/shared/config/sidekiq_config.yml" | |
pr green "done" | |
sidekiqpid=$(cat $base_dir/pids/sidekiq.pid) | |
pr blue "New sidekiq PID: $sidekiqpid" | |
fi | |
# Step 18 - Remove the deploy.lock file and report | |
pr magenta "Removing deploy.lock file" | |
cd $base_dir | |
rm deploy.lock | |
pr green "done" | |
pr magenta "Deployment Complete! :-)" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment