fix(scripts): migrate to rclone from aws-cli and add cleanup actions
This commit is contained in:
161
scripts/backup-upload-s3.sh
Normal file → Executable file
161
scripts/backup-upload-s3.sh
Normal file → Executable file
@@ -8,23 +8,25 @@ Usage:
|
||||
|
||||
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_ENDPOINT_URL S3 endpoint URL.
|
||||
S3_BACKUP_ACCESS_KEY_ID S3 access key.
|
||||
S3_BACKUP_SECRET_ACCESS_KEY S3 secret key.
|
||||
BACKUP_ENCRYPTION_PASSPHRASE Passphrase used to encrypt backup archives.
|
||||
BACKUP_LOCAL_KEEP_LATEST Set to 1 to keep latest encrypted archive locally.
|
||||
S3_BACKUP_BUCKET Bucket name.
|
||||
S3_BACKUP_PREFIX Object prefix. Defaults to qlockify.
|
||||
|
||||
BACKUP_ENCRYPTION_PASSPHRASE Encryption passphrase.
|
||||
BACKUP_LOCAL_KEEP_LATEST Number of latest local plaintext backups to keep. Defaults to 3.
|
||||
BACKUP_REMOTE_KEEP_LATEST Number of latest remote encrypted backups to keep. Defaults to 7.
|
||||
EOF
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[backup-s3] %s\n' "$*"
|
||||
printf '[backup-rclone] %s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '[backup-s3] %s\n' "$*" >&2
|
||||
printf '[backup-rclone] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -35,26 +37,93 @@ require_var() {
|
||||
|
||||
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 "$@"
|
||||
normalize_prefix() {
|
||||
local value="${1:-qlockify}"
|
||||
|
||||
value="${value#/}"
|
||||
value="${value%/}"
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
positive_integer_or_default() {
|
||||
local value="${1:-}"
|
||||
local fallback="$2"
|
||||
|
||||
if [[ "$value" =~ ^[0-9]+$ ]]; then
|
||||
printf '%s' "$value"
|
||||
else
|
||||
aws s3 "$@"
|
||||
printf '%s' "$fallback"
|
||||
fi
|
||||
}
|
||||
|
||||
normalize_prefix() {
|
||||
local value="${1:-qlockify}"
|
||||
value="${value#/}"
|
||||
value="${value%/}"
|
||||
printf '%s' "$value"
|
||||
rclone_remote_path() {
|
||||
printf 'parspack:%s/%s' "$S3_BACKUP_BUCKET" "$S3_BACKUP_PREFIX"
|
||||
}
|
||||
|
||||
cleanup_local_backups() {
|
||||
local keep_count="$1"
|
||||
local latest_dir="$2"
|
||||
|
||||
[[ "$keep_count" -gt 0 ]] || {
|
||||
rm -rf "$latest_dir"
|
||||
return
|
||||
}
|
||||
|
||||
mkdir -p "$latest_dir"
|
||||
find "$latest_dir" -maxdepth 1 -type f -name '*.tar.gz.enc' -delete
|
||||
cp "$ARCHIVE_PATH" "$latest_dir/$ARCHIVE_NAME"
|
||||
|
||||
find "$latest_dir" -maxdepth 1 -type f -name '*.tar.gz' -printf '%T@ %p\n' \
|
||||
| sort -nr \
|
||||
| awk -v keep="$keep_count" 'NR > keep {print $2}' \
|
||||
| xargs -r rm -f
|
||||
|
||||
log "Kept latest $keep_count plaintext backup(s) locally in: $latest_dir"
|
||||
}
|
||||
|
||||
cleanup_remote_backups() {
|
||||
local keep_count="$1"
|
||||
local remote_path="$2"
|
||||
local stale_files
|
||||
|
||||
[[ "$keep_count" -gt 0 ]] || {
|
||||
log "Skipping remote cleanup because BACKUP_REMOTE_KEEP_LATEST is $keep_count"
|
||||
return
|
||||
}
|
||||
|
||||
stale_files="$(
|
||||
rclone lsf "$remote_path" \
|
||||
--config "$RCLONE_CONFIG" \
|
||||
--s3-no-check-bucket \
|
||||
--files-only \
|
||||
| grep '\.tar\.gz\.enc$' \
|
||||
| sort -r \
|
||||
| awk -v keep="$keep_count" 'NR > keep' \
|
||||
|| true
|
||||
)"
|
||||
|
||||
if [[ -z "$stale_files" ]]; then
|
||||
log "No remote backups need cleanup"
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r stale_file; do
|
||||
[[ -n "$stale_file" ]] || continue
|
||||
log "Deleting old remote backup: $remote_path/$stale_file"
|
||||
rclone deletefile "$remote_path/$stale_file" \
|
||||
--config "$RCLONE_CONFIG" \
|
||||
--s3-no-check-bucket
|
||||
done <<< "$stale_files"
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
@@ -62,29 +131,28 @@ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
command -v aws >/dev/null 2>&1 || fail "aws CLI is required"
|
||||
command -v rclone >/dev/null 2>&1 || fail "rclone 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"
|
||||
BACKUP_SCRIPT="$SCRIPT_DIR/backup.sh"
|
||||
|
||||
[[ -x "$BACKUP_SCRIPT" ]] || fail "Backup script is not executable: $BACKUP_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_ENDPOINT_URL
|
||||
require_var S3_BACKUP_ACCESS_KEY_ID
|
||||
require_var S3_BACKUP_SECRET_ACCESS_KEY
|
||||
require_var S3_BACKUP_BUCKET
|
||||
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
|
||||
S3_BACKUP_PREFIX="$(normalize_prefix "${S3_BACKUP_PREFIX:-qlockify}")"
|
||||
BACKUP_LOCAL_KEEP_LATEST="$(positive_integer_or_default "${BACKUP_LOCAL_KEEP_LATEST:-3}" 3)"
|
||||
BACKUP_REMOTE_KEEP_LATEST="$(positive_integer_or_default "${BACKUP_REMOTE_KEEP_LATEST:-7}" 7)"
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
@@ -93,36 +161,53 @@ PLAIN_DIR="$WORK_DIR/plain"
|
||||
mkdir -p "$PLAIN_DIR"
|
||||
|
||||
log "Creating local backup archive"
|
||||
|
||||
DEPLOY_ROOT="$DEPLOY_ROOT" "$BACKUP_SCRIPT" "$PLAIN_DIR"
|
||||
|
||||
shopt -s nullglob
|
||||
archives=("$PLAIN_DIR"/*.tar.gz)
|
||||
shopt -u nullglob
|
||||
|
||||
[[ "${#archives[@]}" -eq 1 ]] || fail "Expected exactly one backup archive, found ${#archives[@]}"
|
||||
|
||||
ARCHIVE_PATH="${archives[0]}"
|
||||
ARCHIVE_NAME="$(basename "$ARCHIVE_PATH")"
|
||||
|
||||
ENCRYPTED_NAME="$ARCHIVE_NAME.enc"
|
||||
ENCRYPTED_PATH="$WORK_DIR/$ENCRYPTED_NAME"
|
||||
S3_URI="s3://$S3_BACKUP_BUCKET/$S3_BACKUP_PREFIX/$ENCRYPTED_NAME"
|
||||
|
||||
log "Encrypting backup archive"
|
||||
|
||||
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 200000 \
|
||||
-in "$ARCHIVE_PATH" \
|
||||
-out "$ENCRYPTED_PATH" \
|
||||
-pass env:BACKUP_ENCRYPTION_PASSPHRASE
|
||||
|
||||
rm -f "$ARCHIVE_PATH"
|
||||
RCLONE_CONFIG="$WORK_DIR/rclone.conf"
|
||||
|
||||
log "Uploading encrypted backup to $S3_URI"
|
||||
aws_s3 cp "$ENCRYPTED_PATH" "$S3_URI" --only-show-errors
|
||||
cat > "$RCLONE_CONFIG" <<EOF
|
||||
[parspack]
|
||||
type = s3
|
||||
provider = Other
|
||||
access_key_id = $S3_BACKUP_ACCESS_KEY_ID
|
||||
secret_access_key = $S3_BACKUP_SECRET_ACCESS_KEY
|
||||
endpoint = $S3_BACKUP_ENDPOINT_URL
|
||||
acl = private
|
||||
force_path_style = true
|
||||
EOF
|
||||
|
||||
if [[ "${BACKUP_LOCAL_KEEP_LATEST:-1}" == "1" ]]; then
|
||||
LATEST_DIR="$DEPLOY_ROOT/backups/latest"
|
||||
mkdir -p "$LATEST_DIR"
|
||||
find "$LATEST_DIR" -type f -name '*.tar.gz.enc' -delete
|
||||
cp "$ENCRYPTED_PATH" "$LATEST_DIR/$ENCRYPTED_NAME"
|
||||
log "Kept latest encrypted backup locally: $LATEST_DIR/$ENCRYPTED_NAME"
|
||||
fi
|
||||
REMOTE_PATH="$(rclone_remote_path)"
|
||||
|
||||
log "S3 backup upload completed: $S3_URI"
|
||||
log "Uploading encrypted backup to $REMOTE_PATH/$ENCRYPTED_NAME"
|
||||
|
||||
rclone copy \
|
||||
"$ENCRYPTED_PATH" \
|
||||
"$REMOTE_PATH" \
|
||||
--config "$RCLONE_CONFIG" \
|
||||
--s3-no-check-bucket \
|
||||
--progress
|
||||
|
||||
cleanup_local_backups "$BACKUP_LOCAL_KEEP_LATEST" "$DEPLOY_ROOT/backups/latest"
|
||||
cleanup_remote_backups "$BACKUP_REMOTE_KEEP_LATEST" "$REMOTE_PATH"
|
||||
|
||||
log "Backup upload completed successfully"
|
||||
|
||||
Reference in New Issue
Block a user