Skip to content

Instantly share code, notes, and snippets.

@luceos
Last active September 3, 2015 08:22
Show Gist options
  • Save luceos/4dc6b2b97c34b87cf9f3 to your computer and use it in GitHub Desktop.
Save luceos/4dc6b2b97c34b87cf9f3 to your computer and use it in GitHub Desktop.
update script for laravel 4/5 creating a backup of files and database, while asking for approval along the way
#!/bin/bash
#
# Laravel update tool
#
#
# use: ./rollout.sh
#
# date: 2015-09-01
# by D. Klabbers / HostingXS B.V.
# for: Laravel 4.* and 5.* (with modification of DATABASE_CONFIG_PATH)
# license: MIT
# disclaimer:
# Use at your own risk, run on a separate install to understand what it does.
# Restating the license; we are not liable for any damages done!
#
#######################################################################
#
# One ring to rule them all, global defines to vassalage
#
#######################################################################
# debug allows the use of writeDebug in the code, if 0 output of that function will be disabled
# this is not used anywhere unless we're debugging code
DEBUG=1
# set this to 1 to enable auto incrementing vendor package versions
UPDATE_VERSION=1
# use ./rollout.sh 5 to use this script for laravel 5
LARAVEL_VERSION=4
if [ -n "${BASH_ARGV[0]}" ] && [ ${BASH_ARGV[0]} == 5 ]; then
LARAVEL_VERSION=5
fi
# path to the database configuration file, we will parse this file to read the connection settings
# in case of laravel version 5.* use 'config/database.php'
DATABASE_CONFIG_PATH='app/config/database.php'
if [ ${LARAVEL_VERSION} == 5 ]; then
DATABASE_CONFIG_PATH='config/database.php'
fi
# the key in the database connections array from which we parse credentials
DATABASE_CONNECTION='mysql'
# specify the vendor folder, this checks for non-committed and modified files; eg: vendor/my-self
# consider to update this to the workbench directory or set to empty string to skip check
VENDOR_FOLDER=''
# the following allow checking whether uncommitted or modified files exist in the vendor folder
MATCH_GIT_UNCOMMITTED='??'
MATCH_GIT_MODIFIED='M'
# matcher for http://semver.org compatible versioning
REG_SEMVER='^[0-9]+\.[0-9]+\.[0-9]+(\-[a-z0-9\.]+)?$'
# sets current directory, so we can get back to where we came from
CURRENT_PATH=${PWD}
CURRENT_MACHINE=$(hostname)
#######################################################################
#
# Formatting funkiness favorable fathomness
#
#######################################################################
# colors to be used for displaying messages while running the script
COLOR_DEFAULT='\033[0m'
COLOR_YELLOW='\033[1;33m'
COLOR_RED='\033[0;31m'
COLOR_GREEN='\033[0;32m'
COLOR_LIGHT_GRAY='\033[0;37m'
function writeLn ()
{
local line="$1"
local color="$2"
local appendNewLines="$3"
if [ -z ${color} ]; then
color=${COLOR_DEFAULT}
fi
if [ -z ${appendNewLines} ]; then
appendNewLines=1
fi
printf "${color}${line}${COLOR_DEFAULT}"
while [ ${appendNewLines} -gt 0 ]
do
printf "\n"
appendNewLines=$[$appendNewLines-1]
done
}
function writeQuestion ()
{
writeLn "$1" ${COLOR_YELLOW}
}
function writeError ()
{
writeLn "$1" ${COLOR_RED}
}
function writeInfo ()
{
writeLn "$1" ${COLOR_LIGHT_GRAY}
}
function writeHeader ()
{
writeLn "$1" ${COLOR_GREEN}
}
function writeDebug ()
{
if [ ${DEBUG} -eq 1 ]; then
writeInfo "DEBUG: $1"
fi
}
# checks status of folder to see whether open / uncommitted changes exist
function gitStatus ()
{
PACKAGE_PATH=${PWD}
local clean=0
local tmp=$(git status --porcelain)
local line=($tmp)
if [ -z "${line[0]}" ]; then
clean=$[$clean+1]
elif [ "${line[0]}" == "${MATCH_GIT_UNCOMMITTED}" ]; then
writeError "${PACKAGE_PATH} has uncommitted files"
elif [ "${line[0]}" == "${MATCH_GIT_MODIFIED}" ]; then
writeError "${PACKAGE_PATH} has modified files"
else
writeError "${PACKAGE_PATH} unknown state ${line}"
exit 303
fi
}
#######################################################################
#
# Find as much as possible before asking stupid questions
#
#######################################################################
CURRENTUSER=$(whoami)
#######################################################################
#
# Shoot first, ask questions later
#
#######################################################################
writeHeader 'HostingXS B.V. update tool (http://www.hostingxs.nl)'
writeHeader 'This script will update a Laravel 4.* or 5.* to a new version automatically.'
# read the installation docroot
writeQuestion 'Please specify the path of the docroot or leave empty for the current directory:'
read DOCROOT
if [ -z "$DOCROOT" ]; then
DOCROOT=${PWD}
else
DOCROOT="${PWD}/$2"
fi
# load database information
tmp=$(php <<CODE
<?php
\$config=include('${DOCROOT}/${DATABASE_CONFIG_PATH}');
echo trim(implode(' ', \$config['connections']['${DATABASE_CONNECTION}']));
CODE
)
DBCONFIG=($tmp)
DB_DRIVER=${DBCONFIG[0]}
DB_HOSTNAME=${DBCONFIG[1]}
DB_DATABASE=${DBCONFIG[2]}
DB_USERNAME=${DBCONFIG[3]}
DB_PASSWORD=${DBCONFIG[4]}
cd ${DOCROOT}
# find repository remote url and name (origin)
tmp=$(git remote -v | grep fetch)
tmpArray=(${tmp})
GIT_REMOTE_NAME=${tmpArray[0]}
GIT_REMOTE_URL=${tmpArray[1]}
# load latest git tag from the remote
tmp=$(git ls-remote --tags ${GIT_REMOTE_URL} | tail -n 1)
tmpArray=(${tmp})
GIT_LATEST_TAG=${tmpArray[1]:10}
# find the current branch
tmp=$(git branch --no-color --contains HEAD)
GIT_BRANCH=${tmp:2}
cd ${CURRENT_PATH}
# read the version
if [ -z "${GIT_LATEST_TAG}" ]; then
writeQuestion 'Please specify the version to upgrade to, please adhere to http://semver.org standards:'
else
writeQuestion "Please specify the version to upgrade to, this has be a higher version than (>) ${GIT_LATEST_TAG}:"
fi
read VERSION
if [ -z "$VERSION" ]; then
writeError 'Please specify a version to upgrade to.'
exit 101
fi
if ! [[ "${VERSION}" =~ ${REG_SEMVER} ]]; then
writeError 'The specified version does not adhere to the http://semver.org standards.'
exit 102
fi
# read the user the installation runs under
writeQuestion 'Please specify the user the installation runs as or keep blank for "developer":'
read RUNSAS
if [ -z "$RUNSAS" ]; then
RUNSAS='developer'
fi
# set backup directory
BACKUP_DIRECTORY="/home/$RUNSAS/backups/$VERSION"
# set backup location for files
BACKUP_FILES="${BACKUP_DIRECTORY}/files.tar.gz"
writeQuestion "Do you want to pull the latest changes from a branch, before updating? Type 'y' for ${GIT_BRANCH}, type a branch or leave empty to skip:"
read BRANCH
if [ "${BRANCH}" != 'y' ] && ! [ -z "${BRANCH}" ]; then
GIT_BRANCH="${BRANCH}"
elif [ -z "$BRANCH" ]; then
GIT_BRANCH=""
fi
#######################################################################
#
# Check whether installation is safe to update
#
#######################################################################
# run the git status check on the docroot
cd ${DOCROOT}
gitStatus
# run the git status checks on the vendor folder [optional]
if [ -n "${VENDOR_FOLDER}" ]; then
cd ${VENDOR_FOLDER}
for package in `find . -maxdepth 1 -type d`
do
VENDOR_PATH=${PWD}
if [ ${package} != '.' ]; then
cd ${package}
gitStatus
cd ${VENDOR_PATH}
fi
done
fi
cd ${CURRENT_PATH}
#######################################################################
#
# List all gathered intel, ask green light for operation "zero day"
#
#######################################################################
writeInfo "Hello ${CURRENTUSER}, your update will concern the following:"
writeInfo " - directory:\t\t${DOCROOT}"
writeInfo " - update to version:\t${VERSION}"
writeInfo " - site user:\t\t${RUNSAS}"
writeInfo " - database:\t\t${DB_DATABASE} ${DB_USERNAME}@${DB_HOSTNAME}"
writeInfo " - backup directory:\t${BACKUP_DIRECTORY}"
if [ -n "${GIT_BRANCH}" ]; then
writeInfo " - pulling from branch:\t${GIT_BRANCH}"
else
writeInfo " - pulling from branch:\tnone"
fi
writeQuestion 'Please confirm the above information by typing the version:'
read ACCEPT
if [ ${ACCEPT} != ${VERSION} ]; then
writeError 'Process halted, confirmation failed.'
exit 666
fi
#######################################################################
#
# Put Laravel in maintenance mode
#
#######################################################################
php ${DOCROOT}/artisan down
#######################################################################
#
# Stuff goes wrong, back up the files
#
#######################################################################
# create a directory for backup
writeInfo "Creating backup of previous version in ${BACKUP_DIRECTORY}"
if [ ! -d "$BACKUP_DIRECTORY" ]; then
mkdir -p ${BACKUP_DIRECTORY}
fi
# create the backup
#tar -cz ${DOCROOT} > "${BACKUP_FILES}"
#######################################################################
#
# Stuff still goes wrong, very much so, safely backup database
#
#######################################################################
BACKUP_DB_FILE="${BACKUP_DIRECTORY}/${DB_DATABASE}.sql"
writeInfo "Creating a backup of database ${DB_DATABASE} to ${BACKUP_DB_FILE}"
if [ ${DB_DRIVER} == 'pgsql' ]; then
PGPASSWORD="${DB_PASSWORD}"
# pg_dump --format=p --clean --no-owner --host=${DB_HOSTNAME} --username=${DB_USERNAME} ${DB_DATABASE} > ${BACKUP_DB_FILE}
elif [ ${DB_DRIVER} == 'mysql' ]; then
mysqldump --quote-names --user=${DB_USERNAME} --password=${DB_PASSWORD} --insert-ignore --skip-comments --add-drop-database ${DB_DATABASE} > ${BACKUP_DB_FILE}
else
writeError "${DB_DRIVER} is not supported"
exit 401
fi
#######################################################################
#
# Check backup before updating
#
#######################################################################
if [ -f ${BACKUP_DB_FILE} ] && [ -f ${BACKUP_FILES} ]; then
writeInfo "Backups created succesfully, updating the application."
else
writeError "Either database dump or files backup hasn't been created."
fi
#######################################################################
#
# Start upgrade procedure
#
#######################################################################
cd ${DOCROOT}
# let's do a dryrun and then run a pull
if [ -n "${GIT_BRANCH}" ]; then
git fetch ${GIT_REMOTE_NAME} ${GIT_BRANCH}
git diff HEAD..${GIT_REMOTE_NAME}/${GIT_BRANCH}
writeQuestion "The above changes will be applied, are you sure? [y/n/s]"
read AGREE
if [ -z "${AGREE}" ] || [ ${AGREE} == 'n' ]; then
writeError "Halting updating."
exit 104
elif [ ${AGREE} == 'y' ]; then
git merge ${GIT_REMOTE_NAME}/${GIT_BRANCH}
elif [ ${AGREE} == 's' ]; then
writeInfo "Skipping merge/pull of ${GIT_REMOTE_NAME}/${GIT_BRANCH}"
fi
fi
# check the updates that will be run
composer update --dry-run --no-scripts
writeQuestion "Are you sure the above dependancies can be safely deployed? [y/n]"
read AGREE
if [ -z "${AGREE}" ] || [ ${AGREE} == 'n' ]; then
writeError "Halting updating."
exit 105
elif [ ${AGREE} == 'y' ]; then
# update the actual installation
composer update
if [ ${UPDATE_VERSION} == 1 ]; then
# set a new tag
git tag -a "${VERSION}" -m "${CURRENTUSER}@${CURRENT_MACHINE}: increment to ${VERSION} from ${GIT_LATEST_TAG}"
# commit this tag
git push --tags
fi
fi
#######################################################################
#
# Remove Laravel from maintenance mode
#
#######################################################################
php ${DOCROOT}/artisan up
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment