#!/bin/bash # ------------------------------------------------------------------------------------- # The DIY backup script. # # This script is invoked to perform the backup of a Bitbucket Server, # or Bitbucket Data Center instance. It requires a properly configured # bitbucket.diy-backup.vars.sh file, which can be copied from # bitbucket.diy-backup.vars.sh.example and customized. # ------------------------------------------------------------------------------------- # Ensure the script terminates whenever a required operation encounters an error set -e SCRIPT_DIR=$(dirname "$0") source "${SCRIPT_DIR}/utils.sh" source "${SCRIPT_DIR}/common.sh" source_archive_strategy source_database_strategy source_disk_strategy check_command "jq" ########################################################## readonly DB_BACKUP_JOB_NAME="Database backup" readonly DISK_BACKUP_JOB_NAME="Disk backup" # Started background jobs declare -A BG_JOBS # Successfully completed background jobs declare -a COMPLETED_BG_JOBS # Failed background jobs declare -A FAILED_BG_JOBS # Run a command in the background and record its PID so we can wait for its completion function run_in_bg { ($1) & local PID=$! BG_JOBS["$2"]=${PID} debug "Started $2 (PID=${PID})" } # Wait for all tracked background jobs (i.e. jobs recorded in 'BG_JOBS') to finish. If one or more jobs return a # non-zero exit code, we log an error for each and return a non-zero value to fail the backup. function wait_for_bg_jobs { for bg_job_name in "${!BG_JOBS[@]}"; do local PID=${BG_JOBS[${bg_job_name}]} debug "Waiting for ${bg_job_name} (PID=${PID})" { wait ${PID} } && { debug "${bg_job_name} finished successfully (PID=${PID})" COMPLETED_BG_JOBS+=("${bg_job_name}") update_backup_progress 50 } || { FAILED_BG_JOBS["${bg_job_name}"]=$? } done if ((${#FAILED_BG_JOBS[@]})); then for bg_job_name in "${!FAILED_BG_JOBS[@]}"; do error "${bg_job_name} failed with status ${FAILED_BG_JOBS[${bg_job_name}]} (PID=${PID})" done return 1 fi } # Clean up after a failed backup function cleanup_incomplete_backup { debug "Cleaning up after failed backup" for bg_job_name in "${COMPLETED_BG_JOBS[@]}"; do case "$bg_job_name" in "$DB_BACKUP_JOB_NAME") cleanup_incomplete_db_backup ;; "$DISK_BACKUP_JOB_NAME") cleanup_incomplete_disk_backup ;; *) error "No cleanup task defined for backup type: $bg_job_name" ;; esac done } ########################################################## info "Preparing for backup" prepare_backup_db prepare_backup_disk # If necessary, lock Bitbucket, start an external backup and wait for instance readiness backup_start backup_wait info "Backing up the database and filesystem in parallel" run_in_bg backup_db "$DB_BACKUP_JOB_NAME" run_in_bg backup_disk "$DISK_BACKUP_JOB_NAME" { wait_for_bg_jobs } || { cleanup_incomplete_backup || error "Failed to cleanup incomplete backup" error "Backing up ${PRODUCT} failed" exit 1 } # If necessary, report 100% progress back to the application, and unlock Bitbucket update_backup_progress 100 success "Successfully completed the backup of your ${PRODUCT} instance" # Cleanup backups retaining the latest $KEEP_BACKUPS cleanup_old_db_backups cleanup_old_disk_backups cleanup_old_elasticsearch_backups if [ -n "${BACKUP_ARCHIVE_TYPE}" ]; then info "Archiving backups and cleaning up old archives" archive_backup cleanup_old_archives fi