Files
home-infra/scripts/deploy-keycloak.sh
T
root 86d99da7e8
Keycloak CI/CD / Deploy Keycloak (push) Successful in 13s
Add Keycloak CI/CD with PostgreSQL backup
2026-09-30 10:26:20 +03:00

1258 lines
25 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
# ============================================================
# Configuration
# ============================================================
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC="${ROOT}/stacks/keycloak"
LIVE="/srv/nfs/infra/keycloak"
ENV_FILE="${LIVE}/env/kc.env"
BACKUP_ROOT="/srv/nfs/infra/.deploy-backups/keycloak"
BACKUP_KEEP=3
STAMP="$(date '+%Y%m%d-%H%M%S')"
BACKUP_DIR="${BACKUP_ROOT}/${STAMP}"
DB_BACKUP_DIR="${BACKUP_DIR}/database"
DB_BACKUP_FILE="${DB_BACKUP_DIR}/keycloak.dump"
# Максимальное ожидание старта Keycloak.
#
# Keycloak по умолчанию допускает довольно долгую DB migration.
#
KEYCLOAK_START_TIMEOUT=1800
KEYCLOAK_CHECK_INTERVAL=10
# ============================================================
# Helpers
# ============================================================
log()
{
echo
echo "============================================================"
echo "$*"
echo "============================================================"
}
fail()
{
echo
echo "ERROR: $*" >&2
exit 1
}
on_error()
{
rc=$?
trap - ERR
echo
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
echo " Keycloak deployment FAILED"
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
if [[ -d "${BACKUP_DIR}" ]]; then
echo
echo "Pre-upgrade backup:"
echo " ${BACKUP_DIR}"
fi
echo
echo "Automatic rollback is intentionally NOT performed."
echo
echo "Reason:"
echo " Keycloak may already have changed the PostgreSQL schema"
echo " during Liquibase migration."
echo
echo "For rollback restore the pre-upgrade database backup first."
exit "${rc}"
}
cleanup_old_backups()
{
echo
echo "--- Backup retention ---"
echo "Keeping last ${BACKUP_KEEP} pre-upgrade backups."
mkdir -p "${BACKUP_ROOT}"
mapfile -t BACKUPS < <(
find "${BACKUP_ROOT}" \
-mindepth 1 \
-maxdepth 1 \
-type d \
-printf '%f\n' |
grep -E '^[0-9]{8}-[0-9]{6}$' |
sort -r
)
if (( ${#BACKUPS[@]} <= BACKUP_KEEP )); then
echo "Current backup count: ${#BACKUPS[@]}"
echo "Nothing to delete."
return 0
fi
for BACKUP_NAME in "${BACKUPS[@]:${BACKUP_KEEP}}"
do
OLD_BACKUP="${BACKUP_ROOT}/${BACKUP_NAME}"
echo
echo "Removing old backup:"
echo " ${OLD_BACKUP}"
rm -rf -- "${OLD_BACKUP}"
done
}
get_container_env()
{
local VARIABLE="$1"
docker inspect \
--format '{{range .Config.Env}}{{println .}}{{end}}' \
keycloak |
grep -m1 "^${VARIABLE}=" |
sed "s/^${VARIABLE}=//" || true
}
extract_version()
{
grep -oE \
'[0-9]+(\.[0-9]+){1,3}' |
head -n1
}
version_less_than()
{
local A="$1"
local B="$2"
[[ "${A}" == "${B}" ]] && return 1
[[ "$(
printf '%s\n%s\n' "${A}" "${B}" |
sort -V |
head -n1
)" == "${A}" ]]
}
wait_for_keycloak()
{
local elapsed=0
echo
echo "Waiting for Keycloak health=healthy..."
while (( elapsed < KEYCLOAK_START_TIMEOUT ))
do
RUNNING="$(
docker inspect \
-f '{{.State.Running}}' \
keycloak 2>/dev/null ||
echo false
)"
if [[ "${RUNNING}" != "true" ]]; then
echo
echo "--- Keycloak logs ---"
docker logs \
--since "${UPGRADE_STARTED:-10m}" \
keycloak || true
fail "Keycloak container stopped during startup"
fi
HEALTH="$(
docker inspect \
-f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' \
keycloak
)"
echo "Keycloak health: ${HEALTH} (${elapsed}s)"
if [[ "${HEALTH}" == "healthy" ]]; then
echo
echo "Keycloak is healthy."
return 0
fi
sleep "${KEYCLOAK_CHECK_INTERVAL}"
elapsed=$((elapsed + KEYCLOAK_CHECK_INTERVAL))
done
echo
echo "--- Keycloak logs ---"
docker logs \
--since "${UPGRADE_STARTED:-30m}" \
keycloak || true
fail \
"Timeout waiting for Keycloak health=healthy after ${KEYCLOAK_START_TIMEOUT}s"
}
trap on_error ERR
# ============================================================
# Required commands
# ============================================================
log "Pre-flight checks"
for CMD in \
docker \
psql \
pg_dump \
pg_restore \
sha256sum \
sort \
grep \
find \
sed \
awk
do
command -v "${CMD}" >/dev/null 2>&1 || \
fail "Required command not found: ${CMD}"
done
docker compose version >/dev/null 2>&1 || \
fail "Docker Compose plugin is not available"
# ============================================================
# Required files
# ============================================================
[[ -f "${SRC}/docker-compose.yml" ]] || \
fail "${SRC}/docker-compose.yml not found"
[[ -f "${LIVE}/docker-compose.yml" ]] || \
fail "${LIVE}/docker-compose.yml not found"
[[ -f "${ENV_FILE}" ]] || \
fail "${ENV_FILE} not found"
# ============================================================
# Backup directory
# ============================================================
mkdir -p "${BACKUP_ROOT}"
[[ -w "${BACKUP_ROOT}" ]] || \
fail "${BACKUP_ROOT} is not writable"
# ============================================================
# Validate Compose
# ============================================================
log "Validate Docker Compose"
docker compose \
-f "${SRC}/docker-compose.yml" \
config >/dev/null
echo "Docker Compose configuration is valid."
# ============================================================
# Detect target Keycloak image
# ============================================================
log "Detect requested Keycloak image"
mapfile -t KEYCLOAK_IMAGES < <(
docker compose \
-f "${SRC}/docker-compose.yml" \
config --images |
sort -u
)
if [[ "${#KEYCLOAK_IMAGES[@]}" -ne 1 ]]; then
echo "Detected images:"
if (( ${#KEYCLOAK_IMAGES[@]} > 0 )); then
printf ' %s\n' "${KEYCLOAK_IMAGES[@]}"
else
echo " none"
fi
fail "Expected exactly one Keycloak image"
fi
NEW_IMAGE="${KEYCLOAK_IMAGES[0]}"
echo "Requested image:"
echo " ${NEW_IMAGE}"
# ============================================================
# Current Keycloak container
# ============================================================
docker inspect keycloak >/dev/null 2>&1 || \
fail "Current Keycloak container does not exist"
CURRENT_RUNNING="$(
docker inspect \
-f '{{.State.Running}}' \
keycloak
)"
[[ "${CURRENT_RUNNING}" == "true" ]] || \
fail "Current Keycloak container is not running"
OLD_IMAGE="$(
docker inspect \
-f '{{.Config.Image}}' \
keycloak
)"
# ============================================================
# Current health
# ============================================================
CURRENT_HEALTH="$(
docker inspect \
-f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' \
keycloak
)"
echo
echo "Current Keycloak health:"
echo " ${CURRENT_HEALTH}"
[[ "${CURRENT_HEALTH}" == "healthy" ]] || \
fail "Current Keycloak is not healthy before deployment"
# ============================================================
# Detect actual current Keycloak version
# ============================================================
log "Detect current Keycloak version"
CURRENT_VERSION_OUTPUT="$(
docker exec \
keycloak \
/opt/keycloak/bin/kc.sh \
--version
)"
CURRENT_KC_VERSION="$(
printf '%s\n' "${CURRENT_VERSION_OUTPUT}" |
extract_version
)"
[[ -n "${CURRENT_KC_VERSION}" ]] || \
fail "Could not determine current Keycloak version"
echo "Current Docker image:"
echo " ${OLD_IMAGE}"
echo "Current Keycloak version:"
echo " ${CURRENT_KC_VERSION}"
# ============================================================
# Pull target image
#
# This does not modify the running container.
# ============================================================
log "Pull target Keycloak image"
docker pull "${NEW_IMAGE}"
# ============================================================
# Detect actual target version
# ============================================================
log "Detect target Keycloak version"
TARGET_VERSION_OUTPUT="$(
docker run \
--rm \
--entrypoint /opt/keycloak/bin/kc.sh \
"${NEW_IMAGE}" \
--version
)"
TARGET_KC_VERSION="$(
printf '%s\n' "${TARGET_VERSION_OUTPUT}" |
extract_version
)"
[[ -n "${TARGET_KC_VERSION}" ]] || \
fail "Could not determine target Keycloak version"
echo "Target Docker image:"
echo " ${NEW_IMAGE}"
echo "Target Keycloak version:"
echo " ${TARGET_KC_VERSION}"
# ============================================================
# Version safety
# ============================================================
log "Validate version transition"
if version_less_than \
"${TARGET_KC_VERSION}" \
"${CURRENT_KC_VERSION}"
then
fail \
"Keycloak downgrade is not allowed: ${CURRENT_KC_VERSION} -> ${TARGET_KC_VERSION}"
fi
VERSION_CHANGED=false
if [[ "${CURRENT_KC_VERSION}" != "${TARGET_KC_VERSION}" ]]
then
VERSION_CHANGED=true
fi
echo "Current version: ${CURRENT_KC_VERSION}"
echo "Target version: ${TARGET_KC_VERSION}"
echo "Version changed: ${VERSION_CHANGED}"
# ============================================================
# Extract effective DB configuration from CURRENT container
# ============================================================
log "Detect Keycloak database configuration"
KC_DB="$(
get_container_env KC_DB
)"
KC_DB_URL="$(
get_container_env KC_DB_URL
)"
KC_DB_URL_HOST="$(
get_container_env KC_DB_URL_HOST
)"
KC_DB_URL_PORT="$(
get_container_env KC_DB_URL_PORT
)"
KC_DB_URL_DATABASE="$(
get_container_env KC_DB_URL_DATABASE
)"
KC_DB_USERNAME="$(
get_container_env KC_DB_USERNAME
)"
KC_DB_PASSWORD="$(
get_container_env KC_DB_PASSWORD
)"
KCRAW_DB_PASSWORD="$(
get_container_env KCRAW_DB_PASSWORD
)"
# ============================================================
# Database vendor
# ============================================================
if [[ -z "${KC_DB}" ]] &&
[[ "${KC_DB_URL}" == jdbc:postgresql://* ]]
then
KC_DB="postgres"
fi
[[ "${KC_DB}" == "postgres" ]] || \
fail "This deployment script currently supports PostgreSQL only. KC_DB=${KC_DB:-undefined}"
# ============================================================
# DB username/password
# ============================================================
[[ -n "${KC_DB_USERNAME}" ]] || \
fail "KC_DB_USERNAME is not defined"
if [[ -n "${KCRAW_DB_PASSWORD}" ]]; then
DB_PASSWORD="${KCRAW_DB_PASSWORD}"
else
DB_PASSWORD="${KC_DB_PASSWORD}"
fi
[[ -n "${DB_PASSWORD}" ]] || \
fail "Neither KC_DB_PASSWORD nor KCRAW_DB_PASSWORD is defined"
# ============================================================
# Parse PostgreSQL connection settings
#
# Priority:
#
# 1. KC_DB_URL
#
# 2. KC_DB_URL_HOST
# KC_DB_URL_PORT
# KC_DB_URL_DATABASE
# ============================================================
if [[ -n "${KC_DB_URL}" ]]
then
[[ "${KC_DB_URL}" == jdbc:postgresql://* ]] || \
fail "Unsupported KC_DB_URL: expected jdbc:postgresql://..."
DB_URL_NO_PREFIX="${KC_DB_URL#jdbc:postgresql://}"
# Remove JDBC query parameters.
DB_URL_NO_PARAMS="${DB_URL_NO_PREFIX%%\?*}"
DB_HOSTPORT="${DB_URL_NO_PARAMS%%/*}"
DB_NAME="${DB_URL_NO_PARAMS#*/}"
[[ "${DB_NAME}" != "${DB_URL_NO_PARAMS}" ]] || \
fail "Could not determine database name from KC_DB_URL"
# --------------------------------------------------------
# IPv6 [address]:port
# --------------------------------------------------------
if [[ "${DB_HOSTPORT}" =~ ^\[([^]]+)\](:([0-9]+))?$ ]]
then
DB_HOST="${BASH_REMATCH[1]}"
DB_PORT="${BASH_REMATCH[3]:-5432}"
# --------------------------------------------------------
# Standard hostname:port
# --------------------------------------------------------
elif [[ "${DB_HOSTPORT}" == *:* ]]
then
DB_HOST="${DB_HOSTPORT%%:*}"
DB_PORT="${DB_HOSTPORT##*:}"
else
DB_HOST="${DB_HOSTPORT}"
DB_PORT="5432"
fi
else
[[ -n "${KC_DB_URL_HOST}" ]] || \
fail "KC_DB_URL_HOST is not defined"
[[ -n "${KC_DB_URL_DATABASE}" ]] || \
fail "KC_DB_URL_DATABASE is not defined"
DB_HOST="${KC_DB_URL_HOST}"
DB_PORT="${KC_DB_URL_PORT:-5432}"
DB_NAME="${KC_DB_URL_DATABASE}"
fi
DB_USER="${KC_DB_USERNAME}"
echo "Database vendor:"
echo " postgres"
echo "Database host:"
echo " ${DB_HOST}"
echo "Database port:"
echo " ${DB_PORT}"
echo "Database name:"
echo " ${DB_NAME}"
echo "Database user:"
echo " ${DB_USER}"
# ============================================================
# PostgreSQL client environment
# ============================================================
export PGPASSWORD="${DB_PASSWORD}"
export PGCONNECT_TIMEOUT=10
# ============================================================
# PRE-UPGRADE BACKUP
#
# Created ONLY when Keycloak version changes.
# ============================================================
if [[ "${VERSION_CHANGED}" == "true" ]]
then
log "Prepare Keycloak pre-upgrade backup"
# ========================================================
# Backup directories
# ========================================================
mkdir -p "${DB_BACKUP_DIR}"
# ========================================================
# Save current Compose
#
# No cp -a because NFS does not support preserving
# all POSIX attributes in this setup.
# ========================================================
echo
echo "--- Backup current docker-compose.yml ---"
cp \
"${LIVE}/docker-compose.yml" \
"${BACKUP_DIR}/docker-compose.yml"
# ========================================================
# Metadata
#
# No passwords are written here.
# ========================================================
cat > "${BACKUP_DIR}/metadata.txt" <<EOF
timestamp=${STAMP}
current_image=${OLD_IMAGE}
target_image=${NEW_IMAGE}
current_keycloak_version=${CURRENT_KC_VERSION}
target_keycloak_version=${TARGET_KC_VERSION}
postgres_host=${DB_HOST}
postgres_port=${DB_PORT}
postgres_database=${DB_NAME}
postgres_user=${DB_USER}
EOF
# ========================================================
# PostgreSQL connection test
# ========================================================
echo
echo "--- Test PostgreSQL connection ---"
DB_TEST="$(
psql \
-h "${DB_HOST}" \
-p "${DB_PORT}" \
-U "${DB_USER}" \
-d "${DB_NAME}" \
-Atqc 'SELECT 1;'
)"
[[ "${DB_TEST}" == "1" ]] || \
fail "Cannot connect to PostgreSQL"
echo "PostgreSQL connection successful."
# ========================================================
# PostgreSQL versions
# ========================================================
echo
echo "--- PostgreSQL version check ---"
SERVER_VERSION="$(
psql \
-h "${DB_HOST}" \
-p "${DB_PORT}" \
-U "${DB_USER}" \
-d "${DB_NAME}" \
-Atqc 'SHOW server_version;'
)"
[[ -n "${SERVER_VERSION}" ]] || \
fail "Could not determine PostgreSQL server version"
echo "${SERVER_VERSION}" \
> "${DB_BACKUP_DIR}/postgresql-version.txt"
SERVER_MAJOR="${SERVER_VERSION%%.*}"
PG_DUMP_VERSION="$(
pg_dump --version |
sed -E 's/.* ([0-9]+(\.[0-9]+)?).*/\1/'
)"
PG_DUMP_MAJOR="${PG_DUMP_VERSION%%.*}"
echo "PostgreSQL server:"
echo " ${SERVER_VERSION}"
echo "pg_dump client:"
echo " ${PG_DUMP_VERSION}"
if (( PG_DUMP_MAJOR < SERVER_MAJOR ))
then
fail \
"pg_dump ${PG_DUMP_VERSION} is older than PostgreSQL server ${SERVER_VERSION}"
fi
# ========================================================
# DB size
# ========================================================
echo
echo "--- Database size ---"
DB_SIZE="$(
psql \
-h "${DB_HOST}" \
-p "${DB_PORT}" \
-U "${DB_USER}" \
-d "${DB_NAME}" \
-Atqc \
'SELECT pg_size_pretty(pg_database_size(current_database()));'
)"
echo "Database size:"
echo " ${DB_SIZE}"
# ========================================================
# pg_dump
# ========================================================
log "Create PostgreSQL backup"
pg_dump \
-h "${DB_HOST}" \
-p "${DB_PORT}" \
-U "${DB_USER}" \
-d "${DB_NAME}" \
--format=custom \
--no-owner \
--no-privileges \
--file="${DB_BACKUP_FILE}"
[[ -s "${DB_BACKUP_FILE}" ]] || \
fail "PostgreSQL dump does not exist or is empty"
# ========================================================
# Validate archive
# ========================================================
echo
echo "--- Validate PostgreSQL dump ---"
pg_restore \
--list \
"${DB_BACKUP_FILE}" \
> "${DB_BACKUP_DIR}/keycloak.dump.list"
[[ -s "${DB_BACKUP_DIR}/keycloak.dump.list" ]] || \
fail "pg_restore could not validate PostgreSQL dump"
# ========================================================
# SHA256
# ========================================================
echo
echo "--- Generate SHA256 ---"
(
cd "${DB_BACKUP_DIR}"
sha256sum \
keycloak.dump \
> keycloak.dump.sha256
sha256sum \
-c keycloak.dump.sha256
)
DB_BACKUP_SIZE="$(
du -h "${DB_BACKUP_FILE}" |
awk '{print $1}'
)"
echo
echo "PostgreSQL backup successful."
echo
echo "Backup directory:"
echo " ${BACKUP_DIR}"
echo
echo "Database dump:"
echo " ${DB_BACKUP_FILE}"
echo
echo "Dump size:"
echo " ${DB_BACKUP_SIZE}"
echo
echo "SHA256:"
cat "${DB_BACKUP_DIR}/keycloak.dump.sha256"
# ========================================================
# Retention
#
# Delete old copies only after new dump has been
# successfully created and validated.
# ========================================================
cleanup_old_backups
fi
# ============================================================
# Deploy new Compose
#
# Nothing in production is changed before successful DB backup.
# ============================================================
log "Deploy docker-compose.yml"
cp \
"${SRC}/docker-compose.yml" \
"${LIVE}/docker-compose.yml"
cd "${LIVE}"
# ============================================================
# Pull Compose images
# ============================================================
log "Pull Docker images"
docker compose pull
# ============================================================
# KEYCLOAK VERSION UPGRADE
# ============================================================
if [[ "${VERSION_CHANGED}" == "true" ]]
then
log "Keycloak upgrade ${CURRENT_KC_VERSION} -> ${TARGET_KC_VERSION}"
# ========================================================
# Stop old Keycloak before DB migration
# ========================================================
echo
echo "--- Stop old Keycloak ---"
docker compose stop keycloak
# ========================================================
# Timestamp for new container logs
# ========================================================
UPGRADE_STARTED="$(
date -u '+%Y-%m-%dT%H:%M:%SZ'
)"
# ========================================================
# Start new Keycloak
#
# Keycloak performs required Liquibase schema migration
# automatically during startup.
# ========================================================
echo
echo "--- Start new Keycloak ---"
docker compose \
up -d \
--no-deps \
keycloak
# ========================================================
# Wait until migration/startup is complete
# ========================================================
log "Wait for Keycloak startup and DB migration"
wait_for_keycloak
# ========================================================
# Startup logs
# ========================================================
log "Keycloak startup log"
STARTUP_LOG="$(
docker logs \
--since "${UPGRADE_STARTED}" \
keycloak 2>&1
)"
echo "${STARTUP_LOG}"
# ========================================================
# Obvious migration failure detection
#
# health=healthy remains the authoritative success check.
# ========================================================
if echo "${STARTUP_LOG}" |
grep -Eqi \
'liquibase[^[:cntrl:]]*(failed|failure|exception)|database migration[^[:cntrl:]]*(failed|failure|exception)'
then
fail "Keycloak startup log contains a database migration failure"
fi
# ========================================================
# Verify actual running version
# ========================================================
log "Validate running Keycloak version"
RUNNING_VERSION_OUTPUT="$(
docker exec \
keycloak \
/opt/keycloak/bin/kc.sh \
--version
)"
RUNNING_KC_VERSION="$(
printf '%s\n' "${RUNNING_VERSION_OUTPUT}" |
extract_version
)"
[[ -n "${RUNNING_KC_VERSION}" ]] || \
fail "Could not determine running Keycloak version"
echo "Expected version:"
echo " ${TARGET_KC_VERSION}"
echo "Running version:"
echo " ${RUNNING_KC_VERSION}"
[[ "${RUNNING_KC_VERSION}" == "${TARGET_KC_VERSION}" ]] || \
fail \
"Running Keycloak version does not match target: ${RUNNING_KC_VERSION} != ${TARGET_KC_VERSION}"
# ============================================================
# NO KEYCLOAK VERSION CHANGE
# ============================================================
else
log "No Keycloak version change"
echo "Current version:"
echo " ${CURRENT_KC_VERSION}"
docker compose \
up -d \
--remove-orphans
UPGRADE_STARTED="$(
date -u -d '10 minutes ago' '+%Y-%m-%dT%H:%M:%SZ'
)"
wait_for_keycloak
fi
# ============================================================
# Final container status
# ============================================================
log "Final container status"
FINAL_RUNNING="$(
docker inspect \
-f '{{.State.Running}}' \
keycloak
)"
FINAL_HEALTH="$(
docker inspect \
-f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' \
keycloak
)"
[[ "${FINAL_RUNNING}" == "true" ]] || \
fail "Keycloak container is not running"
[[ "${FINAL_HEALTH}" == "healthy" ]] || \
fail "Keycloak container is not healthy"
echo "Container:"
echo " running"
echo "Health:"
echo " ${FINAL_HEALTH}"
# ============================================================
# Final version
# ============================================================
FINAL_VERSION_OUTPUT="$(
docker exec \
keycloak \
/opt/keycloak/bin/kc.sh \
--version
)"
FINAL_KC_VERSION="$(
printf '%s\n' "${FINAL_VERSION_OUTPUT}" |
extract_version
)"
[[ -n "${FINAL_KC_VERSION}" ]] || \
fail "Could not determine final Keycloak version"
[[ "${FINAL_KC_VERSION}" == "${TARGET_KC_VERSION}" ]] || \
fail \
"Final Keycloak version mismatch: ${FINAL_KC_VERSION} != ${TARGET_KC_VERSION}"
# ============================================================
# Final image
# ============================================================
RUNNING_IMAGE="$(
docker inspect \
-f '{{.Config.Image}}' \
keycloak
)"
# ============================================================
# Success
# ============================================================
log "Keycloak deployment successful"
echo "Previous Keycloak version:"
echo " ${CURRENT_KC_VERSION}"
echo
echo "Running Keycloak version:"
echo " ${FINAL_KC_VERSION}"
echo
echo "Previous Docker image:"
echo " ${OLD_IMAGE}"
echo
echo "Running Docker image:"
echo " ${RUNNING_IMAGE}"
if [[ "${VERSION_CHANGED}" == "true" ]]
then
echo
echo "Pre-upgrade backup:"
echo " ${BACKUP_DIR}"
echo
echo "PostgreSQL dump:"
echo " ${DB_BACKUP_FILE}"
echo
echo "Backup retention:"
echo " last ${BACKUP_KEEP} backups"
fi
# ============================================================
# Clear DB credentials
# ============================================================
unset PGPASSWORD
unset DB_PASSWORD
unset KC_DB_PASSWORD
unset KCRAW_DB_PASSWORD