diff --git a/Dockerfile b/Dockerfile index b8f9e5e..683570a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,12 @@ RUN apk update \ postgresql14-client \ py3-magic \ py3-dateutil \ - s3cmd + s3cmd \ + curl \ + jq + +# Install sentry-cli +RUN curl -sL https://sentry.io/get-cli/ | bash COPY application/ /data/ WORKDIR /data diff --git a/application/backup.sh b/application/backup.sh index 956b54c..04136d9 100755 --- a/application/backup.sh +++ b/application/backup.sh @@ -1,90 +1,113 @@ #!/usr/bin/env bash -# Function to send error to Sentry -send_error_to_sentry() { - local error_message="$1" - local db_name="$2" - local status_code="$3" - - if [ -n "${SENTRY_DSN}" ]; then - wget -q --header="Content-Type: application/json" \ - --post-data="{ - \"message\": \"${error_message}\", - \"level\": \"error\", - \"extra\": { - \"database\": \"${db_name}\", - \"status_code\": \"${status_code}\", - \"hostname\": \"$(hostname)\" - } - }" \ - -O - "${SENTRY_DSN}" - fi +# Initialize logging with timestamp +log() { + local level="$1"; + local message="$2"; + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${level}: ${message}"; } -MYNAME="postgresql-backup-restore" -STATUS=0 +# Sentry reporting with validation and backwards compatibility +error_to_sentry() { + local error_message="$1"; + local db_name="$2"; + local status_code="$3"; -echo "${MYNAME}: backup: Started" + # Check if SENTRY_DSN is configured - ensures backup continues + if [ -z "${SENTRY_DSN:-}" ]; then + log "DEBUG" "Sentry logging skipped - SENTRY_DSN not configured"; + return 0; + fi -echo "${MYNAME}: Backing up ${DB_NAME}" + # Validate SENTRY_DSN format + if ! [[ "${SENTRY_DSN}" =~ ^https://[^@]+@[^/]+/[0-9]+$ ]]; then + log "WARN" "Invalid SENTRY_DSN format - Sentry logging will be skipped"; + return 0; + fi -start=$(date +%s) -$(PGPASSWORD=${DB_USERPASSWORD} pg_dump --host=${DB_HOST} --username=${DB_USER} --create --clean ${DB_OPTIONS} --dbname=${DB_NAME} > /tmp/${DB_NAME}.sql) || STATUS=$? -end=$(date +%s) + # Attempt to send event to Sentry + if sentry-cli send-event \ + --message "${error_message}" \ + --level error \ + --tag "database:${db_name}" \ + --tag "status:${status_code}"; then + log "DEBUG" "Successfully sent error to Sentry - Message: ${error_message}, Database: ${db_name}, Status: ${status_code}"; + else + log "WARN" "Failed to send error to Sentry, but continuing backup process"; + fi + + return 0; +} + +MYNAME="postgresql-backup-restore"; +STATUS=0; + +log "INFO" "${MYNAME}: backup: Started"; +log "INFO" "${MYNAME}: Backing up ${DB_NAME}"; + +start=$(date +%s); +$(PGPASSWORD=${DB_USERPASSWORD} pg_dump --host=${DB_HOST} --username=${DB_USER} --create --clean ${DB_OPTIONS} --dbname=${DB_NAME} > /tmp/${DB_NAME}.sql) || STATUS=$?; +end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" "${DB_NAME}" - exit $STATUS + error_message="${MYNAME}: FATAL: Backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - echo "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)." + log "INFO" "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)."; fi -start=$(date +%s) -gzip -f /tmp/${DB_NAME}.sql || STATUS=$? -end=$(date +%s) +# Compression +start=$(date +%s); +gzip -f /tmp/${DB_NAME}.sql || STATUS=$?; +end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Compressing backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" "${DB_NAME}" - exit $STATUS + error_message="${MYNAME}: FATAL: Compressing backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - echo "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "INFO" "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds."; fi -start=$(date +%s) -s3cmd put /tmp/${DB_NAME}.sql.gz ${S3_BUCKET} || STATUS=$? -end=$(date +%s) - +# S3 Upload +start=$(date +%s); +s3cmd put /tmp/${DB_NAME}.sql.gz ${S3_BUCKET} || STATUS=$?; +end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Copy backup to ${S3_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" "${DB_NAME}" - exit $STATUS + error_message="${MYNAME}: FATAL: Copy backup to ${S3_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - echo "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "INFO" "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds."; fi +# Backblaze B2 Upload if [ "${B2_BUCKET}" != "" ]; then - start=$(date +%s) + start=$(date +%s); s3cmd \ - --access_key=${B2_APPLICATION_KEY_ID} \ - --secret_key=${B2_APPLICATION_KEY} \ - --host=${B2_HOST} \ - --host-bucket='%(bucket)s.'"${B2_HOST}" \ - put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz - STATUS=$? - end=$(date +%s) + --access_key=${B2_APPLICATION_KEY_ID} \ + --secret_key=${B2_APPLICATION_KEY} \ + --host=${B2_HOST} \ + --host-bucket='%(bucket)s.'"${B2_HOST}" \ + put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz; + STATUS=$?; + end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" - exit $STATUS + error_message="${MYNAME}: FATAL: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - echo "${MYNAME}: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "INFO" "${MYNAME}: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds."; fi fi -echo "${MYNAME}: backup: Completed" +echo "postgresql-backup-restore: backup: Completed"; + +log "INFO" "${MYNAME}: backup: Completed"; + +exit $STATUS;