#!/usr/bin/env bash set -Eeuo pipefail usage() { cat <<'EOF' Usage: restore-from-s3.sh latest restore-from-s3.sh Examples: restore-from-s3.sh latest restore-from-s3.sh qlockify/qlockify-backup-YYYYMMDD-HHMMSS.tar.gz.enc Environment: DEPLOY_ROOT Deployment directory. Defaults to ~/qlockify-deployment. S3_BACKUP_BUCKET S3 bucket name. S3_BACKUP_PREFIX S3 object prefix. Defaults to qlockify. S3_BACKUP_REGION S3 region. Defaults to us-east-1. S3_BACKUP_ENDPOINT_URL Optional S3-compatible endpoint URL. S3_BACKUP_ACCESS_KEY_ID S3 access key. S3_BACKUP_SECRET_ACCESS_KEY S3 secret key. BACKUP_ENCRYPTION_PASSPHRASE Passphrase used to decrypt backup archives. Existing RESTORE_SKIP_DB, RESTORE_SKIP_MEDIA, and RESTORE_SKIP_ENV flags are passed through to restore.sh. EOF } log() { printf '[restore-s3] %s\n' "$*" } fail() { printf '[restore-s3] %s\n' "$*" >&2 exit 1 } require_var() { local name="$1" [[ -n "${!name:-}" ]] || fail "$name is required" } load_env() { local env_path="$1" [[ -f "$env_path" ]] || fail "Deployment env file not found: $env_path" set -a # shellcheck disable=SC1090 . "$env_path" set +a } aws_s3() { if [[ -n "${S3_BACKUP_ENDPOINT_URL:-}" ]]; then aws --endpoint-url "$S3_BACKUP_ENDPOINT_URL" s3 "$@" else aws s3 "$@" fi } normalize_prefix() { local value="${1:-qlockify}" value="${value#/}" value="${value%/}" printf '%s' "$value" } latest_object_key() { aws_s3 ls "s3://$S3_BACKUP_BUCKET/$S3_BACKUP_PREFIX/" --recursive \ | awk '{print $4}' \ | grep '\.tar\.gz\.enc$' \ | sort \ | tail -n 1 } if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage exit 0 fi REQUESTED_KEY="${1:-}" [[ -n "$REQUESTED_KEY" ]] || { usage exit 1 } command -v aws >/dev/null 2>&1 || fail "aws CLI is required" command -v openssl >/dev/null 2>&1 || fail "openssl is required" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_ROOT="${DEPLOY_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}" DEPLOY_ENV="$DEPLOY_ROOT/.env" RESTORE_SCRIPT="$SCRIPT_DIR/restore.sh" [[ -x "$RESTORE_SCRIPT" ]] || fail "Restore script is not executable: $RESTORE_SCRIPT" load_env "$DEPLOY_ENV" S3_BACKUP_PREFIX="$(normalize_prefix "${S3_BACKUP_PREFIX:-qlockify}")" S3_BACKUP_REGION="${S3_BACKUP_REGION:-us-east-1}" require_var S3_BACKUP_BUCKET require_var S3_BACKUP_ACCESS_KEY_ID require_var S3_BACKUP_SECRET_ACCESS_KEY require_var BACKUP_ENCRYPTION_PASSPHRASE export AWS_ACCESS_KEY_ID="$S3_BACKUP_ACCESS_KEY_ID" export AWS_SECRET_ACCESS_KEY="$S3_BACKUP_SECRET_ACCESS_KEY" export AWS_DEFAULT_REGION="$S3_BACKUP_REGION" export AWS_EC2_METADATA_DISABLED=true if [[ "$REQUESTED_KEY" == "latest" ]]; then log "Resolving latest encrypted backup object" OBJECT_KEY="$(latest_object_key)" [[ -n "$OBJECT_KEY" ]] || fail "No encrypted backups found in s3://$S3_BACKUP_BUCKET/$S3_BACKUP_PREFIX/" else OBJECT_KEY="${REQUESTED_KEY#s3://$S3_BACKUP_BUCKET/}" fi WORK_DIR="$(mktemp -d)" trap 'rm -rf "$WORK_DIR"' EXIT ENCRYPTED_PATH="$WORK_DIR/$(basename "$OBJECT_KEY")" DECRYPTED_PATH="$WORK_DIR/${ENCRYPTED_PATH##*/}" DECRYPTED_PATH="${DECRYPTED_PATH%.enc}" log "Downloading encrypted backup from s3://$S3_BACKUP_BUCKET/$OBJECT_KEY" aws_s3 cp "s3://$S3_BACKUP_BUCKET/$OBJECT_KEY" "$ENCRYPTED_PATH" --only-show-errors log "Decrypting backup archive" openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \ -in "$ENCRYPTED_PATH" \ -out "$DECRYPTED_PATH" \ -pass env:BACKUP_ENCRYPTION_PASSPHRASE log "Restoring decrypted backup archive" DEPLOY_ROOT="$DEPLOY_ROOT" "$RESTORE_SCRIPT" "$DECRYPTED_PATH" log "S3 restore completed from: s3://$S3_BACKUP_BUCKET/$OBJECT_KEY"