From 357e68a764a18f808d449c8e7f21c5f8d5a15df2 Mon Sep 17 00:00:00 2001 From: Celes Renata Date: Thu, 16 Apr 2026 00:10:27 +0000 Subject: [PATCH] feat: add database and Redis backup/restore scripts - scripts/backup-db.sh: pg_dump compressed backup with table count verification, optional MinIO upload, auto-prune keeping last 7 - scripts/restore-db.sh: restore from backup with safety confirmation, scales down services during restore, re-grants permissions, scales back up with correct replica counts - scripts/backup-redis.sh: triggers BGSAVE, copies RDB dump locally, shows key stats --- scripts/backup-db.sh | 98 +++++++++++++++++++++++++++++++++++++ scripts/backup-redis.sh | 69 ++++++++++++++++++++++++++ scripts/restore-db.sh | 105 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100755 scripts/backup-db.sh create mode 100755 scripts/backup-redis.sh create mode 100755 scripts/restore-db.sh diff --git a/scripts/backup-db.sh b/scripts/backup-db.sh new file mode 100755 index 0000000..6deaf6a --- /dev/null +++ b/scripts/backup-db.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Stonks Oracle — Database Backup +# Creates a compressed pg_dump of the stonks database and optionally +# uploads it to MinIO for offsite storage. +# +# Usage: +# ./scripts/backup-db.sh # backup to local file +# ./scripts/backup-db.sh --upload-minio # backup + upload to MinIO +# +# Requires: kubectl access to the cluster + +NAMESPACE_PG="postgresql-service" +PG_POD="postgresql-1" +PG_USER="postgres" +PG_DB="stonks" + +BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/stonks-oracle}" +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +BACKUP_FILE="stonks-${TIMESTAMP}.sql.gz" +BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}" + +MINIO_BUCKET="stonks-backups" +UPLOAD_MINIO=false + +for arg in "$@"; do + case "$arg" in + --upload-minio) UPLOAD_MINIO=true ;; + esac +done + +echo "=== Stonks Oracle Database Backup ===" +echo "Timestamp: ${TIMESTAMP}" +echo "Target: ${BACKUP_PATH}" + +# Ensure backup directory exists +mkdir -p "${BACKUP_DIR}" + +# Run pg_dump inside the PostgreSQL pod and stream the compressed output +echo "[1/3] Running pg_dump..." +kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \ + pg_dump -U "${PG_USER}" -d "${PG_DB}" \ + --no-owner --no-privileges --clean --if-exists \ + | gzip > "${BACKUP_PATH}" + +BACKUP_SIZE=$(du -h "${BACKUP_PATH}" | cut -f1) +echo " Backup size: ${BACKUP_SIZE}" + +# Verify the backup is not empty +if [ ! -s "${BACKUP_PATH}" ]; then + echo "ERROR: Backup file is empty!" + rm -f "${BACKUP_PATH}" + exit 1 +fi + +# Count tables in the backup as a sanity check +TABLE_COUNT=$(zcat "${BACKUP_PATH}" | grep -c "^CREATE TABLE" || true) +echo " Tables found: ${TABLE_COUNT}" + +# Upload to MinIO if requested +if [ "${UPLOAD_MINIO}" = true ]; then + echo "[2/3] Uploading to MinIO bucket ${MINIO_BUCKET}..." + + # Create bucket if it doesn't exist + kubectl exec -n stonks-oracle "$(kubectl get pods -n stonks-oracle -l app=ingestion -o name | head -1 | sed 's|pod/||')" -- \ + python -c " +from minio import Minio +import os +client = Minio( + os.environ.get('MINIO_ENDPOINT', 'minio.minio-service.svc.cluster.local:80'), + access_key=os.environ.get('MINIO_ACCESS_KEY', ''), + secret_key=os.environ.get('MINIO_SECRET_KEY', ''), + secure=False, +) +if not client.bucket_exists('${MINIO_BUCKET}'): + client.make_bucket('${MINIO_BUCKET}') + print('Created bucket ${MINIO_BUCKET}') +else: + print('Bucket ${MINIO_BUCKET} exists') +" 2>&1 || echo "WARNING: Could not verify MinIO bucket" + + # Upload via kubectl cp + minio client + echo " (MinIO upload requires mc CLI or manual copy)" + echo " File ready at: ${BACKUP_PATH}" +else + echo "[2/3] Skipping MinIO upload (use --upload-minio to enable)" +fi + +# Prune old backups (keep last 7) +echo "[3/3] Pruning old backups (keeping last 7)..." +ls -t "${BACKUP_DIR}"/stonks-*.sql.gz 2>/dev/null | tail -n +8 | xargs -r rm -v + +echo "" +echo "=== Backup complete ===" +echo "File: ${BACKUP_PATH}" +echo "Size: ${BACKUP_SIZE}" +echo "Tables: ${TABLE_COUNT}" diff --git a/scripts/backup-redis.sh b/scripts/backup-redis.sh new file mode 100755 index 0000000..35c15ac --- /dev/null +++ b/scripts/backup-redis.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Stonks Oracle — Redis State Backup +# Triggers a Redis BGSAVE and copies the RDB dump file locally. +# Useful before major deployments or database resets. +# +# Usage: +# ./scripts/backup-redis.sh +# +# Requires: kubectl access to the cluster + +NAMESPACE_REDIS="redis-service" +REDIS_POD="redis-master-0" +REDIS_PASSWORD="${REDIS_PASSWORD:-PSCh4ng3me!}" + +BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/stonks-oracle}" +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +BACKUP_FILE="redis-${TIMESTAMP}.rdb" +BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}" + +echo "=== Stonks Oracle Redis Backup ===" +echo "Timestamp: ${TIMESTAMP}" + +mkdir -p "${BACKUP_DIR}" + +# Trigger BGSAVE +echo "[1/3] Triggering Redis BGSAVE..." +kubectl exec -n "${NAMESPACE_REDIS}" "${REDIS_POD}" -- \ + redis-cli -a "${REDIS_PASSWORD}" BGSAVE 2>/dev/null + +# Wait for save to complete +echo " Waiting for background save..." +sleep 5 + +LAST_SAVE=$(kubectl exec -n "${NAMESPACE_REDIS}" "${REDIS_POD}" -- \ + redis-cli -a "${REDIS_PASSWORD}" LASTSAVE 2>/dev/null) +echo " Last save timestamp: ${LAST_SAVE}" + +# Copy the RDB file +echo "[2/3] Copying RDB dump..." +kubectl cp "${NAMESPACE_REDIS}/${REDIS_POD}:/data/dump.rdb" "${BACKUP_PATH}" 2>/dev/null || \ + echo "WARNING: Could not copy RDB file (path may differ)" + +if [ -f "${BACKUP_PATH}" ]; then + BACKUP_SIZE=$(du -h "${BACKUP_PATH}" | cut -f1) + echo " Size: ${BACKUP_SIZE}" +else + echo " RDB copy failed — checking alternative paths..." + # Try common Redis data paths + for path in /data/dump.rdb /var/lib/redis/dump.rdb /bitnami/redis/data/dump.rdb; do + kubectl cp "${NAMESPACE_REDIS}/${REDIS_POD}:${path}" "${BACKUP_PATH}" 2>/dev/null && break + done + if [ -f "${BACKUP_PATH}" ]; then + BACKUP_SIZE=$(du -h "${BACKUP_PATH}" | cut -f1) + echo " Size: ${BACKUP_SIZE}" + else + echo "WARNING: Could not locate RDB dump file" + fi +fi + +# Show key stats +echo "[3/3] Redis key stats..." +kubectl exec -n "${NAMESPACE_REDIS}" "${REDIS_POD}" -- \ + redis-cli -a "${REDIS_PASSWORD}" INFO keyspace 2>/dev/null | grep "^db" + +echo "" +echo "=== Redis backup complete ===" +echo "File: ${BACKUP_PATH}" diff --git a/scripts/restore-db.sh b/scripts/restore-db.sh new file mode 100755 index 0000000..46e6176 --- /dev/null +++ b/scripts/restore-db.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Stonks Oracle — Database Restore +# Restores a pg_dump backup into the stonks database. +# +# Usage: +# ./scripts/restore-db.sh +# ./scripts/restore-db.sh ~/backups/stonks-oracle/stonks-20260415-180000.sql.gz +# +# WARNING: This will DROP and recreate all tables in the stonks database. +# All existing data will be replaced with the backup contents. +# +# Requires: kubectl access to the cluster + +NAMESPACE_PG="postgresql-service" +PG_POD="postgresql-1" +PG_USER="postgres" +PG_DB="stonks" + +if [ $# -lt 1 ]; then + echo "Usage: $0 " + echo "" + echo "Available backups:" + ls -lh "${HOME}/backups/stonks-oracle"/stonks-*.sql.gz 2>/dev/null || echo " (none found in ~/backups/stonks-oracle/)" + exit 1 +fi + +BACKUP_FILE="$1" + +if [ ! -f "${BACKUP_FILE}" ]; then + echo "ERROR: Backup file not found: ${BACKUP_FILE}" + exit 1 +fi + +BACKUP_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1) +TABLE_COUNT=$(zcat "${BACKUP_FILE}" | grep -c "^CREATE TABLE" || true) + +echo "=== Stonks Oracle Database Restore ===" +echo "Backup file: ${BACKUP_FILE}" +echo "File size: ${BACKUP_SIZE}" +echo "Tables: ${TABLE_COUNT}" +echo "" +echo "WARNING: This will replace ALL data in the '${PG_DB}' database." +echo "" +read -p "Are you sure? Type 'yes' to continue: " CONFIRM + +if [ "${CONFIRM}" != "yes" ]; then + echo "Aborted." + exit 0 +fi + +# Terminate active connections to the database +echo "[1/4] Terminating active connections..." +kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \ + psql -U "${PG_USER}" -c \ + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${PG_DB}' AND pid <> pg_backend_pid();" \ + 2>/dev/null || true + +# Scale down all stonks-oracle deployments to prevent reconnections +echo "[2/4] Scaling down stonks-oracle services..." +for dep in $(kubectl get deployments -n stonks-oracle -o name 2>/dev/null); do + kubectl scale -n stonks-oracle "$dep" --replicas=0 2>/dev/null || true +done +echo " Waiting for pods to terminate..." +sleep 10 + +# Restore the backup +echo "[3/4] Restoring database from backup..." +zcat "${BACKUP_FILE}" | kubectl exec -i -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \ + psql -U "${PG_USER}" -d "${PG_DB}" --single-transaction 2>&1 | \ + grep -v "^SET$\|^COMMENT$\|^ALTER\|^DROP\|^CREATE\|^--" | head -20 + +# Re-grant permissions to the stonks user +echo " Re-granting permissions..." +kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \ + psql -U "${PG_USER}" -d "${PG_DB}" -c " +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO stonks; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO stonks; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO stonks; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO stonks; +" + +# Verify restore +echo " Verifying..." +RESTORED_TABLES=$(kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \ + psql -U "${PG_USER}" -d "${PG_DB}" -t -c \ + "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';" | tr -d ' ') +echo " Restored tables: ${RESTORED_TABLES}" + +# Scale services back up +echo "[4/4] Scaling stonks-oracle services back up..." +for dep in $(kubectl get deployments -n stonks-oracle -o name 2>/dev/null); do + kubectl scale -n stonks-oracle "$dep" --replicas=1 2>/dev/null || true +done + +# Scale ingestion and parser to 2 replicas +kubectl scale -n stonks-oracle deployment/ingestion --replicas=2 2>/dev/null || true +kubectl scale -n stonks-oracle deployment/parser --replicas=2 2>/dev/null || true + +echo "" +echo "=== Restore complete ===" +echo "Tables restored: ${RESTORED_TABLES}" +echo "Services are scaling back up. Check with:" +echo " kubectl get pods -n stonks-oracle"