captaincore/app/backup/generate
2025-06-30 06:07:41 -04:00

202 lines
No EOL
7.5 KiB
Bash

#!/usr/bin/env bash
#
# Backups one or more sites.
#
# `captaincore backup generate <site>`
#
# [--skip-remote]
# Skips saving to Restic backup repo
#
# [--skip-db]
# Skips database backup
#
if [ ${#@} -ne 1 ]; then
echo -e "${COLOR_RED}Error:${COLOR_NORMAL} Requires a <site>"
exit
fi
while read config; do
if [[ "$config" == "Error:"* ]]; then
continue
fi
declare "$config"
done <<< "$(php ${CAPTAINCORE_PATH}/lib/local-scripts/configs.php fetch)"
site=$1
root_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; root_path=${root_path%app*}
run_command() {
runtime_start=$( date +%s )
if [[ $SKIP_REMOTE == true ]]; then
echo "Skipping remote"
fi
# Extract environment
if [[ "$site" == *"-staging"* ]]; then
environment=staging
fi
if [[ "$site" == *"-production"* ]]; then
environment=production
fi
if [[ "$site" != *"-"* ]]; then
environment=production
fi
# Load site configs
IFS=$'\n'$'\r'; for line in $(captaincore site get $site --bash --captain-id=$CAPTAIN_ID); do declare "$line"; done
# Site found, start the backup
if [[ $domain == "" ]]; then
echo "Error: $site missing domain. Skipping backup."
exit
fi
# Append trailing slash if home_directory exist
if [ "$home_directory" != "" ]; then
home_directory="${home_directory}/"
fi
# Define config file
rclone_config_file="$path/${site}_${site_id}/rclone.conf"
vault_file="$path/${site}_${site_id}/$environment/vault.txt"
if [[ ! -f "$rclone_config_file" || ! -f "$vault_file" ]]; then
captaincore site key-generate $site --captain-id=$CAPTAIN_ID
fi
# Lookup rclone
remote_check=$( rclone config show $environment --config="$rclone_config_file" )
remote_backup_check=$( rclone config show backup --config="$rclone_config_file" )
if [[ $remote_check == *"Couldn't find type of fs"* || $remote_backup_check == *"Couldn't find type of fs"* ]]; then
echo "$(date +'%Y-%m-%d %H:%M') Generating rclone configs for $site"
captaincore site key-generate $site --captain-id=$CAPTAIN_ID
fi
# Captures FTP errors in $ftp_output and file listing to log file
ftp_output=$( { rclone lsd ${environment}:$home_directory --config="$rclone_config_file" ; } 2>&1 )
ftp_search_for_wordpress=$( echo "$ftp_output" | perl -wnE'say for /wp-admin/g' )
# Handle FTP errors
if [[ $ftp_search_for_wordpress != "wp-admin"* ]]; then
wordpress_not_found=true
fi
if [[ "$backup_mode" == "direct" ]]; then
# --- 1. Generate a unique, random name for the temporary payload file ---
do_script_path="${root_path}lib/remote-scripts/_do"
random_token=$(head /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 8)
payload_file="$path_tmp/do_vault_payload-${CAPTAIN_ID}-${site}-${environment}_${random_token}.sh"
echo "📝 Creating temporary payload file at: $payload_file"
# --- 2. Read secrets and write 'export' commands to the payload file ---
# This block reads each line from your secrets file and builds the export commands.
{
echo "export B2_BUCKET='$(sed -n 1p "$vault_file")'"
echo "export B2_PATH='$(sed -n 2p "$vault_file")'"
echo "export B2_ACCOUNT_ID='$(sed -n 3p "$vault_file")'"
echo "export B2_ACCOUNT_KEY='$(sed -n 4p "$vault_file")'"
echo "export RESTIC_PASSWORD='$(sed -n 5p "$vault_file")'"
echo "export EMAIL_NOTIFY=$captaincore_admin_email"
} > "$payload_file"
# --- 3. Append the main _do script to the payload file ---
tail -n +2 "$do_script_path" >> "$payload_file"
# Error handling in case the secrets file is not found
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to create export commands. Check vault_file path: '$vault_file'" >&2
rm -f "$payload_file"
exit 1
fi
ssh_string=$( captaincore ssh ${site}-${environment} --debug --command="bash -s -- vault create" --captain-id=$CAPTAIN_ID )
# --- Execute the command for real ---
echo "🚀 Executing remote vault command..."
cat $payload_file | eval $ssh_string
# --- 5. Clean up the local temporary file ---
echo "🔥 Cleaning up temporary payload file..."
rm "$payload_file"
captaincore backup list-generate ${site}-${environment} --captain-id=$CAPTAIN_ID
captaincore backup get-generate ${site}-${environment} --captain-id=$CAPTAIN_ID
captaincore usage-update ${site}-${environment} --captain-id=$CAPTAIN_ID
cd $path/${site}_${site_id}/${environment}/backups/
runtime_end=$( date +%s )
echo "$runtime_start $runtime_end" >> runtime
exit
fi
# Incremental backup locally with rclone
echo "$(date +'%Y-%m-%d %H:%M') Begin incremental backup ${site}-${environment} to local"
if [[ $SKIP_DB != true ]] && [[ $wordpress_not_found != true ]]; then
# Database backup
captaincore ssh ${site}-${environment} --script="db-backup" --captain-id=$CAPTAIN_ID
if [[ "$provider" == "wpengine" ]]; then
rclone sync ${environment}:_wpeprivate/database-backup.sql $path/${site}_${site_id}/${environment}/backup/ --config="$rclone_config_file"
fi
if [[ "$provider" == "kinsta" ]]; then
rclone sync ${environment}:private/database-backup.sql $path/${site}_${site_id}/${environment}/backup/ --config="$rclone_config_file"
fi
if [[ "$provider" == "rocketdotnet" ]]; then
rclone sync ${environment}:tmp/database-backup.sql $path/${site}_${site_id}/${environment}/backup/ --config="$rclone_config_file"
fi
fi
mkdir -p $path/${site}_${site_id}/${environment}/backup/
# Backup site locally
if [[ "$wp_content" != "wp-content" ]]; then
echo "$(date +'%Y-%m-%d %H:%M') Backing up ${site}-${environment} alternative wp-content location ($wp_content)"
rclone sync ${environment}:$home_directory $path/${site}_${site_id}/${environment}/backup/ --exclude-from="${CAPTAINCORE_PATH}/lib/excludes" --filter="+ $wp_content/**" --filter="- wp-content/**" --filter="- content/**" --config="$rclone_config_file"
else
rclone sync ${environment}:$home_directory $path/${site}_${site_id}/${environment}/backup/ --exclude-from="${CAPTAINCORE_PATH}/lib/excludes" --filter="- content/**" --config="$rclone_config_file"
fi
# Incremental backup upload to Restic
if [[ $SKIP_REMOTE != true ]]; then
echo "$(date +'%Y-%m-%d %H:%M') Storing $site to backup archive"
if [[ $( restic snapshots --repo rclone:$rclone_backup/${site}_${site_id}/${environment}/restic-repo --password-file="${CAPTAINCORE_PATH}/data/restic.key" ) == "" ]]; then
echo "Generating restic repo for $site"
restic init --quiet --repo rclone:$rclone_backup/${site}_${site_id}/${environment}/restic-repo --password-file="${CAPTAINCORE_PATH}/data/restic.key"
fi
cd $path/${site}_${site_id}/${environment}/backup/
restic backup . --quiet --repo rclone:$rclone_backup/${site}_${site_id}/${environment}/restic-repo --exclude-file="${CAPTAINCORE_PATH}/lib/restic-excludes" --password-file="${CAPTAINCORE_PATH}/data/restic.key"
captaincore backup list-generate ${site}-${environment} --captain-id=$CAPTAIN_ID
captaincore backup get-generate ${site}-${environment} --captain-id=$CAPTAIN_ID
fi
captaincore usage-update ${site}-${environment} --captain-id=$CAPTAIN_ID
if [ -f "${path}/process-${process_id}-progress.log" ]; then
echo -n "." >> ${path}/process-${process_id}-progress.log
fi
if [ ! -d "$path/${site}_${site_id}/${environment}/backups/" ]; then
mkdir -p "$path/${site}_${site_id}/${environment}/backups"
fi
cd $path/${site}_${site_id}/${environment}/backups/
runtime_end=$( date +%s )
echo "$runtime_start $runtime_end" >> runtime
}
run_command