Skip to content

Instantly share code, notes, and snippets.

@chapterjason
Created October 31, 2023 11:16
Show Gist options
  • Save chapterjason/4fffc97203fd0913e2918c13f714f719 to your computer and use it in GitHub Desktop.
Save chapterjason/4fffc97203fd0913e2918c13f714f719 to your computer and use it in GitHub Desktop.
Zero Deployment Time Script
#!/usr/bin/env bash
set -euo pipefail
# Base
USER_DIRECTORY="$(cd ~ && pwd)"
ROOT_DIRECTORY="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
LOCK_FILE="${ROOT_DIRECTORY}/.deploy.lock"
NOW=$(date -u +"%Y%m%d%H%M%S")
LOG_FILE="${ROOT_DIRECTORY}/.deploy.${NOW}.log"
# Folder Configuration
SHARED_DIRECTORY="${ROOT_DIRECTORY}/shared"
DEPLOYMENT_DIRECTORY="${ROOT_DIRECTORY}/deployment"
RELEASE_DIRECTORY="${ROOT_DIRECTORY}/release"
CURRENT_RELEASE_DIRECTORY="${RELEASE_DIRECTORY}/${NOW}"
CURRENT_DIRECTORY="${ROOT_DIRECTORY}/current"
# Release Configuration
SHARED_FILES=".env.local.php"
SHARED_DIRECTORIES="var/log
etc/daemons"
WRITEABLE_DIRECTORIES="var/cache
var/log"
EXECUTABLE_FILES="bin/console"
KEEP_RELEASES=5
# Variables
PHP_BIN="${USER_DIRECTORY}/.phpenv/shims/php"
COMPOSER_BIN="${USER_DIRECTORY}/.phpenv/shims/composer"
CONSOLE_BIN="${CURRENT_RELEASE_DIRECTORY}/bin/console"
function after_release() {
if [ -f "${LOG_FILE}" ] && [ -d "${CURRENT_RELEASE_DIRECTORY}" ]; then
mv "${LOG_FILE}" "${CURRENT_RELEASE_DIRECTORY}"
fi
}
function __error_handler() {
EXIT_CODE=$?
if [ "${EXIT_CODE}" -ne 0 ]; then
echo "ERROR: Deployment failed. See log file '${LOG_FILE}' for details."
fi
if [ -f "${LOCK_FILE}" ]; then
rm "${LOCK_FILE}"
fi
if [ -d "${CURRENT_RELEASE_DIRECTORY}" ]; then
rm -rf "${CURRENT_RELEASE_DIRECTORY}"
fi
exit "${EXIT_CODE}"
}
function log() {
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ") - $1" | tee -a "${LOG_FILE}"
}
function exec_console() {
"$PHP_BIN" "${CONSOLE_BIN}" "$@"
}
# register trap on error
trap __error_handler ERR INT TERM EXIT
log "Starting deployment"
log "PHP: ${PHP_BIN}"
log "Composer: ${COMPOSER_BIN}"
log "Console: ${CONSOLE_BIN}"
# lock deployment
if [ -f "${LOCK_FILE}" ]; then
echo "ERROR: Deployment is locked. Please remove the lock file '${LOCK_FILE}' to continue."
exit 1
fi
touch "${LOCK_FILE}"
# create release directory
if [ -d "${CURRENT_RELEASE_DIRECTORY}" ]; then
log "ERROR: Release directory '${CURRENT_RELEASE_DIRECTORY}' already exists."
exit 1
fi
if [ ! -d "${RELEASE_DIRECTORY}" ]; then
log "Creating release directory '${RELEASE_DIRECTORY}'"
mkdir -p "${RELEASE_DIRECTORY}"
fi
log "Creating release directory '${CURRENT_RELEASE_DIRECTORY}'"
mv "${DEPLOYMENT_DIRECTORY}" "${CURRENT_RELEASE_DIRECTORY}"
mkdir "${DEPLOYMENT_DIRECTORY}"
# add symlinks of shared files/dirs
for FILE in ${SHARED_FILES}; do
SOURCE_FILE="${SHARED_DIRECTORY}/${FILE}"
TARGET_FILE="${CURRENT_RELEASE_DIRECTORY}/${FILE}"
SOURCE_DIRECTORY="$(dirname "${SOURCE_FILE}")"
TARGET_DIRECTORY="$(dirname "${TARGET_FILE}")"
if [ ! -f "${SOURCE_FILE}" ]; then
log "WARNING: Shared file '${SOURCE_FILE}' does not exist. Creating empty file."
mkdir -p "${SOURCE_DIRECTORY}"
touch "${SOURCE_FILE}"
fi
if [ -f "${TARGET_FILE}" ]; then
log "WARNING: Removing existing file '${TARGET_FILE}'"
rm "${TARGET_FILE}"
fi
if [ ! -d "${TARGET_DIRECTORY}" ]; then
log "WARNING: Target directory '${TARGET_DIRECTORY}' does not exist. Creating empty directory."
mkdir -p "${TARGET_DIRECTORY}"
fi
log "Symlinking file '${SOURCE_FILE}' to '${TARGET_FILE}'"
ln -s "${SOURCE_FILE}" "${TARGET_FILE}"
done
for DIRECTORY in ${SHARED_DIRECTORIES}; do
SOURCE_DIRECTORY="${SHARED_DIRECTORY}/${DIRECTORY}"
TARGET_DIRECTORY="${CURRENT_RELEASE_DIRECTORY}/${DIRECTORY}"
TARGET_PARENT_DIRECTORY="$(dirname "${TARGET_DIRECTORY}")"
if [ ! -d "${SOURCE_DIRECTORY}" ]; then
log "WARNING: Shared directory '${SOURCE_DIRECTORY}' does not exist. Creating empty directory."
mkdir -p "${SOURCE_DIRECTORY}"
fi
if [ -d "${TARGET_DIRECTORY}" ]; then
log "WARNING: Removing existing directory '${TARGET_DIRECTORY}'"
rm -rf "${TARGET_DIRECTORY}"
fi
if [ ! -d "${TARGET_PARENT_DIRECTORY}" ]; then
log "WARNING: Target parent directory '${TARGET_PARENT_DIRECTORY}' does not exist. Creating empty directory."
mkdir -p "${TARGET_PARENT_DIRECTORY}"
fi
log "Symlinking directory '${SOURCE_DIRECTORY}' to '${TARGET_DIRECTORY}'"
ln -s "${SOURCE_DIRECTORY}" "${TARGET_DIRECTORY}"
done
# make dirs writeable
for DIRECTORY in ${WRITEABLE_DIRECTORIES}; do
TARGET_DIRECTORY="${CURRENT_RELEASE_DIRECTORY}/${DIRECTORY}"
if [ ! -d "${TARGET_DIRECTORY}" ]; then
log "WARNING: Writeable directory '${TARGET_DIRECTORY}' does not exist. Creating empty directory."
mkdir -p "${TARGET_DIRECTORY}"
fi
log "Making directory '${TARGET_DIRECTORY}' writeable"
chmod -R 777 "${TARGET_DIRECTORY}"
done
# make files executable
for FILE in ${EXECUTABLE_FILES}; do
TARGET_FILE="${CURRENT_RELEASE_DIRECTORY}/${FILE}"
if [ ! -f "${TARGET_FILE}" ]; then
log "WARNING: Executable file '${TARGET_FILE}' does not exist."
exit 1
fi
log "Making file '${TARGET_FILE}' executable"
chmod +x "${TARGET_FILE}"
done
log "Installing vendors"
"${COMPOSER_BIN}" install --no-interaction --no-dev --optimize-autoloader
log "Clearing cache"
exec_console cache:clear --no-interaction --no-warmup --env=prod
log "Warming up cache"
exec_console cache:warmup --no-interaction --env=prod
log "Installing assets"
exec_console assets:install --no-interaction --env=prod
log "Migrating database"
exec_console doctrine:migrations:migrate --allow-no-migration --all-or-nothing --no-interaction --env=prod
log "Symlinking release '${CURRENT_RELEASE_DIRECTORY}' to '${CURRENT_DIRECTORY}'"
if [ -L "${CURRENT_DIRECTORY}" ] || [ -d "${CURRENT_DIRECTORY}" ]; then
log "Removing existing symlink '${CURRENT_DIRECTORY}'"
rm "${CURRENT_DIRECTORY}"
fi
ln -s "${CURRENT_RELEASE_DIRECTORY}" "${CURRENT_DIRECTORY}"
log "Reloading workers"
exec_console worker:reload --env=prod --no-interaction
log "Restarting workers"
exec_console messenger:stop-workers --env=prod --no-interaction
# unlock deployment
log "Removing lock file '${LOCK_FILE}'"
rm "${LOCK_FILE}"
# cleanup (remove old release (keep settings))
RELEASES="$(ls -dt "${RELEASE_DIRECTORY}/"*)"
RELEASES="$(echo "${RELEASES}" | tail -n +$((KEEP_RELEASES + 1)))"
for RELEASE in ${RELEASES}; do
log "Removing old release '${RELEASE}'"
rm -rf "${RELEASE}"
done
log "Deployment finished"
after_release
@chapterjason
Copy link
Author

Just realized I should add a tracking if the current folder is already changed before removing current release in the trap.
I don't think that anything after that will cause an error, but who knows?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment