Skip to content

Instantly share code, notes, and snippets.

@vaibhav-kaushal
Last active February 16, 2019 13:14
Show Gist options
  • Save vaibhav-kaushal/bb5976b15d486345fe52a38ce5a5f5f4 to your computer and use it in GitHub Desktop.
Save vaibhav-kaushal/bb5976b15d486345fe52a38ce5a5f5f4 to your computer and use it in GitHub Desktop.
The Ruby on Rails Deployment Script
#!/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