#!/bin/sh
#/ Usage: ghe-backup [-v]
#/ Take snapshots of all GitHub Enterprise data, including Git repository data,
#/ the MySQL database, instance settings, GitHub Pages data, etc.
#/
#/ With -v, enable verbose output and show more information about what's being
#/ transferred.
set -e

# Bring in the backup configuration
cd $(dirname "$0")/..
. share/github-backup-utils/ghe-backup-config

# Used to record failed backup steps
failures=

# Create the timestamped snapshot directory where files for this run will live,
# change into it, and mark the snapshot as incomplete by touching the
# 'incomplete' file. If the backup succeeds, this file will be removed
# signifying that the snapshot is complete.
mkdir -p "$GHE_SNAPSHOT_DIR"
cd "$GHE_SNAPSHOT_DIR"
touch "incomplete"

# This is toggled true once we've successfully re-enabled maintenance mode
# on the remote appliance. This is used to avoid trying to re-enable in the exit
# trap again on successful backup runs.
GHE_MAINTENANCE_MODE_ENABLED=false

# To prevent multiple backup runs happening at the same time, we create a
# in-progress symlink pointing to the snapshot directory. This will fail if
# another backup is already in progress, giving us a form of locking.
#
# Set up a trap to remove the in-progress symlink if we exit for any reason but
# verify that we own the in-progress symlink before doing so.
#
# The cleanup trap also handles disabling maintenance mode on the appliance if
# it was automatically enabled.
cleanup () {
    if [ "$(readlink ../in-progress)" = "$GHE_SNAPSHOT_TIMESTAMP" ]; then
        unlink ../in-progress
    fi

    if $GHE_MAINTENANCE_MODE_ENABLED; then
        ghe-maintenance-mode-disable "$GHE_HOSTNAME"
    fi
}

# Setup exit traps
trap 'cleanup' EXIT
trap 'exit $?' INT # ^C always terminate

# Mark the snapshot as in-progress by creating the symlink. If this fails, it
# means another ghe-backup run is already in progress and we should exit.
# NOTE: The -n argument to ln is non-POSIX but widely supported.
if ! ln -sn "$GHE_SNAPSHOT_TIMESTAMP" ../in-progress 2>/dev/null; then
    snapshot="$(readlink ../in-progress)"
    echo "Error: backup of $GHE_HOSTNAME already in progress in snapshot $snapshot. Aborting." 1>&2
    exit 1
fi

echo "Starting backup of $GHE_HOSTNAME in snapshot $GHE_SNAPSHOT_TIMESTAMP"

# Perform a host connection check and establish the remote appliance version.
# The version is available in the GHE_REMOTE_VERSION variable and also written
# to a version file in the snapshot directory itself.
ghe_remote_version_required
echo "$GHE_REMOTE_VERSION" > version

# Determine whether to use the rsync or tarball backup strategy based on the
# remote appliance version. The tarball strategy must be used with GitHub
# Enterprise versions prior to 11.10.340 since rsync is not available.
# The tarball strategy may be forced for newer versions by setting
# GHE_BACKUP_STRATEGY=tarball in backup.config but this is not recommended.
: ${GHE_BACKUP_STRATEGY:=rsync}
if [ $GHE_VERSION_MAJOR -eq 1 -a $GHE_VERSION_PATCH -lt 340 ]; then
    GHE_BACKUP_STRATEGY="tarball"
fi

# Record the strategy with the snapshot so we will know how to restore.
echo "$GHE_BACKUP_STRATEGY" > strategy

# If we're using the tarball backup strategy, put the appliance in maintenance
# mode and wait for all writing processes to bleed out.
if [ "$GHE_BACKUP_STRATEGY" = "tarball" ]; then
    ghe-maintenance-mode-enable "$GHE_HOSTNAME"
    GHE_MAINTENANCE_MODE_ENABLED=true
fi

echo "Backing up GitHub settings ..."
ghe-backup-settings ||
failures="$failures settings"

echo "Backing up SSH authorized keys ..."
ghe-ssh "$GHE_HOSTNAME" -- 'ghe-export-authorized-keys' > authorized-keys.json ||
failures="$failures authorized-keys"

echo "Backing up SSH host keys ..."
ghe-ssh "$GHE_HOSTNAME" -- 'ghe-export-ssh-host-keys' > ssh-host-keys.tar ||
failures="$failures ssh-host-keys"

echo "Backing up MySQL database ..."
echo 'set -o pipefail; ghe-export-mysql | gzip' |
ghe-ssh "$GHE_HOSTNAME" -- /bin/bash > mysql.sql.gz ||
failures="$failures mysql"

echo "Backing up Redis database ..."
ghe-backup-redis > redis.rdb ||
failures="$failures redis"

echo "Backing up Git repositories ..."
ghe-backup-repositories-${GHE_BACKUP_STRATEGY} ||
failures="$failures repositories"

echo "Backing up GitHub Pages ..."
ghe-backup-pages-${GHE_BACKUP_STRATEGY} ||
failures="$failures pages"

if [ "$GHE_VERSION_MAJOR" -ge 2 ]; then
    echo "Backing up asset attachments ..."
    ghe-backup-userdata alambic_assets ||
    failures="$failures alambic_assets"

    echo "Backing up hook deliveries ..."
    ghe-backup-userdata hookshot ||
    failures="$failures hookshot"
fi

echo "Backing up Elasticsearch indices ..."
ghe-backup-es-${GHE_BACKUP_STRATEGY} ||
failures="$failures elasticsearch"

# If we're using the tarball backup strategy, bring the appliance out of
# maintenance mode now instead of waiting until after pruning stale snapshots.
if $GHE_MAINTENANCE_MODE_ENABLED; then
    ghe-maintenance-mode-disable "$GHE_HOSTNAME" ||
        echo "Warning: Disabling maintenance mode on $GHE_HOSTNAME failed."
    GHE_MAINTENANCE_MODE_ENABLED=false
fi

# If everything was successful, mark the snapshot as complete, update the
# current symlink to point to the new snapshot and prune expired and failed
# snapshots.
if [ -z "$failures" ]; then
    rm "incomplete"

    rm -f "../current"
    ln -s "$GHE_SNAPSHOT_TIMESTAMP" "../current"

    ghe-prune-snapshots
fi

echo "Completed backup of $GHE_HOSTNAME in snapshot $GHE_SNAPSHOT_TIMESTAMP at $(date +"%H:%M:%S")"

# Exit non-zero and list the steps that failed.
if [ -n "$failures" ]; then
    steps="$(echo $failures | sed 's/ /, /g')"
    echo "Error: Snapshot incomplete. Some steps failed: ${steps}. "
    exit 1
fi

# Make sure we exit zero after the conditional
true
