Skip to content

Instantly share code, notes, and snippets.

@jamchamb
Last active January 11, 2020 22:53
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jamchamb/7c404a992f01142bf293 to your computer and use it in GitHub Desktop.
Save jamchamb/7c404a992f01142bf293 to your computer and use it in GitHub Desktop.
Phabricator automatic backup script
#!/bin/bash
# Backup Phabricator database, storage engine, and config files
# NOTE: This script is to be run by cron. See /etc/cron.d/phabricator
# Don't use unset variables
set -u
# Exit if any commands fail (unless they begin a pipe, etc.)
set -e
# Output an error message and set the errors flag
function error() {
echo "ERROR: $1"
errors=true
}
# Directory paths
# Note that the backup, temporary, and storage directories should be on the same filesystem to ensure atomic operations.
BACKUP_DIR="/nfsmount/phabricator/backups"
TMP_ROOT_DIR="/nfsmount/phabricator/tmp"
STORAGE_DIR="/nfsmount/phabricator/storage"
INSTALL_DIR="/opt/phabricator"
# Binary paths
DATE="/bin/date"
MV="/bin/mv"
MKDIR="/bin/mkdir"
GZIP="/bin/gzip"
RM="/bin/rm"
TAR="/bin/tar"
MKTEMP="/bin/mktemp"
DIRNAME="/usr/bin/dirname"
BASENAME="/bin/basename"
STORAGE="$INSTALL_DIR/bin/storage"
#####################################
# VALIDATE BINARIES AND DIRECTORIES #
#####################################
errors=false
# Validate binaries
for binvar in "DATE" "MV" "MKDIR" "GZIP" "RM" "TAR" "MKTEMP" "DIRNAME" "BASENAME" "STORAGE"; do
eval binpath="\$$binvar"
if [ ! -f "$binpath" ]; then
error "Bad path for $binvar: \"$binpath\" is not a file"
elif [ ! -x "$binpath" ]; then
error "Don't have execute permissions for $binvar binary at \"$binpath\'"
fi
done
# Make sure directories exist and are readable
for dirvar in "BACKUP_DIR" "STORAGE_DIR" "INSTALL_DIR" "TMP_ROOT_DIR"; do
eval dirpath="\$$dirvar"
if [ ! -d "$dirpath" ]; then
error "$dirvar path is invalid: \"$dirpath\" is not a directory"
elif [ ! -x "$dirpath" ] || [ ! -r "$dirpath" ]; then
error "Don't have read and/or execute permissions for $dirvar path at \"$dirpath\""
fi
done
# Check for write permissions where necessary
for dirvar in "BACKUP_DIR" "TMP_ROOT_DIR"; do
eval dirpath="\$$dirvar"
if [ ! -w "$dirpath" ]; then
error "Don't have write permissions for $dirvar path at \"$dirpath\""
fi
done
# Exit if there were environment problems
$errors && exit 1
# Create the backup directories if they don't exist
for i in {0..4}; do
cur_dir="$BACKUP_DIR/backup.$i"
if [ -d "$cur_dir" ] && [ ! -L "$cur_dir" ]; then
# Backup directory exists, check permissions
if [ ! -r "$cur_dir" ] || [ ! -w "$cur_dir" ] || [ ! -x "$cur_dir" ]; then
error "Backup directory \"$cur_dir\" exists but is not readable, writable, and executable"
fi
elif [ -e "$cur_dir" ]; then
error "Backup directory path \"$cur_dir\" already in use by a symlink or file"
else
$MKDIR -- "$cur_dir"
fi
done
# Exit if backup directories couldn't be set up
$errors && exit 2
##############################
# PERFORM PHABRICATOR BACKUP #
##############################
# Create the SQL dump filename
timestamp=$($DATE +'%Y%m%dT%H%M%S%z')
sql_file="phabricator-db-$timestamp.sql"
# Make temporary directory for new backup files
cur_tmp_dir=$($MKTEMP -d --tmpdir="$TMP_ROOT_DIR" "phabbackup.TEMP.XXXXXXXX")
# Delete the temporary directory if script gets killed here
trap "error 'Phabricator backup interrupted. Cleaning up temporary directory \"$cur_tmp_dir\"'; \"$RM\" -rf -- $cur_tmp_dir; exit" INT TERM EXIT
# Dump all the MySQL databases
$STORAGE dump > "$cur_tmp_dir/$sql_file"
if [ ! -s "$cur_tmp_dir/$sql_file" ]; then
error "Phabricator database dump is empty"
exit 3
fi
$GZIP -- "$cur_tmp_dir/$sql_file"
# Create the local storage engine backup.
# The -C option prevents cron from emailing us "Removing leading `/' from member names"
# every time the backup runs.
$TAR -zcf "$cur_tmp_dir/phabricator-storage-$timestamp.tar.gz" -C "$($DIRNAME "$STORAGE_DIR")" -- "$($BASENAME "$STORAGE_DIR")"
# Copy the configuration JSON
$GZIP -c -- "$INSTALL_DIR/conf/local/local.json" > "$cur_tmp_dir/phabricator-local-$timestamp.json.gz"
# Finished creating a consistent set of backup files.
# Now we change the trap to indicate backup directory rotation failed.
trap "error 'Backup directory rotation failed. Please check \"$TMP_ROOT_DIR\" and \"$BACKUP_DIR\"'; exit" INT TERM EXIT
# Set aside oldest backup for deletion if new backup is successful
old_backup_dir=$($MKTEMP -d --tmpdir="$TMP_ROOT_DIR" "phabbackup.OLD.XXXXXXXX")
$MV -- "$BACKUP_DIR/backup.4/" "$old_backup_dir"
# Go from oldest to newest backup, changing the directory names by 1
for i in {4..1}; do
$MV -- "$BACKUP_DIR/backup.$[${i}-1]" "$BACKUP_DIR/backup.${i}"
done
# Move the new files to the new backup directory
$MV -- "$cur_tmp_dir" "$BACKUP_DIR/backup.0"
# Finally delete oldest backup
$RM -rf -- "$old_backup_dir"
# Disable trap
trap - INT TERM EXIT
@ianmacs
Copy link

ianmacs commented Nov 5, 2017

Nice shell code. But it looks incomplete to me. Did you ever need to do a restore?

What is missing: These are where you resolve the phabricator "setup issues" after a fresh install

  • /etc/mysql/my.cnf
  • /etc/php5/fpm/php.ini
  • phabricator/support/preamble.php

Your web server setup, e.g.

  • /etc/nginx
  • /etc/ssl

The repositories, if you have them.

And I am not sure that your mysql dump also includes the phabricator database user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment