Skip to content

Ideas from Bart #14

@retlehs

Description

@retlehs

@bartkleinreesink shared their sync script modifications that include making the script executable from any directory, as well as separating the configuration so that it's entirely in the WP-CLI config

A while ago I bought your sync script to try it out and I’m trying to make it a little more easy to use. Thought I’d give you an update on what I have.

Added the script to my PATH variable to make it executable from anywhere and made it check if wp-cli.yml is present in the current working directory. I’ve also made it parse the YAML file to be able to configurate the entire script from wp-cli.yml to decouple the script from the project.

#!/bin/bash

##########################################################################################################
##
##     Syncing WordPress environments with WP-CLI aliases
##     Copyright (c) Ben Word
##
##########################################################################################################
##
##     Put this file somewhere on your computer, for instance, in your home folder in a directory.
##     Add this directory to your PATH variable so you can execute it from anywhere.
##     Make sure this file is executable: chmod +x wp-sync
##
##     Run wp-sync in a folder where wp-cli.yml is present. Typically the project root.
##
##     Usage: wp-sync [[--skip-db] [--skip-assets] [--remote]] [ENV_FROM] [ENV_TO]
## 
##     Note: WP-CLI is required on development, testing, acceptance and production environments for
##     this script to work.
##
##
##########################################################################################################
##
##     -------------  No need to touch things below this line  -------------
##
##########################################################################################################

CONFIG=./wp-cli.yml

if [ ! -f "$CONFIG" ]; then
    echo "Make sure wp-cli.yml is present in current working directory"
    exit;
fi

check_vars() {
    var_names=("$@")
    for var_name in "${var_names[@]}"; do
        [ -z "${!var_name}" ] && echo "$var_name is unset." && var_unset=true
    done
    [ -n "$var_unset" ] && exit 1
    return 0
}

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]}}
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
      }
   }'
}

# Parse wp-cli.yml in current working directory 
eval $(parse_yaml ${CONFIG})

check_vars remote_user__production remote_host__production env_url__production remote_user__testing remote_host__testing env_url__testing remote_user__acceptance remote_host__acceptance env_url__acceptance uploads_dir__remote uploads_dir__local env_url__development

PRODDIR="${remote_user__production}@${remote_host__production}:${uploads_dir__remote}"
PRODSITE=${env_url__production}

ACCDIR="${remote_user__acceptance}@${remote_host__acceptance}:${uploads_dir__remote}"
ACCSITE=${env_url__acceptance}

TESTDIR="${remote_user__testing}@${remote_host__testing}:${uploads_dir__remote}"
TESTSITE=${env_url__testing}

DEVDIR=${uploads_dir__local}
DEVSITE=${env_url__development}

LOCAL=true
SKIP_DB=false
SKIP_ASSETS=false
POSITIONAL_ARGS=()

CYAN='\033[1;36m'
RED='\033[1;31m';
NC='\033[0m'


while [[ $# -gt 0 ]]; do
  case $1 in
    --skip-db)
      SKIP_DB=true
      shift
      ;;
    --skip-assets)
      SKIP_ASSETS=true
      shift
      ;;
    --remote)
      LOCAL=false
      shift
      ;;
    --*)
      echo "Unknown option $1"
      exit 1
      ;;
    *)
      POSITIONAL_ARGS+=("$1")
      shift
      ;;
  esac
done

set -- "${POSITIONAL_ARGS[@]}"

if [ $# != 2 ]
then
  echo "Usage: $0 [[--skip-db] [--skip-assets] [--remote]] [ENV_FROM] [ENV_TO]"
  exit;
fi

FROM=$1
TO=$2

bold=$(tput bold)
normal=$(tput sgr0)

case "$1-$2" in
  production-development)    DIR="down ⬇️ "          FROMSITE=$PRODSITE; FROMDIR=$PRODDIR; TOSITE=$DEVSITE;  TODIR=$DEVDIR; ;;
  acceptance-development)    DIR="down ⬇️ "          FROMSITE=$ACCSITE; FROMDIR=$ACCDIR; TOSITE=$DEVSITE;  TODIR=$DEVDIR; ;;
  testing-development)       DIR="down ⬇️ "          FROMSITE=$TESTSITE; FROMDIR=$TESTDIR; TOSITE=$DEVSITE;  TODIR=$DEVDIR; ;;
  development-production)    DIR="up ⬆️ "            FROMSITE=$DEVSITE;  FROMDIR=$DEVDIR;  TOSITE=$PRODSITE; TODIR=$PRODDIR; ;;
  development-acceptance)    DIR="up ⬆️ "            FROMSITE=$DEVSITE;  FROMDIR=$DEVDIR;  TOSITE=$ACCSITE; TODIR=$ACCDIR; ;;
  development-testing)       DIR="up ⬆️ "            FROMSITE=$DEVSITE;  FROMDIR=$DEVDIR;  TOSITE=$TESTSITE; TODIR=$TESTDIR; ;;
  production-acceptance)     DIR="horizontally ↔️ ";  FROMSITE=$PRODSITE; FROMDIR=$PRODDIR; TOSITE=$ACCSITE; TODIR=$ACCDIR; ;;
  acceptance-production)     DIR="horizontally ↔️ ";  FROMSITE=$ACCSITE; FROMDIR=$ACCDIR; TOSITE=$PRODSITE; TODIR=$PRODDIR; ;;
  acceptance-testing)        DIR="horizontally ↔️ ";  FROMSITE=$ACCSITE; FROMDIR=$ACCDIR; TOSITE=$TESTSITE; TODIR=$TESTDIR; ;;
  testing-acceptance)        DIR="horizontally ↔️ ";  FROMSITE=$TESTSITE; FROMDIR=$TESTDIR; TOSITE=$ACCSITE; TODIR=$ACCDIR; ;;
  *) echo "usage: $0 [[--skip-db] [--skip-assets] [--remote]] production development | acceptance development | development acceptance | development production | acceptance production | production acceptance" && exit 1 ;;
esac

if [ "$SKIP_DB" = false ]
then
  DB_MESSAGE=" - replace the ${bold}$TO${normal} database${normal} ($TOSITE) with the ${bold}$FROM${normal} database ($FROMSITE)"
fi

if [ "$SKIP_ASSETS" = false ]
then
  ASSETS_MESSAGE=" - sync ${bold}$FROMDIR${normal} from ${bold}$FROM${normal} ($FROMSITE) to ${bold}$TODIR${normal} on ${bold}$TO${normal} ($TOSITE)?"
fi

if [ "$SKIP_DB" = true ] && [ "$SKIP_ASSETS" = true ]
then
  echo "Nothing to synchronize."
  exit;
fi

echo
printf "${CYAN}Heads up! Would you really like to:${NC}\n\n"
echo $DB_MESSAGE
echo $ASSETS_MESSAGE
printf "\n\n"

if [ $TOSITE = $PRODSITE ]
then
  printf "${RED}You're about to sync to production, are you really sure?${NC}\n"
fi

read -r -p " [y/N] " response

if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
  # Change to site directory
  # Commented out because we want this sync script above the public folder
  # cd ../ &&
  echo

  # Make sure both environments are available before we continue
  availfrom() {
    local AVAILFROM

    if [[ "$LOCAL" = true && $FROM == "development" ]]; then
      AVAILFROM=$(wp option get home 2>&1)
    else
      AVAILFROM=$(wp "@$FROM" option get home 2>&1)
    fi
    if [[ $AVAILFROM == *"Error"* ]]; then
      echo $AVAILFROM
      echo "❌  Unable to connect to $FROM"
      exit 1
    else
      echo "✅  Able to connect to $FROM"
    fi
  };
  availfrom

  availto() {
    local AVAILTO
    if [[ "$LOCAL" = true && $TO == "development" ]]; then
      AVAILTO=$(wp option get home 2>&1)
    else
      AVAILTO=$(wp "@$TO" option get home 2>&1)
    fi

    if [[ $AVAILTO == *"Error"* ]]; then
      echo $AVAILTO
      echo "❌  Unable to connect to $TO $AVAILTO"
      exit 1
    else
      echo "✅  Able to connect to $TO"
    fi
  };
  availto

  if [ "$SKIP_DB" = false ]
  then
  echo "Syncing database..."
    # Export/import database, run search & replace
    if [[ "$LOCAL" = true && $TO == "development" ]]; then
      wp db export --default-character-set=utf8mb4 &&
      printf "${CYAN}Notice:${NC} Exported $TO database to working directory\n" &&
      wp db reset --yes &&
      wp "@$FROM" db export --default-character-set=utf8mb4 - | wp db import - &&
      wp search-replace "$FROMSITE" "$TOSITE" --all-tables-with-prefix
    elif [[ "$LOCAL" = true && $FROM == "development" ]]; then
      wp "@$TO" db export --default-character-set=utf8mb4 &&
      printf "${CYAN}Notice:${NC} Exported $TO database to home folder on server\n" &&
      wp "@$TO" db reset --yes &&
      wp db export --default-character-set=utf8mb4 - | wp "@$TO" db import - &&
      wp "@$TO" search-replace "$FROMSITE" "$TOSITE" --all-tables-with-prefix
    else
      wp "@$TO" db export --default-character-set=utf8mb4 &&
      printf "${CYAN}Notice:${NC} Exported $TO database to home folder on server\n" &&
      wp "@$TO" db reset --yes &&
      wp "@$FROM" db export --default-character-set=utf8mb4 - | wp "@$TO" db import - &&
      wp "@$TO" search-replace "$FROMSITE" "$TOSITE" --all-tables-with-prefix
    fi
  fi

  if [ "$SKIP_ASSETS" = false ]
  then
  echo "Syncing assets..."
    # Make uploads dir if non existent
    mkdir -p "$uploads_dir__local"
    # Sync uploads directory
    chmod -R 755 "$uploads_dir__local" &&
    if [[ $DIR == "horizontally"* ]]; then
      [[ $FROMDIR =~ ^(.*): ]] && FROMHOST=${BASH_REMATCH[1]}
      [[ $FROMDIR =~ ^(.*):(.*)$ ]] && FROMDIR=${BASH_REMATCH[2]}
      [[ $TODIR =~ ^(.*): ]] && TOHOST=${BASH_REMATCH[1]}
      [[ $TODIR =~ ^(.*):(.*)$ ]] && TODIR=${BASH_REMATCH[2]}

      ssh -o ForwardAgent=yes $FROMHOST "rsync -aze 'ssh -o StrictHostKeyChecking=no' --progress $FROMDIR $TOHOST:$TODIR"
    else
      rsync -az --progress "$FROMDIR" "$TODIR"
    fi
  fi

  # Slack notification when sync direction is up or horizontal
  # if [[ $DIR != "down"* ]]; then
  #   USER="$(git config user.name)"
  #   curl -X POST -H "Content-type: application/json" --data "{\"attachments\":[{\"fallback\": \"\",\"color\":\"#36a64f\",\"text\":\"🔄 Sync from ${FROMSITE} to ${TOSITE} by ${USER} complete \"}],\"channel\":\"#site\"}" https://hooks.slack.com/services/xx/xx/xx
  # fi
  echo -e "🔄  Sync from $FROM to $TO complete.\n\n    ${bold}$TOSITE${normal}\n"
fi

# Global parameter defaults
path: public/wp
user: admin
disabled_commands:
  - db drop

# Aliases to other WordPress installs (e.g. `wp @testing rewrite flush`)
@testing:
    ssh: ssh_user@123.123.123.123
    user: admin
    path: /home/ssh_user/domains/wptesting.com/public_html/wp
@acceptance:
    ssh: ssh_user@123.123.123.123
    user:  admin
    path: /home/ssh_user/domains/wpacceptance.com/public_html/wp
@production:
    ssh: ssh_user@123.123.123.123
    user:  admin
    path: /data/home/ssh_user/domains/wp.com/public_html/wp

# Custom keys for wp-sync script
remote_user:
    testing: ssh_user
    acceptance: ssh_user
    production: ssh_user
remote_host:
    testing: "123.123.123.123"
    acceptance: "123.123.123.123"
    production: "123.123.123.123"
env_url:
    development: "https://wpdevelopment.com"
    testing: "https://wptesting.com"
    acceptance: "https://wpacceptance.com"
    production: "https://wp.com"
uploads_dir:
    local: "public/content/uploads/"
    remote: "~/builds/shared/uploads/"

Obviously this is not perfect, since I’m repeating myself in the configuration. And ‘@’ variables aren’t properly parsed with the parse function. It could also be more cohesive, but these are just some ideas to maybe work out a finer solution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions