mirror of
https://github.com/we-promise/sure.git
synced 2026-09-05 14:51:15 +00:00
- Replaces prodrigestivill/postgres-backup-local with an Alpine-based container - Enables backing up to 70+ cloud providers natively - Adds environment variable controls for file overwrite vs timestamp - Resolves security audit flags and rclone backup scoping - Resolves database credential exposure and addresses CodeRabbit review items
87 lines
2.9 KiB
Bash
Executable File
87 lines
2.9 KiB
Bash
Executable File
#!/bin/sh
|
|
set -e
|
|
|
|
# shellcheck disable=SC3040
|
|
if (set -o pipefail 2>/dev/null); then
|
|
set -o pipefail
|
|
fi
|
|
|
|
# Configuration
|
|
BACKUP_OVERWRITE=${BACKUP_OVERWRITE:-false}
|
|
BACKUP_KEEP_DAYS=${BACKUP_KEEP_DAYS:-7}
|
|
BACKUP_DESTINATION=${BACKUP_DESTINATION:-}
|
|
INSTANCE_ID=${INSTANCE_ID:-default}
|
|
POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
|
|
|
if [ -z "$BACKUP_DESTINATION" ]; then
|
|
echo "Error: BACKUP_DESTINATION is not set."
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$POSTGRES_DB" ] || [ -z "$POSTGRES_USER" ] || [ -z "$POSTGRES_PASSWORD" ] || [ -z "$POSTGRES_HOST" ]; then
|
|
echo "Error: Database connection variables are not fully set."
|
|
exit 1
|
|
fi
|
|
|
|
case "${POSTGRES_HOST}${POSTGRES_PORT}${POSTGRES_DB}${POSTGRES_USER}${POSTGRES_PASSWORD}" in
|
|
*"
|
|
"*)
|
|
echo "Error: Database connection parameters must not contain newlines."
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [ "$BACKUP_OVERWRITE" != "true" ] && [ -n "$BACKUP_KEEP_DAYS" ]; then
|
|
if ! [ "$BACKUP_KEEP_DAYS" -gt 0 ] 2>/dev/null; then
|
|
echo "Error: BACKUP_KEEP_DAYS must be a positive integer (got: ${BACKUP_KEEP_DAYS})."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
|
|
|
|
if [ "$BACKUP_OVERWRITE" = "true" ]; then
|
|
FILENAME="backup_latest.sql.gz"
|
|
else
|
|
FILENAME="backup_${TIMESTAMP}_$$.sql.gz"
|
|
fi
|
|
|
|
TMP_DIR=$(umask 077 && mktemp -d)
|
|
TEMP_FILE="${TMP_DIR}/${FILENAME}"
|
|
PGPASS_FILE="${TMP_DIR}/.pgpass"
|
|
|
|
# POSIX trap handler to guarantee cleanup of temporary workspace on exit or termination signal
|
|
trap 'rm -rf "${TMP_DIR}"' EXIT INT TERM
|
|
|
|
# Securely generate .pgpass file with strict permissions (0600) and escaped fields
|
|
PG_HOST_ESC=$(printf '%s' "${POSTGRES_HOST}" | sed -e 's/\\/\\\\/g' -e 's/:/\\:/g')
|
|
PG_PORT_ESC=$(printf '%s' "${POSTGRES_PORT}" | sed -e 's/\\/\\\\/g' -e 's/:/\\:/g')
|
|
PG_DB_ESC=$(printf '%s' "${POSTGRES_DB}" | sed -e 's/\\/\\\\/g' -e 's/:/\\:/g')
|
|
PG_USER_ESC=$(printf '%s' "${POSTGRES_USER}" | sed -e 's/\\/\\\\/g' -e 's/:/\\:/g')
|
|
PG_PASS_ESC=$(printf '%s' "${POSTGRES_PASSWORD}" | sed -e 's/\\/\\\\/g' -e 's/:/\\:/g')
|
|
|
|
(umask 077 && printf '%s:%s:%s:%s:%s\n' "${PG_HOST_ESC}" "${PG_PORT_ESC}" "${PG_DB_ESC}" "${PG_USER_ESC}" "${PG_PASS_ESC}" > "${PGPASS_FILE}")
|
|
chmod 0600 "${PGPASS_FILE}"
|
|
export PGPASSFILE="${PGPASS_FILE}"
|
|
|
|
# Create temp output file with restricted permissions before pg_dump execution
|
|
(umask 077 && touch "${TEMP_FILE}")
|
|
chmod 0600 "${TEMP_FILE}"
|
|
|
|
echo "Starting backup for ${POSTGRES_DB} to ${TEMP_FILE}..."
|
|
pg_dump -h "${POSTGRES_HOST}" -p "${POSTGRES_PORT}" -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" | gzip > "${TEMP_FILE}"
|
|
|
|
DEST_PATH="${BACKUP_DESTINATION%/}/${INSTANCE_ID}"
|
|
echo "Uploading ${TEMP_FILE} to ${DEST_PATH}/${FILENAME} via rclone..."
|
|
rclone copy "${TEMP_FILE}" "${DEST_PATH}"
|
|
|
|
if [ "$BACKUP_OVERWRITE" != "true" ] && [ -n "$BACKUP_KEEP_DAYS" ]; then
|
|
echo "Pruning remote backups older than ${BACKUP_KEEP_DAYS} days in ${DEST_PATH}..."
|
|
rclone delete "${DEST_PATH}" --min-age "${BACKUP_KEEP_DAYS}d" --include "backup_*.sql.gz"
|
|
fi
|
|
|
|
echo "Cleaning up..."
|
|
rm -rf "${TMP_DIR}"
|
|
|
|
echo "Backup complete!"
|