Automated Backup Script for Pantheon Hosted Websites

an image of a man presiding over backup servers.

DigiSavvy hosts many websites on Pantheon. Sites on a paid plan have automatic backups enabled, but sites that are not yet on a paid plan do not have this feature; this is the heart of the problem we’re solving for. Thus, we needed to create a script that runs daily backups for those websites that don’t have the automatic backup feature.

Enter the Automate Pantheon backups workflow

We knew this was possible using a mix of tools like Terminus, Bash, and Linux cronjobs. After some discussion, we started working on a solution to automate the Pantheon backup process for non-paid Pantheon sites.

The Solution

We created a bash script that runs daily to make a backup for all Pantheon websites without an automatic backup feature.

Also, a notification message is sent to our Slack channel to inform us about possible errors or issues.

Requirements

You will need some specific environment and tools to implement this solution. Make sure to set up these items.

  • An Ubuntu server with SSH access: a high level of server customization is needed to install some tools and resolve possible issues. We use Ubuntu, but you can use your preferred Linux flavor.
  • Terminus: this is the official Pantheon CLI tool. It needs to be installed on your Ubuntu server.
  • Slack App: Optional but necessary to fire the notification after the backups run. Follow this tutorial to configure it. Also, remember to add the app to the channel where you want to send the notification.

Writing the scripts

The scripts need to be written using bash syntax is necessary because we need to make some conditional checks and execute some Linux and Terminus commands.

The backup bash script

This bash script iterates over an array of website string identifiers and uses the Terminus command terminus backup:create to create a backup for each. The --keep-for option establishes the retention period, in days, to retain the backup.

#!/bin/sh

# Exit on error
set -e

# Stash list of all Pantheon sites in the org
PANTHEON_SITES=("site1.env" "site2.env" "site3.env")

# Loop through each site in the list
for sitename in ${PANTHEON_SITES[@]}; do
  terminus backup:create --keep-for 30 $sitename
done

Terminus command structure typically includes <site>.<env> determining the target site and environment to execute against.

Note that the <> symbols are part of the example and should not be included in your Terminus commands.

Also, Terminus must be authenticated with a machine token before using any command. You may also need to set up SSH key access to execute remote instructions.

In our case, the scripts we implemented to automate the Pantheon backup process weren’t hosted on it but on a different server. Thus, we had to set up SSH key access.

Checking the backup state

We wrote another bash script to check out the status of all backups created by the backup script.

There are two arrays defined in the script, one for the backed-up websites string identifiers and the other one for each part of a single backup.

Every Pantheon backup comprises three files.

  • Code: anything version controlled and committed via the Site Dashboard. Uncommitted SFTP changes to code are not backed up.
  • Files: images and assets stored in the standard upload path wp-content/uploads for WordPress and sites/all/default/files for Drupal.
  • Database: is a MySQL dump of your site’s DB.

A backup is successful when these three files are available for download.

However, sometimes some of these files aren’t created because Terminus failed at some point in the backup process.

The script uses the Terminus command terminus backup:get to check the existence of these three files. The backup status will be considered failed if any of these three files are missing.

The result of each backup check is saved in a file called backups_results.txt . The backups_results.txt contains a string representation of a JSON object. This object has an entry for each backed-up website.

#!/bin/sh

# Stash list of all Pantheon sites in the org
PANTHEON_SITES=("site1.env" "site2.env" "site3.env")
FILES=("code" "files" "database")
backups_checks='{'
# JSON file with all the websites with a true variable. This file will save the backup status of each website

# Loop through each site in the list to check the backups
for sitename in ${PANTHEON_SITES[@]}; do
  check_result=true # Assume the backup was sucefully made 
  # Each backup has a code, files and database file. If one of these is missing, Pantheon reports the back as failed.
  for file_type in ${FILES[@]}; do
    result=$(terminus backup:get --no-ansi --element $file_type $sitename)
    if [ -z "$result" ]
    then 
      # Edit the corresponding JSON entry to false.
      check_result=false
    fi
  done
  # Save true or false in a json file. That file will be used by a php script to generate the Slack notification
  backups_checks+="\\"$sitename\\":\\"$check_result\\","
done

# Remove last character ','
backups_checks=${backups_checks:0:-1}

backups_checks+="}"
echo $backups_checks > backups_results.txt

Slack Notification – Optional

Lastly, we created a PHP file to send a notification using the Slack API. First, the string inside the ‘backups_results.txt’ is pulled using the file_get_contents() function and converted to a valid PHP variable using the function json_decode(). This variable will help to set up the logic needed to create the Slack message. The code iterates overreach website and creates a notification for each one. The message will vary depending on the status of its last backup.

The message is sent to a specific Slack channel using the endpoint chat.postMessage . You can also use the endpoint conversations.list to find the channel ID where you want to send the message.

<?php

$backups_results_file = file_get_contents( 'backups_results.txt' );
$backups_results      = json_decode( $backups_results_file, true );
slack_post( $backups_results );

/**
 * POST the message to the Slack channel named 'client-support'
 */
function slack_post( $backups_results ) {
	$ch = curl_init();
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
	curl_setopt( $ch, CURLOPT_TIMEOUT, 10 );
	curl_setopt(
		$ch,
		CURLOPT_HTTPHEADER,
		array(
			'Content-Type: application/json',
			'Authorization: Bearer your-slack-token',
		)
	);

	# The id of the client-support channel. The channel id could be retrieved using this function
	$channel_id = 'YOUR_CHANNEL_ID';

	# Prepare the slack message
	$slack_blocks = array(
		array(
			'type' => 'section',
			'text' => array(
				'type'  => 'plain_text',
				'emoji' => true,
				'text'  => 'Some Pantheon backups were made',
			),
		),
		array(
			'type' => 'divider',
		),
	);

	foreach ( $backups_results as $website_name => $website_backup_status ) {
		$website_dashboard = shell_exec( "terminus dashboard:view --print -- $website_name" );
		if ( $website_backup_status ) {
			$status_icon = ':white_check_mark:';
			$status_text = "*Website $website_name* \\n The backup was successfully made $status_icon \\n Visit $website_dashboard to download the backup";
		} else {
			$status_icon = ':x:';
			$status_text = "*Website $website_name* \\n The backup could not be completed $status_icon \\n Visit $website_dashboard to manually create the backup";
		}

		$slack_blocks[] = array(
			'type' => 'section',
			'text' => array(
				'type' => 'mrkdwn',
				'text' => $status_text,
			),
		);
	}

	$payload = json_encode(
		array(
			'channel' => $channel_id,
			'text'    => 'Posting to Digisavy client-support channel',
			'blocks'  => $slack_blocks,
		)
	);
	curl_setopt( $ch, CURLOPT_URL, '<https://slack.com/api/chat.postMessage>' );
	curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'POST' );
	curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload );
	$result = curl_exec( $ch );
	$result_encode = json_decode( $result );

	curl_close( $ch );
}

Setting up the cronjob

The cronjob is a big part of the backup automation process. Follow the next steps to set it up. Access your server using SHH and run the command crontab -e to open the cronjob config file.

A crontab file consists of commands, one command per line, that execute automatically at the time specified by the first five fields of each command line. Each command within a crontab file must consist of one line, even if that line is very long. The crontab file does not recognize extra carriage returns.

If you need help generating your cron command and configuring the syntax for the schedule, then this crontab generator can help.

Your cron line must contain

  • A command to access the folder where the scripts live
  • A command to execute the scripts.

The purpose of the PATH variable is to let crontab know where the libraries are located in the scripts, for example, Terminus.

This is an example of the line we added to our server crontab file.

PATH="/path/to/backup/scripts/.local/bin:/usr/bin:/bin:/usr/local/bin"

#Pantheon backups
0 11 * * * cd /path/to/backup/scripts/; bash pantheon-backups.sh; bash pantheon-backups-check.sh; php slack-notification.php >/dev/null 2>&1

Now we don’t need to make all the backups manually like before because the process is fully automated. Also, we save some time for our daily workflow.

Get Notified When We Publish New Content!

Join more than 2,500 people who get our marketing automation, business marketing, and WordPress news!

* By submitting your email here, you agree to our Privacy Policy

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *