#!/bin/sh set -eu umask 077 require_variable() { variable_name="$1" eval "variable_value=\${$variable_name:-}" if [ -z "$variable_value" ]; then echo "$variable_name is required." >&2 exit 1 fi } aws_cli() { endpoint="${BACKUP_S3_ENDPOINT:-${AWS_ENDPOINT:-}}" if [ -n "$endpoint" ]; then aws --endpoint-url "$endpoint" "$@" else aws "$@" fi } for variable in \ AWS_ACCESS_KEY_ID \ AWS_DEFAULT_REGION \ AWS_SECRET_ACCESS_KEY \ BACKUP_S3_BUCKET \ DB_DATABASE \ DB_HOST \ DB_PASSWORD \ DB_USERNAME do require_variable "$variable" done backup_directory="${BACKUP_DIRECTORY:-/backups}" backup_prefix="${BACKUP_S3_PREFIX:-database-backups/postgres}" backup_prefix="${backup_prefix#/}" backup_prefix="${backup_prefix%/}" backup_label="${1:-manual}" backup_label="$(printf "%s" "$backup_label" | tr -cd "A-Za-z0-9_-")" retention_days="${BACKUP_LOCAL_RETENTION_DAYS:-7}" timestamp="$(date -u +"%Y%m%dT%H%M%SZ")" database_name="$(printf "%s" "$DB_DATABASE" | tr -cd "A-Za-z0-9_-")" backup_name="${timestamp}-${database_name}-${backup_label:-manual}.dump" backup_path="$backup_directory/$backup_name" temporary_path="$backup_path.partial" checksum_path="$backup_path.sha256" lock_directory="/tmp/bowli-database-backup.lock" case "$retention_days" in ""|*[!0-9]*) echo "BACKUP_LOCAL_RETENTION_DAYS must be a positive integer." >&2 exit 1 ;; esac if ! mkdir "$lock_directory" 2>/dev/null; then echo "Another database backup is already running." >&2 exit 1 fi cleanup() { rm -f "$temporary_path" rmdir "$lock_directory" 2>/dev/null || true } trap cleanup EXIT INT TERM mkdir -p "$backup_directory" export PGPASSWORD="$DB_PASSWORD" export PGCONNECT_TIMEOUT="${PGCONNECT_TIMEOUT:-10}" echo "Creating PostgreSQL backup: $backup_name" pg_dump \ --host="$DB_HOST" \ --port="${DB_PORT:-5432}" \ --username="$DB_USERNAME" \ --dbname="$DB_DATABASE" \ --format=custom \ --compress=9 \ --no-owner \ --no-acl \ --file="$temporary_path" pg_restore --list "$temporary_path" >/dev/null mv "$temporary_path" "$backup_path" ( cd "$backup_directory" sha256sum "$backup_name" > "$backup_name.sha256" ) s3_directory="s3://$BACKUP_S3_BUCKET" if [ -n "$backup_prefix" ]; then s3_directory="$s3_directory/$backup_prefix" fi echo "Uploading backup to $s3_directory/" aws_cli s3 cp \ "$backup_path" \ "$s3_directory/$backup_name" \ --sse AES256 \ --only-show-errors aws_cli s3 cp \ "$checksum_path" \ "$s3_directory/$backup_name.sha256" \ --sse AES256 \ --only-show-errors touch "$backup_directory/.last-success" find "$backup_directory" -type f \ \( -name "*.dump" -o -name "*.dump.sha256" \) \ -mtime "+$retention_days" \ -delete echo "Database backup completed: $backup_name"