Initial
This commit is contained in:
178
backup.sh
Normal file
178
backup.sh
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration from environment variables
|
||||
POSTGRES_HOST="${POSTGRES_HOST:-localhost}"
|
||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
||||
POSTGRES_USER="${POSTGRES_USER:-postgres}"
|
||||
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-}"
|
||||
POSTGRES_DB="${POSTGRES_DB:-postgres}"
|
||||
POSTGRES_DATABASES="${POSTGRES_DATABASES:-}" # Comma-separated list of databases, if empty backs up all
|
||||
|
||||
S3_BUCKET="${S3_BUCKET}"
|
||||
S3_PREFIX="${S3_PREFIX:-postgres-backups}"
|
||||
S3_ENDPOINT="${S3_ENDPOINT}" # Required for third-party S3 services
|
||||
S3_ACCESS_KEY_ID="${S3_ACCESS_KEY_ID}"
|
||||
S3_SECRET_ACCESS_KEY="${S3_SECRET_ACCESS_KEY}"
|
||||
S3_REGION="${S3_REGION:-auto}" # Default to 'auto' for S3-compatible services
|
||||
|
||||
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
|
||||
|
||||
# Generate timestamp
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
|
||||
# Function to log messages
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
}
|
||||
|
||||
# Function to setup rclone configuration
|
||||
setup_rclone() {
|
||||
log "Setting up rclone configuration for S3-compatible storage"
|
||||
|
||||
# Create rclone config directory
|
||||
mkdir -p ~/.config/rclone
|
||||
|
||||
# Create rclone configuration
|
||||
cat > ~/.config/rclone/rclone.conf << EOF
|
||||
[s3remote]
|
||||
type = s3
|
||||
provider = Other
|
||||
access_key_id = ${S3_ACCESS_KEY_ID}
|
||||
secret_access_key = ${S3_SECRET_ACCESS_KEY}
|
||||
endpoint = ${S3_ENDPOINT}
|
||||
region = ${S3_REGION}
|
||||
force_path_style = true
|
||||
acl = private
|
||||
EOF
|
||||
}
|
||||
|
||||
# Function to get list of databases
|
||||
get_databases() {
|
||||
if [[ -n "${POSTGRES_DATABASES}" ]]; then
|
||||
echo "${POSTGRES_DATABASES}" | tr ',' '\n'
|
||||
else
|
||||
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d postgres -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;" | grep -v '^$' | xargs
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to backup a single database
|
||||
backup_database() {
|
||||
local db_name="$1"
|
||||
local backup_file="/backups/${db_name}_${TIMESTAMP}.sql.gz"
|
||||
|
||||
log "Starting backup of database: ${db_name}"
|
||||
log "Creating compressed SQL dump"
|
||||
|
||||
# Create database dump with gzip compression
|
||||
PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \
|
||||
-h "${POSTGRES_HOST}" \
|
||||
-p "${POSTGRES_PORT}" \
|
||||
-U "${POSTGRES_USER}" \
|
||||
-d "${db_name}" \
|
||||
--no-password \
|
||||
--format=plain \
|
||||
--clean \
|
||||
--if-exists \
|
||||
--no-privileges \
|
||||
--no-owner \
|
||||
| gzip --rsyncable > "${backup_file}"
|
||||
|
||||
# Upload to S3 with flat structure
|
||||
local s3_key="${S3_PREFIX}/$(basename "${backup_file}")"
|
||||
log "Uploading backup to S3: s3://${S3_BUCKET}/${s3_key}"
|
||||
|
||||
rclone copy "${backup_file}" "s3remote:${S3_BUCKET}/${S3_PREFIX}/" --progress
|
||||
|
||||
# Verify upload
|
||||
if ! rclone ls "s3remote:${S3_BUCKET}/${s3_key}" > /dev/null; then
|
||||
log "ERROR: Failed to verify backup upload"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Successfully uploaded backup for ${db_name}"
|
||||
|
||||
# Clean up local file
|
||||
rm -f "${backup_file}"
|
||||
}
|
||||
|
||||
# Function to cleanup old backups
|
||||
cleanup_old_backups() {
|
||||
local db_name="$1"
|
||||
local cutoff_date=$(date -d "${BACKUP_RETENTION_DAYS} days ago" +%Y%m%d_%H%M%S)
|
||||
|
||||
log "Cleaning up backups older than ${BACKUP_RETENTION_DAYS} days for database: ${db_name}"
|
||||
|
||||
# List and delete old backups using rclone with flat structure
|
||||
rclone lsf "s3remote:${S3_BUCKET}/${S3_PREFIX}/" --include "${db_name}_*.sql.gz" | while read -r backup_file; do
|
||||
# Extract timestamp from filename
|
||||
backup_date=$(echo "$backup_file" | grep -o '[0-9]\{8\}_[0-9]\{6\}' || true)
|
||||
if [[ -n "$backup_date" && "$backup_date" < "$cutoff_date" ]]; then
|
||||
log "Deleting old backup file: ${backup_file}"
|
||||
rclone delete "s3remote:${S3_BUCKET}/${S3_PREFIX}/${backup_file}" || log "Failed to delete ${backup_file}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Function to send notification (placeholder for webhook/email integration)
|
||||
send_notification() {
|
||||
local status="$1"
|
||||
local message="$2"
|
||||
|
||||
if [[ -n "${WEBHOOK_URL:-}" ]]; then
|
||||
curl -X POST "${WEBHOOK_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"status\": \"${status}\", \"message\": \"${message}\", \"timestamp\": \"$(date -Iseconds)\"}" \
|
||||
|| log "Failed to send notification"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log "Starting PostgreSQL backup process"
|
||||
|
||||
# Validate required environment variables
|
||||
if [[ -z "${S3_BUCKET}" || -z "${S3_ACCESS_KEY_ID}" || -z "${S3_SECRET_ACCESS_KEY}" || -z "${S3_ENDPOINT}" ]]; then
|
||||
log "ERROR: Missing required environment variables (S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup rclone
|
||||
setup_rclone
|
||||
|
||||
# Test database connection
|
||||
log "Testing database connection"
|
||||
if ! PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d postgres -c "SELECT 1" > /dev/null 2>&1; then
|
||||
log "ERROR: Cannot connect to PostgreSQL database"
|
||||
send_notification "error" "Cannot connect to PostgreSQL database"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get list of databases to backup
|
||||
databases=$(get_databases)
|
||||
log "Databases to backup: ${databases}"
|
||||
|
||||
# Backup each database
|
||||
backup_success=true
|
||||
for db in ${databases}; do
|
||||
if backup_database "${db}"; then
|
||||
cleanup_old_backups "${db}"
|
||||
else
|
||||
log "ERROR: Failed to backup database: ${db}"
|
||||
backup_success=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${backup_success}" == "true" ]]; then
|
||||
log "All database backups completed successfully"
|
||||
send_notification "success" "All PostgreSQL database backups completed successfully"
|
||||
else
|
||||
log "Some database backups failed"
|
||||
send_notification "error" "Some PostgreSQL database backups failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Execute main function
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user