104 lines
3.9 KiB
Bash
Executable File
104 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
|
env_file="${FRAMEFLOW_ENV_FILE:-${repo_dir}/.env.production}"
|
|
backup_parent="${1:-${repo_dir}/backups}"
|
|
metrics_dir="${FRAMEFLOW_BACKUP_METRICS_DIR:-/var/lib/frameflow/metrics}"
|
|
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
backup_dir="${backup_parent%/}/frameflow-${timestamp}"
|
|
started_at_seconds="$(date +%s)"
|
|
metrics_tmp=""
|
|
|
|
cleanup() {
|
|
if [[ -n "${metrics_tmp}" && -f "${metrics_tmp}" ]]; then
|
|
rm -f "${metrics_tmp}"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
if [[ ! -f "${env_file}" ]]; then
|
|
echo "Production environment file not found: ${env_file}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
compose=(docker compose --env-file "${env_file}" -f "${repo_dir}/docker-compose.prod.yml")
|
|
mkdir -p "${backup_parent}"
|
|
if ! mkdir "${backup_dir}"; then
|
|
echo "Refusing to overwrite an existing backup directory: ${backup_dir}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
postgres_container="$("${compose[@]}" ps -q postgres)"
|
|
redis_container="$("${compose[@]}" ps -q redis)"
|
|
minio_container="$("${compose[@]}" ps -q minio)"
|
|
|
|
for container in "${postgres_container}" "${redis_container}" "${minio_container}"; do
|
|
if [[ -z "${container}" ]]; then
|
|
echo "PostgreSQL, Redis, and MinIO must be running before backup." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "Creating PostgreSQL logical backup..."
|
|
"${compose[@]}" exec -T postgres sh -c 'exec pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "${backup_dir}/postgres.dump"
|
|
|
|
echo "Flushing and archiving Redis persistence..."
|
|
"${compose[@]}" exec -T redis sh -c '
|
|
if [ -n "${REDIS_PASSWORD:-}" ]; then
|
|
redis-cli --no-auth-warning -a "$REDIS_PASSWORD" SAVE >/dev/null
|
|
else
|
|
redis-cli SAVE >/dev/null
|
|
fi
|
|
'
|
|
docker run --rm --volumes-from "${redis_container}" -v "${backup_dir}:/backup" alpine:3.20 \
|
|
tar -czf /backup/redis-data.tar.gz -C /data .
|
|
|
|
echo "Archiving MinIO object data..."
|
|
docker run --rm --volumes-from "${minio_container}" -v "${backup_dir}:/backup" alpine:3.20 \
|
|
tar -czf /backup/minio-data.tar.gz -C /data .
|
|
|
|
{
|
|
echo "created_at=${timestamp}"
|
|
echo "compose_project=$(docker inspect --format '{{ index .Config.Labels "com.docker.compose.project" }}' "${postgres_container}")"
|
|
echo "postgres_container=${postgres_container}"
|
|
echo "redis_container=${redis_container}"
|
|
echo "minio_container=${minio_container}"
|
|
} > "${backup_dir}/manifest.txt"
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
(cd "${backup_dir}" && sha256sum postgres.dump redis-data.tar.gz minio-data.tar.gz > SHA256SUMS)
|
|
else
|
|
(cd "${backup_dir}" && shasum -a 256 postgres.dump redis-data.tar.gz minio-data.tar.gz > SHA256SUMS)
|
|
fi
|
|
|
|
"${repo_dir}/scripts/verify-backup.sh" "${backup_dir}"
|
|
|
|
completed_at_seconds="$(date +%s)"
|
|
backup_size_bytes=0
|
|
for backup_file in postgres.dump redis-data.tar.gz minio-data.tar.gz manifest.txt SHA256SUMS; do
|
|
file_size="$(wc -c < "${backup_dir}/${backup_file}" | tr -d '[:space:]')"
|
|
backup_size_bytes=$((backup_size_bytes + file_size))
|
|
done
|
|
|
|
mkdir -p "${metrics_dir}"
|
|
metrics_tmp="$(mktemp "${metrics_dir%/}/frameflow_backup.prom.tmp.XXXXXX")"
|
|
cat > "${metrics_tmp}" <<EOF
|
|
# HELP frameflow_backup_last_success_timestamp_seconds Unix timestamp of the last complete FrameFlow backup.
|
|
# TYPE frameflow_backup_last_success_timestamp_seconds gauge
|
|
frameflow_backup_last_success_timestamp_seconds ${completed_at_seconds}
|
|
# HELP frameflow_backup_last_size_bytes Total size of files in the last complete FrameFlow backup.
|
|
# TYPE frameflow_backup_last_size_bytes gauge
|
|
frameflow_backup_last_size_bytes ${backup_size_bytes}
|
|
# HELP frameflow_backup_last_duration_seconds Duration of the last complete FrameFlow backup.
|
|
# TYPE frameflow_backup_last_duration_seconds gauge
|
|
frameflow_backup_last_duration_seconds $((completed_at_seconds - started_at_seconds))
|
|
EOF
|
|
chmod 0644 "${metrics_tmp}"
|
|
mv -f "${metrics_tmp}" "${metrics_dir%/}/frameflow_backup.prom"
|
|
metrics_tmp=""
|
|
|
|
echo "Backup complete: ${backup_dir}"
|
|
echo "Backup metrics updated: ${metrics_dir%/}/frameflow_backup.prom"
|