Shell script for updating production sites

Hi everyone!

I made a bash script for updating all of my production sites. Maybe you guys will find it useful.

Most importantly though, I’d love to get feedback on how to improve it and/or any downsides you foresee to using this method.

FYI: my clients don’t have the requirement of their sites having 99% uptime: ie. it’s ok for their site to go down for a day (not often) if it saves me time on workflow

Usage

Update all production sites: ./update-prod.sh -a
Update specific sites: ./update-prod.sh -s site1 site2 site3

Notes

The usage for site1 depends heavily on your folder names. All my sites for this example are .com. The parameters site1 site2 etc. are all the root domain, minus the suffix. My folder structure is consistent with this. For example:

  • Project folder: ~/Sites/Development/DO-example
  • Bedrock folder: ~/Sites/Development/DO-example/example
  • URL: https://example.com

To work in all scenarios, you would need to pass in the full domain name and appropriately name your project folder and bedrock folder. For this you would adjust the script slightly. For example:

  • Project folder: ~/Sites/Development/DO-example.com
  • Bedrock folder: ~/Sites/Development/DO-example.com/example.com
  • URL: https://example.com

If I have a staging site, I add an if statement inside the update script. For example:

if [[ "$SITE" = "example" ]]; then
    ./bin/deploy.sh production staging."$SITE".com
else
    ./bin/deploy.sh production "$SITE".com
fi

Full Script

#!/bin/bash

# Update all sites
update_all () {
  read -r -p "Would you really like to update all production sites? [y/N] " response

  if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then

  update_list site1 site2 site3

  fi
}

## Update list of sites
update_list () {
  for SITE in "$@"
  do
    cd ~/Sites/Development/DO-"$SITE"/"$SITE" &&
    composer update &&
    git commit composer.lock -m "wordpress updates" &&
    git push
    cd ../trellis &&
	./bin/deploy.sh production "$SITE".com
	
	### Open site in browser to make sure nothing is broken
	open https://"$SITE".com
  done
}

## Loop through script arguments
unset -v sub
while getopts "has:" OPTION
  do
    case $OPTION in
      a)
        update_all
        echo "All sites finished updating"
        exit
        ;;
      s)
        sub=("$OPTARG")
        until [[ $(eval "echo \${$OPTIND}") =~ ^-.* ]] || [ -z $(eval "echo \${$OPTIND}") ]; do
            sub+=($(eval "echo \${$OPTIND}"))
            OPTIND=$((OPTIND + 1))
        done
        update_list ${sub[@]}
        exit
        ;;
      h)
        echo "-a : updates all sites"
        echo "-s : update selected sites (usage: -s site1 site2)"
        exit
        ;;
      \?)
        echo "Use the -h flag for the help menu"
        exit
        ;;
  esac
done
5 Likes

This is cool, thanks for sharing!

Do you have staging servers? I’d be interested in using this (on a daily cron) to automatically update minor versions of plugins and WP on my staging branches & servers. Once verified that things are all :ok_hand: on staging, I’d merge the changes into master.

Nowadays I’ve got CircleCI (free) handling the deploys for my Trellis sites, so commits to staging trigger a deploy to staging, and then commits to master trigger a deploy for production.

1 Like

I really need to work in branches. Do you have an outline of your branch workflow anywhere? Every time I’ve tried to use them I’ve messed something up.

I use the feature branch workflow - https://www.atlassian.com/git/tutorials/comparing-workflows

2 Likes

@ben Thank you :slight_smile:

I don’t have separate staging servers currently, but your workflow totally makes sense and is much more safe than pushing updates directly to production.

Thanks for sharing about CircleCI. Could you share your deploy script please?

1 Like