|
#!/bin/bash |
|
set -euo pipefail |
|
|
|
# ============================== |
|
# Global configuration |
|
# ============================== |
|
S3_BUCKET="<bucket-name>" |
|
HOST_NAME="$(hostname)" |
|
DATE="$(date +%F-%H%M%S)" # YYYY-MM-DD-HHMMSS |
|
AWS_PROFILE="cloudflare-r2" |
|
R2_ACCOUNT_ID="<r2-account-id>" |
|
R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com" |
|
|
|
# Filter used to select Docker volumes |
|
VOLUME_FILTER="db" |
|
|
|
# ============================== |
|
# AWS CLI runner (Cloudflare R2) |
|
# ============================== |
|
aws_cli() { |
|
docker run --rm \ |
|
-v "${HOME}/.aws:/root/.aws:ro" \ |
|
-v "/tmp:/tmp" \ |
|
-e "AWS_PROFILE=${AWS_PROFILE}" \ |
|
amazon/aws-cli "$@" |
|
} |
|
|
|
# ============================== |
|
# Upload backup and cleanup |
|
# ============================== |
|
upload_and_cleanup() { |
|
local temp_dir="$1" |
|
local volume_name="$2" |
|
local archive_name="$3" |
|
local archive_path="${temp_dir}/${archive_name}" |
|
local s3_key="${HOST_NAME}/docker/${volume_name}/${archive_name}" |
|
|
|
echo "Archive path: ${archive_path}" |
|
echo "S3 key: ${s3_key}" |
|
|
|
# Ensure archive exists and is not empty |
|
if [[ ! -s "${archive_path}" ]]; then |
|
echo "ERROR: archive is empty or does not exist: ${archive_path}" |
|
return 1 |
|
fi |
|
|
|
echo "Uploading ${archive_name} to R2..." |
|
aws_cli s3api put-object \ |
|
--bucket "${S3_BUCKET}" \ |
|
--key "${s3_key}" \ |
|
--body "${archive_path}" \ |
|
--endpoint-url "${R2_ENDPOINT}" \ |
|
--checksum-algorithm CRC32 |
|
|
|
# Remove temporary directory after successful upload |
|
rm -rf "${temp_dir}" |
|
} |
|
|
|
# ============================== |
|
# Main logic |
|
# ============================== |
|
volumes="$(docker volume ls -q | grep -F "${VOLUME_FILTER}" || true)" |
|
|
|
if [[ -z "${volumes}" ]]; then |
|
echo "No Docker volumes matched filter: ${VOLUME_FILTER}" |
|
exit 0 |
|
fi |
|
|
|
for volume in ${volumes}; do |
|
echo "Processing volume: ${volume}" |
|
|
|
temp_dir="$(mktemp -d)" |
|
archive_name="${volume}-${DATE}.tar.gz" |
|
|
|
# Ensure temp directory is cleaned up if something fails |
|
trap 'rm -rf "${temp_dir}"' RETURN |
|
|
|
# Create compressed backup from Docker volume |
|
docker run --rm \ |
|
-v "${volume}:/volume:ro" \ |
|
-v "${temp_dir}:/backup" \ |
|
busybox tar czvf "/backup/${archive_name}" -C /volume ./ |
|
|
|
# Fix ownership of generated archive |
|
docker run --rm \ |
|
-v "${temp_dir}:/backup" \ |
|
busybox chown "$(id -u):$(id -g)" "/backup/${archive_name}" |
|
|
|
# Upload backup and clean up local files |
|
upload_and_cleanup "${temp_dir}" "${volume}" "${archive_name}" |
|
|
|
# Disable trap for next iteration |
|
trap - RETURN |
|
done |
|
|
|
echo "Backup process completed." |