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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions