#!/bin/bash set -euo pipefail # ~SCRIPT~ deploy.sh # * Copies a build to the specified target and performs updates. # * The build should already be prepared elsewhere. # * Run from project root folder. # * Will create database dumps (using drush) # * By default, runs all related drush commands. ####################################### # Using deploy.sh ####################################### # ~REQUIRED~ # SSH_TARGET: # Server to push code to/run drush commands on (host aliases work if configured in $HOME/.ssh/config) # DEPLOY_ENV: # A label for the environment, used to name backup files, and select settings file # Defaults to ${CI_ENVIRONMENT_NAME}, if available, which is automatically set by gitlab-ci # DEPLOY_ROOT: # Directory name for composer file, containing vendor+config sub-folders # ~OPTIONAL~ # DRUPAL_DIR: # Defaults to web # BACKUP_DIR: # Defaults to ~/backups # BUILD_ROOT: # Defaults to ${PWD} # DRUSH_DISABLE: # Set to "1" to disable ALL drush commands. # May be needed if an install failed or is not already present. # REMOTE_DRUSH: # Defaults to ${DEPLOY_ROOT}/vendor/bin/drush # UPDATE_SCRIPT: # The post-deploy update routine, specified from perspective of the project root. # This should know how to find drush or can use $1 for it (see update.sh). # Defaults to scripts/update.sh # UPDATE_DISABLE: # Set to "1" to disable only update commands (e.g. drush updb) # Note: system will be left in maintenance mode! # drush state:set system.maintenance_mode FALSE # DEBUG: # Set to "1" to call show-vars.sh # WEB_EXCLUDES: # --exclude .well-known --exclude sites/default/files --exclude sites/default/settings.*.php" ####################################### # Load env vars, if supplied ####################################### [ $# -eq 1 ] && source "env-${1}" ####################################### # DEFAULTS ####################################### _backup_dir="~/backups" _config_dir="config" # Folder under deploy root with config _remote_drush="${DEPLOY_ROOT:-}/vendor/bin/drush" # Full remote path to drush used by ssh _update_script="scripts/update.sh" _web_dir="web" # Under deploy root, the Drupal public web directory _web_excludes='--exclude "/.well-known" --exclude "/sites/default/files" --exclude "/sites/default/settings.*.php' # Note: `_php_settings_dir` cannot be included here; Default directly in optionals block ####################################### # Check for required variables: ####################################### [ -z "${DEPLOY_ENV:-}" ] && DEPLOY_ENV="${CI_ENVIRONMENT_NAME:-}" [ -z "${DEPLOY_ENV:-}" ] && { echo 'Missing ENV var: CI_ENVIRONMENT_NAME/DEPLOY_ENV'; exit 1; } [ -z "${DEPLOY_ROOT:-}" ] && { echo "Missing ENV var: DEPLOY_ROOT"; exit 1; } [ -z "${SSH_TARGET:-}" ] && { echo "Missing ENV var: SSH_TARGET"; exit 1; } ####################################### # Optional variable setup from defaults: ####################################### [ -z "${BACKUP_DIR:-}" ] && BACKUP_DIR="${_backup_dir}" [ -z "${BUILD_ROOT:-}" ] && BUILD_ROOT="./" [ -z "${CONFIG_DIR:-}" ] && CONFIG_DIR="${_config_dir}" # NO trailing slash! [ -z "${DRUPAL_DIR:-}" ] && DRUPAL_DIR="${_web_dir}" # Name of Drupal public sub-folder [ -z "${REMOTE_DRUSH:-}" ] && REMOTE_DRUSH="${_remote_drush}" # Full path to remote drush [ -z "${UPDATE_SCRIPT:-}" ] && UPDATE_SCRIPT="${_update_script}" [ -z "${WEB_EXCLUDES:-}" ] && WEB_EXCLUDES="${_web_excludes}" [ -z "${DRUSH_DISABLE:-}" ] && DRUSH_DISABLE="0" [ -z "${UPDATE_DISABLE:-}" ] && UPDATE_DISABLE="0" [ -z "${PHP_SETTINGS_DIR:-}" ] && PHP_SETTINGS_DIR="${DRUPAL_DIR}/sites/default" # Be sure to not overwrite any server-only .user.ini if [ ! -f ${BUILD_ROOT}/${DRUPAL_DIR}/.user.ini ]; then WEB_EXCLUDES="--exclude .user.ini ${WEB_EXCLUDES}" fi if [ "${DEBUG:-0}" == "1" ]; then #./scripts/show-vars.sh # per-env echo "[DEBUG] IN-USE SETTINGS:" echo "SSH_TARGET=$SSH_TARGET" echo "DEPLOY_ENV=${DEPLOY_ENV}" echo "DEPLOY_ROOT=${DEPLOY_ROOT}" echo "BUILD_ROOT=${BUILD_ROOT}" echo "CONFIG_DIR=${CONFIG_DIR}" echo "DRUPAL_DIR=${DRUPAL_DIR}" echo "REMOTE_DRUSH=${REMOTE_DRUSH}" echo "BACKUP_DIR=${BACKUP_DIR}" echo "DRUSH_DISABLE=${DRUSH_DISABLE}" echo "UPDATE_SCRIPT=${UPDATE_SCRIPT}" echo "UPDATE_DISABLE=${UPDATE_DISABLE}" echo "WEB_EXCLUDES=${WEB_EXCLUDES}" exit 0 fi ####################################### # Functions ####################################### function drush_enabled() { if [ "${DRUSH_DISABLE}" == "1" ] then return 1 #1 is false (so, disabled) else return 0 #0 is true (enabled) because...bash fi } ####################################### # Check drush/site status ####################################### echo "Starting deployment to ${DEPLOY_ENV}" drush_enabled || echo "Note: Drush commands are disabled. Commands marked [D] will not execute." echo "Verify connectivity (print user & working directory)" ssh ${SSH_TARGET} "whoami; pwd" echo "[D] Verify drush functionality (cr, status bootstrap)" drush_enabled && ssh ${SSH_TARGET} "${REMOTE_DRUSH} cr" # Expected good result: "Drupal bootstrap : Successful", otherwise empty (but drush returns 0) ssh_cmd="${REMOTE_DRUSH} status --fields=bootstrap" drush_enabled && { [[ $(ssh ${SSH_TARGET} "${ssh_cmd}") = *Successful* ]] || { echo ">> Drush failed bootstrap!"; exit 1; } ; } ####################################### # Enable maintenance mode ####################################### # echo "[D] Set maintenance mode ON" # drush_enabled && ssh ${SSH_TARGET} "${REMOTE_DRUSH} state:set system.maintenance_mode TRUE" ####################################### # Perform a database backup ####################################### backup_file="${DEPLOY_ENV}_$(date +%Y%m%dT%H%M%S).sql.gz" ssh_cmd="[ ! -d ${BACKUP_DIR} ] && mkdir -p ${BACKUP_DIR} || true" echo "Using backup dir '${BACKUP_DIR}' (created as needed)" ssh "${SSH_TARGET}" "$ssh_cmd" ssh_cmd="${REMOTE_DRUSH} sql-dump | gzip > ${BACKUP_DIR}/$backup_file" echo "[D] Creating backup file ${backup_file}" drush_enabled && ssh "${SSH_TARGET}" "${ssh_cmd}" ####################################### # DEPLOY CODE: ####################################### # Check for sub-dirs... ssh_cmd="[ ! -d ${DEPLOY_ROOT}/${CONFIG_DIR} ] && mkdir -p ${DEPLOY_ROOT}/${CONFIG_DIR} || true" echo "Ensure '${CONFIG_DIR}' exists" ssh "${SSH_TARGET}" "$ssh_cmd" ssh_cmd="[ ! -d ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default ] && mkdir -p ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default || true" echo "Ensure '${DRUPAL_DIR}/sites/default exists'" ssh "${SSH_TARGET}" "$ssh_cmd" # Disable write protect... echo "Disabling write protection on settings folder and files." ssh_cmd="chmod ug+w ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default/" ssh ${SSH_TARGET} ${ssh_cmd} ssh_cmd="find ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default/ -maxdepth 1 -type f -name 'settings*php' -exec chmod ug+w '{}' \;" ssh ${SSH_TARGET} ${ssh_cmd} # Rsync's printf "Pushing latest codebase: config…" rsync -rz --delete ${BUILD_ROOT}/${CONFIG_DIR}/ ${SSH_TARGET}:${DEPLOY_ROOT}/config printf "\b ✓, ${DRUPAL_DIR}…" rsync -rz --delete ${WEB_EXCLUDES} ${BUILD_ROOT}/${DRUPAL_DIR}/ ${SSH_TARGET}:${DEPLOY_ROOT}/${DRUPAL_DIR} printf "\b ✓, vendor…" rsync -rz --delete --links ${BUILD_ROOT}/vendor ${SSH_TARGET}:${DEPLOY_ROOT}/ printf "\b ✓, scripts…" [ -d ./scripts ] && rsync -rz --delete ${BUILD_ROOT}/scripts ${SSH_TARGET}:${DEPLOY_ROOT}/ printf "\b ✓, composer…" scp -q composer.json ${SSH_TARGET}:${DEPLOY_ROOT}/composer.json printf "\b ✓, settings…" scp -q ${BUILD_ROOT}/${PHP_SETTINGS_DIR}/settings.${DEPLOY_ENV}.php ${SSH_TARGET}:${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default/settings.local.php printf "\b ✓\nFile sync complete!\n" # Re-protect settings echo "Enabling write protection on settings folder and files." ssh_cmd="[ -d ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default ] && chmod ug-w ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default/" ssh ${SSH_TARGET} ${ssh_cmd} ssh_cmd="find ${DEPLOY_ROOT}/${DRUPAL_DIR}/sites/default/ -maxdepth 1 -type f -name 'settings*php' -exec chmod ug-w '{}' \;" ssh ${SSH_TARGET} ${ssh_cmd} ####################################### # [P|re]load database ####################################### if [ ! -z "${PRELOAD_DB_FILE:-}" -a "${DEPLOY_ENV}" != "live" -a "${DEPLOY_ENV}" != "prod" ]; then ssh_cmd="${REMOTE_DRUSH} sql-drop -y" echo "[D] Dropping existing database" drush_enabled && ssh "${SSH_TARGET}" "${ssh_cmd}" if drush_enabled; then ssh_cmd="${REMOTE_DRUSH} sqlc < ${PRELOAD_DB_FILE}" echo "[D] Loading database from ${PRELOAD_DB_FILE}" ssh "${SSH_TARGET}" "${ssh_cmd}" else echo "Fallback: Load database file using mysql (requires .my.cnf)" ssh_cmd="mysql < ${PRELOAD_DB_FILE}" echo "Loading database from ${PRELOAD_DB_FILE}" ssh "${SSH_TARGET}" "${ssh_cmd}" fi fi ####################################### # RUN DRUPAL TASKS (via UPDATE_SCRIPT): ####################################### if drush_enabled && [ "${UPDATE_DISABLE}" == "0" -a -f "${BUILD_ROOT}/${UPDATE_SCRIPT}" ]; then echo ">> Calling ${UPDATE_SCRIPT} to perform updates..." ssh ${SSH_TARGET} "${DEPLOY_ROOT}/${UPDATE_SCRIPT} ${REMOTE_DRUSH}" # echo ">> [D] Set maintenance mode OFF" # ssh ${SSH_TARGET} ${REMOTE_DRUSH} state:set system.maintenance_mode FALSE else echo ">> [D] Skipping follow-up drush (cr, updb, cim, etc)." # echo ">> Maintenence mode on. To disable: ${drush} state:set system.maintenance_mode FALSE" fi echo "Deployment to ${DEPLOY_ENV} completed!"